res.statusText is always empty under HTTP/2, causing "Gateway error: "
with no detail. Now reads the JSON response body to surface the
server's actual error message and always includes the status code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- Track .env.development and .env.production for desktop app
- Add MAIN_VITE_API_URL to all env files
- Remove hardcoded GATEWAY_URL from dev scripts (now in .env.development)
- Update .env.example with full documentation of all env vars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Read API host from import.meta.env.RENDERER_VITE_API_URL instead of
hardcoding it, so staging/dev builds point to the correct backend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In packaged Electron builds, @multica/core is externalized and .env
files are excluded. Bridge import.meta.env.MAIN_VITE_API_URL (injected
at build time by electron-vite) to process.env.MULTICA_API_URL so that
getApiBaseUrl() reads the correct value in staging/test environments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
- Detect both "tool_use" (Anthropic) and "toolCall" (Hub) content types
- Re-send typing indicator after sending/editing status message since
Telegram clears typing state on any message send/edit
- Remove debug file logging (appendFileSync to /tmp)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Table cells already containing **bold** markers got double-wrapped,
creating ****text**** which produced overlapping <b><i> tags rejected
by Telegram's HTML parser.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
- Extract useQRToken and useCountdown hooks to qr-hooks.ts so qr-code.tsx
only exports components (fixes react-refresh/only-export-components)
- Remove unused useRef import from connect-step.tsx
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cleanupPendingRequests() iterated the service-wide pendingRequests Map
and rejected every entry. Since it was called from connectUser's catch
block (a per-user error path), one user's connection failure would
spuriously reject other users' in-flight verify RPCs.
The call was also redundant: sendVerifyRpc already cleans up its own
entry in all failure paths (timeout, RPC error, route failure).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DeviceConfirmDialog only exists in the main Layout, so verify
requests were timing out during onboarding. Since the user is
actively generating the QR code, auto-approve and show a success
state instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces in-memory Map with file-backed store at
~/.super-multica/gateway/telegram-users.json so user bindings
survive gateway restarts during local development.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add short code store, bot commands (/start, /status, /help), and
POST /telegram/connect-code endpoint for Desktop to create QR codes.
Users scan a QR → Telegram opens → /start {code} → auto-connects.
- ShortCodeStore: in-memory Map with TTL for connection info
- Bot commands registered via setMyCommands
- Refactor handleConnectionLink into shared connectUser method
- Fetch bot username via getMe() for deep link URL
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When TELEGRAM_WEBHOOK_URL is not set, the bot automatically starts in
long-polling mode (bot.start()) instead of waiting for webhook calls.
This allows local development without ngrok or a public URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
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>
Group chats have unresolved issues in the Gateway context:
- Per-user deviceId causes context overwrites across groups
- Reply chains across users route to wrong Hubs
- Welcome messages are noisy in groups
Restrict to private chats only until a proper group model is designed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Port all 7 features from the Desktop long-polling Telegram channel plugin
to the Gateway webhook-based Telegram bot:
- Markdown → Telegram HTML formatting with parse-error fallback
- Text chunking for messages >4096 chars (paragraph-boundary split)
- Reply-to original message + 👀 ack reaction lifecycle
- Group chat support (mention/reply filtering, @mention stripping)
- Per-chat message serialization (prevents race conditions)
- Inbound media handling (voice/audio/photo/video/document)
with transcription (Whisper) and description (Vision API)
- Outbound file captions with HTML formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scans test files at startup and warns when vi.mock() targets internal
modules (relative paths or @multica/* packages). Reports file paths
and line numbers for all violations without interfering with test
execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Establish "external-only mock" rule: only third-party dependencies
may be mocked in tests. Internal modules must use real implementations
with temp directories, reset functions, or dependency injection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
- Fix publish repo from "multica" to "super-multica"
- Add zip to mac targets (required for electron-updater auto-updates)
- Enable hardenedRuntime (required for notarization)
- Add notarize config with teamId placeholder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>