Document the full subagent lifecycle: spawn, concurrency queue,
execution, completion handling, two-tier announcement delivery,
and record archival. Include provider inheritance chain and
error propagation diagrams.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add error reporting rule to subagent system prompt: subagents must
explicitly report tool failures and missing credentials in their
final message. Add timeout guidelines to parent system prompt with
recommended values by task complexity (10-30 min).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When subagents are still running, sessions_list now returns an
explicit instruction telling the LLM not to poll again and wait
for automatic result delivery. Normalizes status display to
uppercase ([RUNNING], [OK], [ERROR]).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass parent's resolvedProvider through the tool chain (tools.ts →
sessions-spawn.ts → hub.createSubagent) so subagents use the same
LLM provider as the parent. Previously subagents fell back to the
hardcoded default provider, causing API key errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace immediate record deletion with archiveAtMs-based deferred
cleanup (60min retention). This keeps records queryable via
sessions_list after completion. Add sweeper to clean expired records.
Check childAgent.lastRunError after waitForIdle to detect failed runs
that resolve the promise without throwing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add isRunning flag to Agent (runner.ts) for detecting active runs.
Add lastRunError to AsyncAgent for propagating child run errors to
the registry. Fix duplicate message emission in writeInternal by
resetting forwardInternalAssistant before persistAssistantSummary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add announce-queue.ts for batching nearby subagent completions with
debounce (1s) and collect mode. Implement two-tier delivery in
announce.ts: queue when parent is busy, writeInternal when idle.
All delivery uses writeInternal() to mark messages as internal,
preventing announcement bubbles from appearing in the UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
setProvider() updated the resolved provider and model but did not
rebuild the system prompt, so the runtime info line still showed the
old provider/model (e.g. claude-code/claude-opus-4-6) after switching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Registry model IDs used hyphens (claude-sonnet-4-5) but pi-ai expects
dots (claude-sonnet-4.5). Also adds a fallback model config for OpenRouter
models not in pi-ai's registry, since OpenRouter supports thousands of models.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Queued through the serialization queue to safely switch provider,
send a minimal test prompt, and restore the previous provider.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change primary from custom purple oklch(0.51 0.23 277) to shadcn
default neutral near-black/near-white. Also reset chart and sidebar
primary colors to shadcn defaults.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire registry and sessions_spawn through the lane-based queue so
sub-agents respect max concurrency. Add resolveSubagentTimeoutMs()
with defaults (10 min), 0 = no timeout, and safe clamping.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a command queue system adapted from OpenClaw to prevent
unbounded sub-agent spawning. Default max concurrency: 10.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add clientName field to DeviceMeta so non-browser clients (Telegram,
Discord, etc.) can provide a human-readable label instead of relying on
userAgent parsing. Desktop UI now prioritizes clientName over parsed UA
string, fixing "Unknown on Unknown" display for Telegram connections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the new channel system design: FIFO pendingRoutes queue,
activeRoute/activeAcks state, agent_start/agent_end lifecycle,
InboundDebouncer, typing/reaction lifecycle, and UI metadata stripping.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add stripUserMetadata() to remove timestamp envelopes and media type
labels ([Voice Message] Transcript:, [Image] Description:, etc.) that
are injected for LLM context but should not appear in the desktop UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the agent fails due to missing API key, the error banner now
shows a "Configure" button that opens the same ApiKeyDialog (or
OAuthDialog) used on the home page. After successful configuration
the error clears and the user can immediately start chatting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
StreamPayload.event was missing the agent_error event type, causing
TypeScript errors in useGatewayChat and useLocalChat where the
comparison payload.event.type === "agent_error" had no type overlap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Desktop path already forwards agent_error to chat.setError() via
use-local-chat.ts, but the Web/Gateway path was missing this handling.
Add agent_error interception in the StreamAction branch so Web clients
render LLM errors the same way Desktop does.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the agent encounters an error (e.g. no API key configured),
the Chat UI now shows an error banner instead of silently hanging.
The user can still type and retry after fixing their configuration.
- Add AgentErrorEvent to SDK stream types
- Forward agent_error events through IPC to renderer
- Handle error events in useLocalChat hook
- Keep chat input enabled for AGENT_ERROR (retriable)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After the chat refactoring moved state management to @multica/hooks,
the Zustand stores (useConnectionStore, useMessagesStore, useAutoConnect)
are no longer imported by any application code. This removes them along
with their unused dependencies (zustand, uuid, react).
- Delete connection-store.ts, messages.ts, use-auto-connect.ts
- Extract Message/ToolStatus types into types.ts (preserves UI imports)
- Remove saveConnection/loadConnection/clearConnection from connection.ts
- Drop zustand, uuid, react deps from package.json
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Return latest messages by default instead of oldest. Support paginated
loading of older messages when scrolling up via IntersectionObserver,
with scrollHeight compensation to preserve scroll position.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move gateway-specific chat logic into dedicated useGatewayChat hook.
useChat remains a pure state hook with no IO. Update ChatView props,
remove legacy chat.tsx and connect-prompt.tsx.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract StatusWrapper to replace duplicated fullscreen/inline wrapper in
ConnectionStatus and RejectedStatus. Extract PairingHeader to replace
repeated title+description blocks in mobile and desktop views. Make
desktop container mb responsive (mb-14 sm:mb-28).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move all CSS out of globals.css into inline styles within each component.
Loading (Apple-style radiating lines) for passive waiting states;
Spinner (3x3 grid pulse) for active processing/execution states.
Switch device-pairing ConnectionStatus from Loading to Spinner.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract ChatView from web chat-page into packages/ui as a prop-driven
component (accepts UseChatReturn shape, no transport dependency)
- Move DevicePairing from apps/web to packages/ui with locally defined
ConnectionIdentity type (no @multica/hooks dependency)
- Create @multica/hooks package with useGatewayConnection and useChat
(moved from apps/web/hooks)
- Add isLoadingHistory state to useChat with skeleton loading in ChatView
- Add MulticaIcon (pure CSS asterisk via clip-path, adapts to theme)
- Slim web chat-page.tsx from 188 to 65 lines (just wires hooks to UI)
Desktop can now reuse ChatView and DevicePairing directly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Wrap maybeCompact() in try/catch to ensure compaction_end always fires
- Widen multicaListeners type to match subscribeAll() callback signature
- Import CompactionEndEvent from SDK instead of inline type casts
- Add doc comment explaining reason field type difference (agent vs SDK)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Forward compaction_start/compaction_end events through Hub (Gateway path)
and Desktop IPC (local path) to the Zustand messages store. Adds
CompactionEvent types to the SDK, compacting/lastCompaction state to
useMessagesStore, and event routing in both connection-store and
use-local-chat.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ExecApprovalItem for human-in-the-loop command approval with uniform
outline buttons (Allow/Always/Deny), countdown timer, and command display.
Refine ToolCallItem and ThinkingItem: transparent by default, unified
bg-muted/30 wrapper on expand with seamless button+content integration.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ThinkingItem component for displaying LLM extended thinking. Renders
as a collapsible row (matching ToolCallItem style) with expand to reveal
thinking text. MessageList now extracts and renders ThinkingContent blocks
before text content, matching the LLM output order.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add break-words to markdown-content wrapper so URLs and file paths in
<p>, <li>, <a> etc. wrap at container boundary instead of overflowing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>