Commit graph

244 commits

Author SHA1 Message Date
Jiayuan Zhang
bd0b380e2e refactor(credentials): remove skills.env.json5 support
Remove centralized skills.env.json5 in favor of per-skill .env files.
Clean up CredentialManager by removing hasEnv/getEnv/getResolvedEnvSnapshot
methods and skills env loading. Update CLI credentials and skills commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 13:34:51 +08:00
Jiayuan Zhang
9f98ccca58 feat(skills): store API keys in per-skill .env files
Move skill environment variables from centralized skills.env.json5 to
per-skill .env files within each skill's directory. This makes credential
management more intuitive and self-contained.

- Fix parser to handle metadata.requires, always, os, skillKey, install
- Add minimal .env parser (dotenv.ts) and load .env at skill parse time
- Add env field to Skill type for per-skill environment variables
- Update eligibility checker to use skill.env instead of CredentialManager
- Preserve user .env files across bundled skill upgrades in loader

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 13:34:51 +08:00
Naiyuan Qing
430f2c177e refactor(ui): move LoadingIndicator into MessageList
- Move LoadingIndicator from ChatView into MessageList for consistent padding
- Add isLoading and hasPendingApprovals props to MessageList
- Adjust message spacing (my-1 → my-2) for better visual balance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-15 11:03:59 +08:00
Naiyuan Qing
deb747a859 refactor(ui): unify loading indicator component
- Create LoadingIndicator component with "generating" and "streaming" variants
- Remove inline loading indicator from StreamingMarkdown (empty content returns empty fragment)
- Use unified LoadingIndicator in ChatView with consistent positioning
- Eliminates layout shift between different loading states

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-15 10:52:46 +08:00
Naiyuan Qing
c6ca5f3270 refactor(ui): unify container layout and adjust spacing
- Use container utility class consistently across chat components
- Change container max-width from 5xl to 4xl for better readability
- Adjust message bubble padding (p-3 -> p-2)
- Fix logout dropdown alignment and add destructive variant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-15 10:47:59 +08:00
Naiyuan Qing
0c5de3c5f4 fix(ui): preserve newlines in chat input multiline text
- Use TipTap's getText({ blockSeparator: '\n' }) instead of doc.textContent
  to preserve newlines between paragraphs when submitting messages
- Add whitespace-pre-wrap CSS to user message bubbles to render newlines
- Add className prop support to StreamingMarkdown component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-15 10:32:58 +08:00
yushen
4dba1cfdf0 refactor: unify API URL env var to MULTICA_API_URL
Replace scattered API_URL, MAIN_VITE_API_URL, and RENDERER_VITE_API_URL
with a single MULTICA_API_URL across all apps and packages.

- Desktop: use envPrefix to expose MULTICA_* to main process, rename
  RENDERER_VITE_API_URL → RENDERER_VITE_MULTICA_API_URL, remove
  MAIN_VITE_API_URL (now read directly via MULTICA_API_URL)
- Web: add .env.development with MULTICA_API_URL, enforce required check
  in next.config.ts, update .gitignore to allow .env.development
