Commit graph

141 commits

Author SHA1 Message Date
Jiang Bohan
c49ab1fee4 fix(heartbeat): use internal run to hide heartbeat from chat history
The heartbeat runner used agent.write() (normal write), which persisted
both the heartbeat prompt and HEARTBEAT_OK response as regular messages
visible in the UI chat history.

Switch to runInternalForResult() — a new AsyncAgent method that runs
via runInternal() (messages marked internal: true, rolled back from
in-memory state). This hides both the heartbeat prompt and response
from the UI entirely, while still persisting to JSONL for diagnostics.

The previous commit's event-stream heartbeat ACK filter remains as a
defense-in-depth layer for edge cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:41:57 +08:00
Jiang Bohan
8d6a803739 fix(agent): filter heartbeat ACK messages from desktop event stream
The heartbeat runner uses agent.write() (normal write), so heartbeat
ACK responses like "HEARTBEAT_OK" were not suppressed by the internal
run filter and leaked into the desktop UI chat. The Gateway path was
already fixed (Hub has delayed-start + isHeartbeatAckEvent filtering),
but the local Desktop path through AsyncAgent had no such filtering.

Add createFilteredHandler() to AsyncAgent that buffers message_start
for assistant messages and checks subsequent events with
isHeartbeatAckEvent(). Pure heartbeat ACKs are suppressed end-to-end;
all other messages are forwarded normally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:34:23 +08:00
Bohan Jiang
16cd5d0aaf
Merge pull request #126 from multica-ai/feat/subagent-announce-delivery
feat(subagent): two-tier announce delivery, provider inheritance, error propagation
2026-02-11 19:04:39 +08:00
Jiang Bohan
af3a42a00e fix(subagent): forward announcement summary to UI stream
Changed forwardAssistant from false to true in sendAnnounceDirect() so
the assistant's summary response is streamed to the desktop UI in
real-time. The announcement prompt stays internal (hidden from UI), but
the user now sees the completion notification.

Previously, persistAssistantSummary saved the response to JSONL but
never emitted events to the UI subscriber, leaving users with no
visible feedback after subagent tasks completed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:58:32 +08:00
Jiang Bohan
47cbf60687 fix(agent): reload tools after setProvider to fix stale closure
setProvider() updated toolsOptions.provider but didn't reload tool
instances. The sessions_spawn tool captured the old provider in its
closure at creation time, causing subagents to inherit a stale provider.

Now calls resolveTools() + setTools() after updating toolsOptions so
sessions_spawn gets a fresh closure with the correct provider.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:58:24 +08:00
Jiang Bohan
d7a02182ab feat(subagent): add announce:"silent" mode for deferred coalesced announcements
Add an `announce` parameter to sessions_spawn that controls when findings
are delivered to the parent agent:

- "immediate" (default): announce per-completion (existing behavior)
- "silent": defer until ALL silent runs from the same requester complete,
  then deliver ONE coalesced announcement with all findings

This enables workflows like "spawn 10 parallel subagents to collect data,
then summarize everything at once" without intermediate results.

Changes:
- types.ts: add `announce` field to SubagentRunRecord & RegisterSubagentRunParams
- sessions-spawn.ts: add `announce` parameter to tool schema
- registry.ts: split checkAndAnnounce into immediate/silent groups,
  extract announceGroup helper, use count-match guard for silent readiness
