Commit graph

91 commits

Author SHA1 Message Date
Naiyuan Qing
9bcb0993b1 Merge remote-tracking branch 'origin/main' into exec-approvals
# Conflicts:
#	apps/desktop/src/hooks/use-local-chat.ts
2026-02-05 17:54:19 +08:00
Naiyuan Qing
bece8f0545 refactor(web): adapt chat page for extracted shared hooks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 17:51:12 +08:00
Naiyuan Qing
7a21686505 refactor(hooks,ui): extract useGatewayChat hook and update shared components
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>
2026-02-05 17:50:47 +08:00
Naiyuan Qing
056b9abff6 refactor(ui): deduplicate device-pairing with StatusWrapper and PairingHeader
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>
2026-02-05 17:07:45 +08:00
Naiyuan Qing
b896ac402a refactor(ui): self-contain Loading/Spinner styles and clarify usage semantics
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>
2026-02-05 15:38:39 +08:00
Naiyuan Qing
53c350ea33 refactor(ui,hooks): extract shared ChatView and DevicePairing to packages
- 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>
2026-02-05 15:28:44 +08:00
yushen
8f9dcbf7e5 fix(agent): address compaction feedback review findings
- 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>
2026-02-05 15:27:08 +08:00
yushen
1316d329ee feat(hub): propagate compaction events to all frontend transports
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>
2026-02-05 14:55:00 +08:00
Naiyuan Qing
7fdbf24c4e feat(ui): add exec approval card and refine tool/thinking expand style
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>
2026-02-05 14:44:16 +08:00
Naiyuan Qing
0a6e930c2b feat(ui): render thinking content blocks in message list
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>
2026-02-05 14:44:09 +08:00
Naiyuan Qing
348b12b532 fix(ui): prevent long unbreakable strings from overflowing markdown
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>
2026-02-05 14:44:04 +08:00
Naiyuan Qing
44fef52071 feat(ui): add container utility and loading spinner component
Add @utility container (w-full max-w-4xl mx-auto) to globals.css and
create a reusable Loading spinner component for consistent loading states.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 14:43:48 +08:00
Naiyuan Qing
3c303df8f1 Merge branch 'main' into exec-approvals 2026-02-05 11:06:52 +08:00
Naiyuan Qing
e8012a5580 feat(ui): use default font size for messages and larger send button
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 11:04:21 +08:00
Naiyuan Qing
22c957ee76 chore(web): enable experimental HTTPS and fix scanner active states
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:44:29 +08:00
Naiyuan Qing
037908cf8d feat(connection): add device verification status feedback for collaborators
Add a "verifying" connection state between "connected" and "registered"
so collaborators see clear feedback while waiting for the device owner
to approve their connection on Desktop.

Changes across the stack:
- Hub: verify RPC returns isNewDevice flag to distinguish new vs whitelisted
- SDK: emit "verifying" state before verify RPC, pass isNewDevice through
- Store: capture isNewDevice via onVerified, capture rejection via onError
- UI: ConnectionStatus (waiting), RejectedStatus (declined), and
  verify success overlay (approved) replace the stuck scanner screen

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:44:15 +08:00
Naiyuan Qing
cff9c004db feat(ui): add paste mode success/error feedback with state-based rendering
Replace overlay approach with conditional rendering for paste validation
states. Success shows checkmark icon with 600ms delay before connecting,
error shows alert with message and auto-resets after 2s. Improves
Connecting text visibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 09:53:09 +08:00
Naiyuan Qing
544425ae40 feat(ui): improve mobile onboarding copy and chat input sizing
Rewrite user-facing text to be source-agnostic and result-focused:
- Connect prompt: "Scan to start" / "Scan a QR code to use an Agent"
- Empty chat: "Your Agent is ready"
- Input placeholder: "Ask your Agent..." / "Scan QR code to get started"
- Bump chat input font to text-base for mobile readability

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 09:23:53 +08:00
Naiyuan Qing
8e8a0d58d1 feat(ui): redesign connect prompt with scan/paste tabs and fullscreen QR
Replace sheet-based scanner with inline QrScannerView, add scan/paste
mode toggle for desktop, and fullscreen camera overlay for mobile.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 09:23:46 +08:00
Naiyuan Qing
845e503899 feat(ui): redesign QR scanner with state machine and mobile sheet
Rewrite QrScannerView with full state machine (idle/requesting/scanning/
detected/success/error), corner bracket animations snapping to QR
cornerPoints, flash toggle, and haptic feedback.