- Core: make MULTICA_API_URL required in api-client (no silent fallback)
- Scripts: pass MULTICA_API_URL in dev-local.sh for web process
- Turbo: update globalEnv from API_URL to MULTICA_API_URL
- Docs: update references to the new env var name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 06:31:00 +08:00
Jiayuan Zhang
7a92f716d9
refactor(skills): remove unused SkillConfig.apiKey/env/primaryEnv (#192)
These fields were only checked during eligibility but never injected
at runtime via credentialManager.getEnv(). Remove the half-implemented
per-skill credential config to reduce confusion.

API key configuration remains supported via skills.env.json5 and
process.env.

Refs: MUL-246, MUL-255

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 03:22:02 +08:00
Jiayuan Zhang
058af56d47
fix(ui): show generating indicator while agent is processing (#191)
When the user sends a message and the agent hasn't started streaming
yet, the chat area showed no visual feedback. Now a "Generating..."
spinner appears between message send and the first streaming content,
matching the existing indicator style used in StreamingMarkdown.

Closes MUL-224

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 03:13:18 +08:00
Jiayuan Zhang
418282da15
Merge pull request #183 from multica-ai/forrestchang/optimize-office-skills
refactor(skills): deduplicate office module and merge PDF scripts
2026-02-15 02:05:04 +08:00
Jiayuan Zhang
6234ba6139 refactor(skills): deduplicate office/ module across docx, xlsx, pptx
Move the shared office/ directory (pack/unpack, validators, schemas,
soffice wrapper) to skills/_shared/office/ and replace the three
identical copies with symlinks. Update skill loader to dereference
symlinks during copy to managed directory, and skip _-prefixed
directories in the bundled skills scan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 01:31:53 +08:00
Jiayuan Zhang
40fb94e3c3 feat(utils): add SMC_DATA_DIR env var to override root data directory
Allows isolating dev and prod data by setting SMC_DATA_DIR (e.g.
~/.super-multica-dev). Also fixes cron/store.ts which bypassed
DATA_DIR with a hardcoded path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:39:13 +08:00
yushen
1a9e915d49 fix(hub): skip text processing after MessageAggregator reset
Add an `active` flag so that message_update/message_end events are
ignored after reset() until the next message_start, preventing stale
accumulated text from being re-emitted as a block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 07:49:20 +08:00
yushen
b8aa5ad7e6 refactor(core): convert MULTICA_API_URL constant to getApiBaseUrl() function
Lazy-read process.env at call time instead of module import time.
This ensures the env bridge in the Electron main process has time to
set process.env.MULTICA_API_URL before the first API request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 07:30:36 +08:00
Jiayuan Zhang
ef8b38899a fix(core): check for "toolCall" type in hasToolUse() to match pi-ai types
The hasToolUse() function was checking for "tool_use" (raw Anthropic format)
but pi-ai normalizes tool call blocks to type "toolCall". This made tool
narration non-functional in the ChannelManager (Desktop/embedded) path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 03:32:16 +08:00
Jiayuan Zhang
3c911807e1 chore(channels): remove unused Desktop Telegram channel plugin
The Telegram bot is handled entirely by the Gateway
(apps/gateway/telegram/). The Desktop channel plugin was never
registered (commented out in initChannels) and is dead code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 02:28:00 +08:00
Jiayuan Zhang
dde8cc542a feat(telegram): show editable status message during tool execution
Instead of silently discarding tool narration or spamming separate
messages, send a single editable Telegram message that updates in-place
as the agent works. First tool narration sends a reply; subsequent
narrations edit the same message. The final answer is sent as a new
message.

- Add replyTextEditable/editText to ChannelOutboundAdapter interface
- Implement editFormatted + editable methods in Telegram plugin
- Track statusMessageId in ChannelManager, clear on agent_end/error
- Add sendOrEditStatus to Gateway TelegramService with same behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 02:14:42 +08:00
Jiayuan Zhang
81998e6309 fix(telegram): skip tool narration messages, only send final answer
When the agent uses tools (web search, etc.), it generates intermediate
narration text like "Let me search..." before each tool call. These were
being sent as separate Telegram messages, causing message spam. Now we
detect tool_use blocks in the message content and skip sending those
intermediate messages — only the final answer reaches the user.

Applied to both Desktop channel plugin and Gateway Telegram service.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 02:04:17 +08:00
Jiayuan Zhang
8270762d66 feat(telegram): convert Markdown tables to vertical list format
Telegram doesn't support HTML tables, so pipe-delimited Markdown tables
were rendered as hard-to-read plain text on mobile. This converts tables
to a vertical "Header: Value" format with bold first column before
sending to Telegram.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 01:56:21 +08:00
Jiayuan Zhang
9189883710
Merge pull request #175 from multica-ai/forrestchang/telegram-arch
feat(gateway): Telegram QR-based connection flow with centralized bot
2026-02-14 01:45:46 +08:00
Jiayuan Zhang
c2a2c75b24
Merge pull request #174 from multica-ai/forrestchang/arch-analysis
test(core): migrate tests to strict mock policy with real implementations
2026-02-14 00:13:24 +08:00
Jiang Bohan
a86709a4cd chore: switch API host from api-dev.copilothub.ai to api.multica.ai
Update all backend API base URLs to use the production multica.ai domain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:25:00 +08:00
Jiayuan Zhang
4cb8b93f93 refactor(desktop): hide manual Telegram bot creation, use official Gateway bot only
Remove the user-facing ability to create custom Telegram bots via BotFather.
Non-technical users should only need to message @multica_bot on Telegram.
- Disable telegramChannel plugin registration in initChannels()
- Remove ConnectStep from onboarding flow (Privacy → Provider → Start)
- Replace TelegramCard with simple text pointing to @multica_bot

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:20:39 +08:00
Jiayuan Zhang
f6b6c2909b test(core): migrate 5 tests from internal mocks to real implementations
- store.test.ts: use baseDir option instead of mocking paths.js
- session-file-repair.test.ts: remove write-lock mock, assert behavior
- announce-findings.test.ts: use real storage with temp dirs
- sessions-list.test.ts: use real registry with seed helper
- compaction.test.ts: mock only third-party pi-coding-agent, use real
  context-window internals

All tests exercise real code paths, improving confidence in actual
behavior per the strict mock policy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:17:52 +08:00
Jiayuan Zhang
37e550200c refactor(core): add baseDir DI and test helpers for mock-free testing
Add AuthStoreOptions with baseDir to auth-profiles/store.ts functions,
add baseDir option to announce.ts readLatestAssistantReply, and add
seedSubagentRunForTests helper to registry.ts. These enable tests to
use real implementations with temp directories instead of mocking
internal modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:17:41 +08:00
Jiayuan Zhang
b6db23c4b3 feat(agent): add structured run logging for debugging
Introduce a RunLog system that records agent execution events as
structured JSONL to ~/.super-multica/sessions/{id}/run-log.jsonl.
Enable via MULTICA_RUN_LOG=1 env var or AgentOptions.enableRunLog.

Logs: run lifecycle, LLM calls, tool execution timing, context
overflow recovery, auth profile rotation, error classification,
and compaction events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:38:02 +08:00
Jiayuan Zhang
27c3ba5682 feat(web): add Accept: text/markdown header for Cloudflare Markdown for Agents
Prefer markdown responses from servers that support Cloudflare's Markdown
for Agents feature, reducing token usage by ~80% when available. Non-supporting
servers fall back to HTML as before.

- Update Accept header to prefer text/markdown in web_fetch requests
- Add text/markdown content-type handling to skip HTML parsing pipeline
- Capture x-markdown-tokens response header in WebFetchResult
- Add extractMarkdownTitle() helper for native markdown title extraction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 19:47:17 +08:00
LinYushen
882dc8592b
Merge pull request #157 from multica-ai/feat/improve-compaction
feat(compaction): improve summary mode with fallback, split-turn, and metadata
2026-02-13 19:15:38 +08:00
yushen
882e34c491 fix(core): preserve context-specific auth error messages
Add optional context parameter to getAuthHeaders() so callers can
provide feature-specific suffixes (e.g. "to use web search") in the
not-logged-in error message, restoring the original behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:58:27 +08:00
yushen
53cffe923b refactor(tools): use shared api-client for auth headers
Replace duplicated getLocalAuth() + manual header construction in
finance/api.ts and web-search.ts with the shared getAuthHeaders()
and API_BASE_URL from hub/api-client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:56:26 +08:00
yushen
f76312511d feat(core): add shared api-client module for auth headers
Extract API_BASE_URL and getAuthHeaders() into a reusable module
so that tools don't duplicate base URL and auth header construction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:56:20 +08:00
yushen
77953a611d feat(finance): replace API key auth with auth headers
Route all financial data requests through api-dev.copilothub.ai/api/v1/financial
proxy and authenticate via sid/device-id/os-type headers instead of X-API-KEY.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:52:46 +08:00
yushen
92a137414d feat(web-search): replace HMAC signing with auth headers
Migrate web_search tool from HMAC-SHA256 reqId signing to
sid/device-id/os-type auth headers, matching the desktop API client
pattern. Update endpoint to /api/v1/web-search.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:25:43 +08:00
yushen
ec413effb9 feat(core): add auth-store module to read local auth data
Reads sid and deviceId from ~/.super-multica/auth.json for use by
tools that need authenticated API access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:25:37 +08:00
Naiyuan Qing
9877a4f97e
Merge pull request #167 from multica-ai/feat/desktop-device-id
feat: unify encrypted Device ID across all platforms
2026-02-13 17:47:54 +08:00
Naiyuan Qing
242be23876 feat(utils): unify encrypted Device ID across all platforms
- Add common generateEncryptedId() utility in @multica/utils
- All Device IDs now use same encryption algorithm (40 hex chars)
- Web: store encrypted format directly in localStorage
- Desktop: use shared utility, accept encrypted ID from Web
- Hub: use shared utility for hub-id generation
- Telegram: use shared utility for device ID generation
- Gateway hook: use encrypted format for client connections

Algorithm: sha256(sha256(uuid).slice(0,32)).slice(0,8) + sha256(uuid).slice(0,32)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 17:34:23 +08:00
Jiayuan Zhang
fe6fdd4ddc
Merge pull request #165 from multica-ai/forrestchang/analyze-run-logs
feat(session): optimize image storage with externalization and resize
2026-02-13 17:13:40 +08:00
Jiayuan Zhang
7ac90a2ce7 feat(tools): add image resize wrapper for read tool
Wraps the read tool from pi-coding-agent to automatically downscale
oversized images (>1MB or >2000px) before they enter the session.
Uses macOS sips for resize with no extra dependencies, following the
same pattern as OpenClaw. Falls back gracefully on non-macOS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:12:37 +08:00
Jiayuan Zhang
278334474f feat(session): externalize base64 images from session JSONL
Session JSONL files were bloated because base64 image data was stored
inline (a real session had 6.7MB of images in a 9.8MB file). Images
are now extracted to per-session media/ directories as binary files,
with compact $ref references stored in the JSONL. Images are restored
transparently on read. Old sessions with inline base64 remain
backward compatible and auto-migrate on next compaction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:12:30 +08:00
Jiang Bohan
13fbeaf4d2 fix(providers): use dedicated openai-codex provider instead of aliasing to openai
Codex OAuth tokens use the ChatGPT backend API (chatgpt.com), not the
standard OpenAI API (api.openai.com). pi-ai already has a dedicated
openai-codex provider with the correct API format (openai-codex-responses)
and base URL. Remove the alias that was incorrectly mapping openai-codex
to openai, which caused 401 errors due to missing scopes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:01:57 +08:00
Naiyuan Qing
e0f80b0054 merge: resolve conflict with main (keep both auth and subagents handlers) 2026-02-13 14:41:15 +08:00
Naiyuan Qing
7faf421996 merge: resolve conflict with main branch (build scripts) 2026-02-13 14:36:42 +08:00
Jiang Bohan
499c573544 feat(desktop): add subagent dashboard with real-time status tracking
Adds a status bar and slide-out dashboard panel to the chat view that
shows subagent progress in real-time. Polls at 2s when active, 10s when
idle. Completed runs auto-hide after 5 minutes. Includes dismiss button
and 30s auto-dismiss for the status bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 13:46:58 +08:00
yushen
d7e85d0c25 fix(compaction): prevent double-counting in split-turn detection
- detectSplitTurn now returns adjustedToSummarize (truncated at
  assistantIndex) so split prefix messages are not summarized twice
  and removedCount is not inflated
- Remove unused contextWindow parameter from computeAdaptiveChunkRatio
- Remove duplicated single-chunk fast path in compactMessagesWithChunkedSummary
  (the multi-chunk loop handles 1 chunk naturally)
- Fix triple-newline formatting in buildPlainTextFallback by concatenating
  metadata sections directly instead of joining through parts array

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 13:33:31 +08:00
yushen
9e7907a814 feat(compaction): switch default to summary mode with split-turn and adaptive chunking
- Default compaction mode changed from "tokens" to "summary" (LLM-based)
- Split turn detection: handles orphaned tool_result messages at compaction
  boundary by summarizing the split turn prefix separately
- Adaptive chunk sizing: dynamically adjusts chunk ratio (0.15-0.4) based on
  average message token count instead of fixed 50k tokens
- Fallback chain: summary compaction wraps in try/catch, falling back to
  tokens mode on complete failure; summarizeWithFallback provides 3-level
  degradation within summary mode itself
- Metadata appended to summaries: file operations + tool failures sections
- CompactionResult extended with fileOperations and toolFailures fields
- CompactionEndEvent gains optional summary field
- API key resolution improved: reuses agent's own key for summary generation

Backward compatible: explicit "count" or "tokens" mode behaves identically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 13:26:34 +08:00
yushen
ba44de89b7 feat(compaction): add metadata extraction and multi-level summary fallback
New modules for improved compaction:
- compaction-metadata.ts: extracts file operations (read/modified) and tool
  failures from compacted messages, appended to summaries for context retention
- summary-fallback.ts: 3-level degradation chain (full LLM summary → filtered
  summary excluding oversized messages → plain-text fallback with metadata)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 13:26:22 +08:00
Naiyuan Qing
ca1c260735 feat(ui): add size prop to MulticaIcon bordered variant
Supports sm, md, lg sizes for flexible usage in different contexts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 12:24:48 +08:00
Naiyuan Qing
35ab158775 feat(types): add AuthUser type for auth integration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 12:24:43 +08:00
Jiang Bohan
a490c737cc feat(providers): add gpt-5.3-codex and update OpenAI model lists
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:55:16 +08:00
Jiang Bohan
16c325c808 fix(oauth): don't expire OAuth credentials that have refresh_token
Codex and Claude Code OAuth tokens with refresh_token can auto-renew,
so they should not be considered expired based on last_refresh time or
file mtime. Previously, credentials were hardcoded to expire after 1
hour, causing valid Codex sessions to show as unconfigured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:42:24 +08:00