- sections.ts: add announce mode guidance to system prompt
- registry.test.ts: add silent mode tests (field storage, group isolation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:40:00 +08:00
Jiang Bohan
de928cfe2b fix(agent): add NO_REPLY detection utility for filtering silent replies
Extract SILENT_REPLY_TOKEN and isSilentReplyText() into a shared
module. Detects NO_REPLY at the start or end of text (with optional
whitespace/punctuation) to filter out silent announcement responses
that should not be forwarded to the user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:16:22 +08:00
Jiang Bohan
d01fcffe32 docs(subagent): add architecture flowchart and README
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>
2026-02-11 17:10:12 +08:00
Jiang Bohan
58ec56234c feat(subagent): improve system prompts for error reporting and timeout
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>
2026-02-11 17:10:05 +08:00
Jiang Bohan
716e69ac39 feat(subagent): add anti-polling guards to sessions_list
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>
2026-02-11 17:09:56 +08:00
Jiang Bohan
121b644df4 fix(subagent): inherit parent provider in spawned subagents
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>
2026-02-11 17:09:49 +08:00
Jiang Bohan
2e6d419c27 feat(subagent): deferred record cleanup and error propagation
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>
2026-02-11 17:09:42 +08:00
Jiang Bohan
299c947893 feat(agent): expose isRunning and lastRunError on Agent and AsyncAgent
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>
2026-02-11 17:09:33 +08:00
Jiang Bohan
45db13cbdd feat(subagent): add two-tier announce delivery with debounced queue
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>
2026-02-11 17:09:26 +08:00
Jiayuan Zhang
4e5c99a97f feat(ui): show task context for sessions_spawn in chat UI
Display the label (or truncated task) as subtitle for SpawnSession tool
calls, so users can see what each spawned session is working on.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:59:16 +08:00
Jiayuan Zhang
5a3dc6c1e1 feat(ui): add data tool display config in chat UI
Add chart icon, smart subtitle (shows action + ticker, e.g. "price_snapshot AAPL"),
and running state label for the data tool in the tool call UI component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:54:42 +08:00
Jiayuan Zhang
d80e97648c fix(core): read data tool API key from credentials.json5
Use credentialManager.getToolConfig("data") as the primary source for
the Financial Datasets API key, with FINANCIAL_DATASETS_API_KEY env var
as fallback. Also add data tool entry to the credentials template.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:45:46 +08:00
Jiayuan Zhang
d47594d7b2 feat(core): add unified data tool with finance domain support
Introduces a `data` tool that provides structured access to the Financial
Datasets API (financialdatasets.ai). Supports 18 finance actions covering
stock prices, financial statements, key metrics, SEC filings, analyst
estimates, insider trades, news, and crypto data.

Designed as a stable interface — the backend can be swapped from direct
API calls to a Multica Data Service without changing the tool schema.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:43:58 +08:00
Jiayuan Zhang
afe51d325d fix(core): rebuild system prompt after provider switch
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>
2026-02-11 00:12:08 +08:00
Jiayuan Zhang
c24ac8a098 fix(core): fix OpenRouter model resolution and add fallback for custom models
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>
2026-02-10 23:40:31 +08:00
Jiayuan Zhang
d7ccbf066e feat(core): add testProvider method to AsyncAgent
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>
2026-02-10 23:02:39 +08:00
Jiayuan Zhang
1b252c1cf9 chore(ui): switch primary color to shadcn default
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>
2026-02-10 22:16:57 +08:00
Bohan Jiang
98b8126c89
Merge pull request #120 from multica-ai/feat/subagent-concurrency-queue
feat(subagent): add concurrency queue and default timeout
2026-02-10 20:04:16 +08:00
Jiang Bohan
d78f8480bf chore(agent): remove debug invalid tool-call injection 2026-02-10 19:53:33 +08:00
Jiang Bohan
db25f8f44a chore(agent): add debug hook to inject invalid tool call id 2026-02-10 19:43:49 +08:00
Jiang Bohan
e2d4803f8b fix(agent): sanitize invalid tool call ids in context 2026-02-10 19:33:44 +08:00
Jiang Bohan
683dfa759b feat(subagent): integrate command queue and configurable timeout
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>
2026-02-10 19:27:26 +08:00
Jiang Bohan
240fdd1286 feat(subagent): add lane-based command queue with concurrency control
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>
2026-02-10 19:27:19 +08:00
Naiyuan Qing
24c3af1185 fix(ui): handle possibly undefined values in strict mode 2026-02-10 18:19:41 +08:00
Naiyuan Qing
6ef58a0cab refactor: restructure to monorepo architecture
- Move core agent engine to packages/core/
- Add packages/types/ for shared TypeScript types
- Add packages/utils/ for utility functions
- Add apps/cli/ for command-line interface
- Add apps/gateway/ for NestJS WebSocket gateway
- Add apps/server/ for REST API server
- Restructure desktop app (electron/ → src/main/, src/preload/)
- Update pnpm workspace configuration
- Remove legacy src/ directory

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-10 18:00:23 +08:00
yushen
d367e77c0a feat(device): add clientName to DeviceMeta for multi-client display
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>
2026-02-10 16:43:44 +08:00
Naiyuan Qing
c27f4e66b5 docs(channels): update README with route queue pattern and ack lifecycle
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>
2026-02-09 18:27:06 +08:00
Naiyuan Qing
ec67dd6706 fix(ui): strip channel metadata prefixes from user messages in display
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>
2026-02-09 18:27:06 +08:00
Naiyuan Qing
0895d42d3b Merge remote-tracking branch 'origin/main' into feat/telegram-channel
# Conflicts:
#	apps/desktop/src/hooks/use-local-chat.ts
#	packages/sdk/src/actions/stream.ts
#	packages/ui/src/components/chat-view.tsx
#	src/agent/async-agent.ts
#	src/agent/events.ts
2026-02-09 14:28:06 +08:00
Bohan Jiang
b7085b2bf5
Merge pull request #107 from multica-ai/fix/new-user-onboarding
fix(desktop): new user onboarding — show errors and configure dialog in Chat
2026-02-09 13:54:48 +08:00
Jiang Bohan
ed681a96bf feat(desktop): add Configure button in chat error banner
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>
2026-02-09 13:51:55 +08:00
Naiyuan Qing
23905daaa1 Merge remote-tracking branch 'origin/main' into feat/telegram-channel
# Conflicts:
#	apps/desktop/electron/electron-env.d.ts
#	apps/desktop/electron/ipc/index.ts
#	apps/desktop/electron/preload.ts
#	apps/desktop/src/App.tsx
#	apps/desktop/src/pages/layout.tsx
#	src/agent/async-agent.ts
#	src/agent/runner.ts
#	src/hub/hub.ts
2026-02-09 13:44:08 +08:00
Naiyuan Qing
6a02fd29be fix(ui): adjust chat input padding, icon, and button size
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 11:43:49 +08:00
Naiyuan Qing
54b3ebe9e9 feat(ui): render error messages with Markdown
Use MemoizedMarkdown for error messages so links are clickable
instead of plain text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 08:04:19 +08:00
Naiyuan Qing
1819f4196d fix(sdk): add AgentErrorEvent to StreamPayload type
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>
2026-02-09 08:04:13 +08:00
Naiyuan Qing
56ebe613db fix(hooks): handle agent_error events in useGatewayChat
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>
2026-02-09 07:30:35 +08:00
Jiang Bohan
0e383f51ab feat(desktop): display agent errors in Chat UI instead of hanging
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>
2026-02-06 22:23:53 +08:00
yushen
6ecdbc5783 fix(exec-approval): treat expiresAtMs=-1 as non-expiring 2026-02-06 18:09:01 +08:00
Naiyuan Qing
1e6da205df feat(ui): spin MulticaIcon on hover
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 20:22:29 +08:00
Naiyuan Qing
5e174b8fef fix(ui): responsive horizontal padding on message list
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 20:15:19 +08:00
Naiyuan Qing
21312abd01 feat: chat input padding 2026-02-05 19:02:38 +08:00
Naiyuan Qing
039a625dbd
Merge pull request #96 from multica-ai/feat/message-return
feat(chat): add message history pagination with scroll-up loading
2026-02-05 18:48:56 +08:00
Naiyuan Qing
02f3534f2e chore(chat): set DEFAULT_MESSAGES_LIMIT to 200 for production
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:45:15 +08:00
Naiyuan Qing
1fe27a59d0 chore(store): remove unused Zustand stores and slim down package
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>
2026-02-05 18:41:31 +08:00
Naiyuan Qing
65c2fea1b6 feat(chat): add message history pagination with scroll-up loading
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>
2026-02-05 18:40:15 +08:00