Add QrScannerSheet as bottom sheet wrapper for mobile. Simplify
ConnectPrompt to use useIsMobile() — mobile shows scan button + sheet,
desktop shows paste-only UI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 08:22:49 +08:00
Naiyuan Qing
d0681ef46d feat(ui): extend QR scanner hook with lifecycle control and flash API
Add cornerPoints, hasFlash/toggleFlash, and manual start/stop/pause
to useQrScanner. Change enabled default to false for click-to-start.
Add scoped CSS animations for scanner bracket breathing and error shake.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 08:22:42 +08:00
Naiyuan Qing
b74a5ea1a7 feat(ui): replace ChatInput textarea with Tiptap editor
Swap the plain <textarea> for a minimal Tiptap (ProseMirror) editor
with IME-safe Enter-to-submit, Shift+Enter newline, placeholder
support, and an optional imperative ref (getText/setText/focus/clear).
All rich-text extensions are disabled — only plain text is allowed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 07:56:11 +08:00
Naiyuan Qing
83e1210ec0 merge: resolve conflicts with main, keep pi-ai types + add extractThinkingFromEvent
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 18:44:23 +08:00
Naiyuan Qing
e4f1d51453 feat(store,ui): handle Hub error messages and display error banner
- Handle `action: "error"` messages in connection-store (e.g. UNAUTHORIZED)
- Widen lastError type to `{code, message}` to support all error codes
- Display dismissible error banner in Chat with role="alert" and aria-live
- Add accessible close button with focus-visible ring

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 18:39:32 +08:00
Naiyuan Qing
1f7951df1b fix(ui): render tool calls immediately from assistant toolCall blocks
Tools were only visible after tool_execution_end because the UI relied
solely on toolResult messages (created at tool_execution_start, ~8ms
before end). Now MessageList detects toolCall blocks in the streaming
assistant message and renders them as "running" ToolCallItems immediately.
Once a real toolResult message arrives, the synthetic one is replaced.

- Add resolvedToolCallIds set to deduplicate assistant vs toolResult renders
- Extract getTextContent to shared utils to avoid duplication
- Wrap MessageList and ToolCallItem in memo for performance
- Add accessible region/tabIndex to expanded result panel

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 18:12:34 +08:00
Naiyuan Qing
cfd46ee602 refactor(sdk,store): clean up SDK exports and scope tool interruption
- Remove backward-compatible aliases (TextBlock, ThinkingBlock, ToolCallBlock)
  and extractTextFromEvent from SDK — unused after prior refactors
- Add explicit ContentBlock doc comment explaining the wider union tradeoff
- Scope endStream tool interruption to the same agentId (prevents
  cross-agent interference in multi-agent scenarios)
- Handle tool_execution_update event (no-op for now, avoids unhandled case)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 18:12:24 +08:00
Naiyuan Qing
703c4686d9 feat(ui): improve tool-call-item with status dots, smart subtitles, and accessibility
- Replace hugeicons status icons with CSS colored dots + glow-pulse
  animation for running state (honors prefers-reduced-motion)
- Add smart subtitles from toolArgs: file basename, command preview,
  search pattern, URL hostname
- Add right-aligned stats: line count, match count, file count
- Add hover chevron for expand/collapse affordance
- Fix accessibility: aria-label, aria-expanded, focus-visible ring
- Store toolArgs in Message (was received but discarded)
- Extract toolArgs from assistant ToolCall blocks during fetchHistory
- Add --tool-running/success/error CSS variables with dark mode support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:30:46 +08:00
Naiyuan Qing
583242baba feat(store,ui): add tool execution lifecycle and ContentBlock rendering
- Message.content is now ContentBlock[] (was string), supporting
  text, thinking, toolCall, and image blocks
- Add toolResult role with toolCallId, toolName, toolStatus, isError
- Add startToolExecution/endToolExecution to MessagesStore
- MessageList renders toolResult messages via ToolCallItem
- Extract text from ContentBlock[] for markdown rendering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:30:37 +08:00
Naiyuan Qing
ef4e57ffdd refactor(sdk): unify message types with pi-ai source of truth
Replace hand-written message/content types in @multica/sdk with
`import type` from @mariozechner/pi-ai and @mariozechner/pi-agent-core.
This ensures compile-time correctness and eliminates type drift between
backend and frontend (e.g. "tool_use" vs "toolCall", "tool_result" vs
"toolResult").

- stream.ts: re-export TextContent, ThinkingContent, ToolCall,
  ImageContent from pi-ai; use AgentEvent from pi-agent-core
- rpc.ts: AgentMessageItem = pi-ai Message (no more manual mirroring)
- connection-store.ts: use SDK types instead of inline hand-written ones
- ContentBlock now includes ImageContent to match backend reality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:13:01 +08:00
yushen
d930040258 feat(sdk): add exec approval action types for client integration
- ExecApprovalRequestAction: Hub-to-Client approval request payload
- ResolveExecApprovalParams/Result: Client-to-Hub RPC types
- ApprovalDecision type: allow-once, allow-always, deny
- Export from SDK actions index for frontend consumption

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:07:22 +08:00
LinYushen
c70dd338c2
Merge pull request #84 from multica-ai/reasoning-mode-support
feat(agent): add reasoning mode for thinking content display
2026-02-04 16:15:20 +08:00
yushen
0340358a9b feat(sdk): add thinking content extraction for stream events
Update StreamMessageEvent content type to include thinking blocks.
Add extractThinkingFromEvent() helper and export it, enabling clients
to access reasoning content from streamed agent responses.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:50:41 +08:00
Naiyuan Qing
096b284cef
Merge pull request #80 from multica-ai/feat/web-qr-scanner
feat(ui): add QR code scanner to ConnectPrompt
2026-02-04 15:44:02 +08:00
Naiyuan Qing
2f5dbcdde9 feat(ui): add QR code scanner to ConnectPrompt
Add browser-based QR scanning as an alternative to paste mode in the
ConnectPrompt component. Mobile users can scan the Desktop QR code
directly instead of manually copying connection codes.

- Add qr-scanner dependency (WebWorker-based decoding, BarcodeDetector support)
- Create use-qr-scanner hook wrapping camera lifecycle and cleanup
- Create QrScannerView component with viewfinder overlay
- ConnectPrompt auto-detects mobile (touch + narrow viewport) and defaults to scan mode
- Lazy-load scanner component for zero initial bundle impact
- Graceful fallback to paste mode on permission denial or no camera

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:41:12 +08:00
yushen
dd701a2472 feat(sdk): collect device metadata during verify handshake
Auto-collect navigator.userAgent, platform, and language in the SDK
verify RPC payload. Add DeviceMeta type. Hub receives this metadata
for display in the Desktop UI device list and confirmation dialog.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:43:41 +08:00
yushen
3d13c28cfc feat(store): pass hubId and token to GatewayClient for verification
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:27:33 +08:00
yushen
0eac2b2a23 feat(sdk): add auto-verify handshake after gateway registration
Embed transparent verification logic in GatewayClient that automatically
sends an RPC "verify" request to the Hub after REGISTERED event. Adds
hubId, token, and verifyTimeout options. Upper-layer callers see no change
— "registered" state means both gateway registration and Hub verification
are complete. Failures surface via onError callback.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 13:27:23 +08:00
Naiyuan Qing
62267aaf19 refactor(ui): split Chat into focused components with skeleton loading
- Extract ConnectPrompt: self-contained connection code input
- Extract MessageList: pure display of messages with streaming support
- Add ChatSkeleton: skeleton placeholder shown during reconnection
- Chat component: three-state rendering (skeleton → connect → messages),
  keeps <main ref> always mounted so useScrollFade works correctly
- Remove hub-sidebar.tsx (dead code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 10:12:51 +08:00
Naiyuan Qing
162a86dff4 refactor(store): consolidate 3 stores into ConnectionStore + MessagesStore
Replace useGatewayStore, useHubStore, useDeviceId, and useHubInit with:
- ConnectionStore: WebSocket lifecycle, deviceId persistence via Zustand persist,
  fetch message history on registration via getAgentMessages RPC
- MessagesStore: simplified to current-agent-only, sendMessage accepts SendContext
  to break circular import with ConnectionStore
- useAutoConnect: returns { loading } for skeleton UI, skips connect if already
  connected (fixes Electron tab-switch reconnect), no cleanup disconnect

Removes: gateway.ts, hub.ts, hub-init.ts, device-id.ts, sonner dep from store

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 10:12:45 +08:00
Naiyuan Qing
dacbfa9e3d refactor(ui): purify Chat component and move header to app layouts
- Remove all props from Chat (showHeader, headerActions) making it a
  zero-config pure chat component with only connection input, messages,
  and send functionality
- Create AppHeader client component for web app with brand, theme
  toggle, disconnect button, and useHubInit
- Add disconnect button to desktop layout header
- Add reset() action to hub store to eliminate duplicated state reset
- Remove unused token field from gateway store
- Remove dead code: connection-bar.tsx
- Guard handleConnect against empty deviceId race condition

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:31:24 +08:00
Naiyuan Qing
b6d7626239 fix(store): use string prefix + URLSearchParams for connection URL parsing
The URL constructor parses non-standard protocols (multica://)
differently across engines — Chromium may return empty hostname
while Node.js returns "connect". Replace new URL() with simple
startsWith check and URLSearchParams to ensure consistent behavior
in both web and Electron environments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:13:51 +08:00
Naiyuan Qing
ff80cf0732 feat(desktop): integrate Chat component into desktop app
Add @multica/store and zustand to desktop dependencies. Replace
placeholder chat page with the shared Chat component. Add Toaster
for toast notifications and remove padding on the chat route.
Change Chat root from h-dvh to h-full for container adaptability.
Add showHeader prop to Chat; desktop passes showHeader={false}
since it has its own layout header.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:08:30 +08:00
Naiyuan Qing
a088875118 refactor(ui): move theme toggle from Chat to web app layout
Chat component no longer depends on next-themes, making it safe to use
in the desktop app. Theme toggle is now a fixed button in the web layout.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:02:48 +08:00
Naiyuan Qing
bb223d8a8c Merge branch 'main' into feat/connection-code 2026-02-03 19:42:41 +08:00
Naiyuan Qing
2bd87ccf5c theme buttom 2026-02-03 19:42:14 +08:00
Naiyuan Qing
6add5fc9c3 refactor(ui): remove sidebar, integrate connection flow into Chat
- Remove ConnectionBar sidebar from layout
- Chat component now handles connection code input inline (centered)
- Header: Multica branding left, Disconnect button right (when connected)
- Chat input disabled until connected

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 19:01:48 +08:00
Jiang Bohan
dafbf856ac merge: resolve conflicts with main branch
- Merge auth-profiles feature from main into runner.ts
- Merge closeCallbacks feature from main into async-agent.ts
- Regenerate pnpm-lock.yaml with new dependencies

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 18:57:05 +08:00
Naiyuan Qing
e7521b4cb0 feat(web): replace sidebar with connection code flow
- Add connection code parsing (JSON + base64) with localStorage persistence
- Add connectWithCode() to gateway store for code-driven connections
- Replace Sidebar with simple ConnectionBar (paste code / status / disconnect)
- Simplify Chat component by removing header clutter
- Auto-restore connection on page refresh from saved code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 18:53:49 +08:00
Jiang Bohan
03bcd853d3 feat(desktop): add UI components for Tools and Skills management
- Create tool-list.tsx with collapsible groups and toggle switches
- Create skill-list.tsx with status badges and action dialogs
- Create qr-code.tsx for connection QR code display
- Add dialog.tsx component to @multica/ui

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 18:25:30 +08:00
Naiyuan Qing
54ee2f3b00 feat(ui): add streaming indicator to StreamingMarkdown
Show a spinner with "Generating..." label while assistant messages are
being streamed. The indicator appears immediately when content is empty
and persists below the content until streaming completes.

Also fix duplicate React key warning by including block index in the key
to handle blocks with identical content.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:56:22 +08:00