Pulls 21 upstream commits (147 files, +17234 / -317) since merge-base
4bea9ee6. Major upstream additions:
- feat(trace): session trace monitoring + LangSmith-style two-pane UI
(large new subtree under src/server/services/traceCaptureService.ts,
src/server/api/traces.ts, desktop/src/components/trace/, lib/trace/)
- feat(adapters): WhatsApp linked-device adapter (new sub-tree)
- feat(adapter): Telegram command menu
- feat(desktop): plugin list bulk toggles
- feat(desktop): output style desktop config
- fix(agent): defer permission/runtime restarts during active turns
- fix(agent): avoid concurrent worktree config writes
- fix(desktop): native preview screenshots, memory file preview-first
Conflict resolution (8 files, all handled by hand):
- desktop/package.json: kept fork version 0.5.8 (vs upstream 0.4.1)
and the fork's homepage URL.
- src/server/router.ts: kept BOTH /api/projects (fork) and
/api/traces (upstream) handlers + imports.
- desktop/src/components/chat/chatBlocks.test.tsx: kept fork's
"expanded by default" thinking-block tests; upstream had the old
"collapsed by default" assertions which contradict the live
ThinkingBlock.tsx default (`useState(true)`).
- desktop/src/pages/Settings.tsx: merged imports — added
`type TranslationKey` (upstream, used by trace tab) plus kept
fork's providerCompat store imports.
- desktop/src/components/plugins/PluginList.tsx: integrated
upstream's bulk-toggle (selectedPluginIds, ConfirmDialog,
bulkEnable/Disable) on top of fork's catalog/marketplace/inline
actions. Row layout now has checkbox + details button + inline
toggle/uninstall + chevron all coexisting. Both ConfirmDialog
instances (uninstall + bulk-batch) kept.
- src/server/api/sessions.ts: kept BOTH session-summary (handoff,
fork) and trace-call (upstream) handler functions and imports.
Routing dispatch was auto-merged correctly.
- src/server/services/conversationService.ts: unified
SessionStartOptions to carry fork's coordinatorMode +
handoffSystemPrompt AND upstream's resumeInterruptedTurn.
- src/server/ws/handler.ts: unified RuntimeOverride type to keep
fork's `thinkingEnabled` field (used 30+ times elsewhere) AND
upstream's new ActiveUserTurnState plus the deferred-restart Maps
(activeUserTurns, deferredRuntimeRestarts, deferredPermissionModes).
cleanupSessionRuntimeState now drains both fork's session sets
(coordinatorModeSessions, handoffSummarySessions) and upstream's
new Maps.
Verification:
- bun run lint (desktop): clean.
- bun test src/server/__tests__/mcp.test.ts -t marketplace: 2/2 pass.
- bun test src/server/__tests__/sessions.test.ts: 76/77 pass. The
single failure (slash-commands legacy custom commands) reproduces
on un-merged origin/main and is pre-existing — not introduced by
this merge.
Not-tested:
- Full bun run verify gate. Recommended before push/PR.
- Live trace UI smoke (no provider creds in this environment).
- Plugin list bulk-toggle smoke (no headed Electron available).
- WhatsApp adapter (separate sub-tree, not exercised by typecheck).
Confidence: medium-high (typecheck clean, no markers, conflicts all
semantically reasonable). Recommend a maintainer eye on
ws/handler.ts and conversationService.ts before merging upward,
since the deferred-restart vs handoff-injection paths touch the
same Map-of-pending-state surface.
Scope-risk: broad (147 files brought in via merge). Risk surface is
contained because trace + WhatsApp are independent subtrees and the
hand-resolved conflicts kept both sides additive rather than
rewriting either's logic.
* feat(agent-tool): rewrite contradictory review verdicts at the harness layer
The code-reviewer and security-reviewer subagents end with a parseable
verdict (`REVIEW: APPROVE` / `SECURITY: PASS`) and their prompts forbid
emitting the positive form alongside `[CRITICAL]` or `[HIGH]` findings.
That rule is enforced only in prose, so when the model slips, the
parent agent reads "APPROVE" and ships.
Add a pure post-processing pass over the subagent's final text in
`finalizeAgentTool`: detect the verdict line, look for severity tags
above it, rewrite to `CHANGES_NEEDED` when they conflict, and prepend
a one-line audit notice so the parent (and a human reading the
transcript) can see the harness intervened. Logs a
`tengu_agent_sentinel_mismatch` analytics event for fleet visibility.
Debugger's `ROOT CAUSE: FOUND` is intentionally out of scope — its
"evidence from a real command" rule isn't mechanically verifiable.
Tested: bun test src/tools/AgentTool/sentinelCheck.test.ts (11 cases)
Confidence: high
Scope-risk: narrow
* feat(agent-tool): show subagent_type in tool-use transcript line
Each `Agent` tool call previously rendered as just its description in
the transcript, which made it invisible whether the main agent picked
a specialist or fell back to the default. Diagnosing "why didn't it
use code-reviewer?" required reading analytics or guessing.
Append `→ <subagent_type>` to the rendered description when the type
is explicit. Omitted (general-purpose default or fork) keeps the old
format — absence of the marker is itself the signal.
Tested: visual smoke only — pure UI render change with no behaviour
Confidence: high
Scope-risk: narrow
* feat(agent-tool): nudge verification subagent after edits accumulate
Verification is the only built-in agent that's *required* by the
session-specific guidance for non-trivial implementations, but the
trigger condition ("3+ file edits / backend / infra change") is
evaluated by the main agent from its own work — a classic LLM
self-audit failure mode. The model can convince itself that 4 edits
"are mostly one core change with small adjustments" and skip
verification entirely.
Add a `verification_gate_reminder` attachment that fires once when
the main thread accumulates `editCount >= threshold` (default 3)
file-mutating tool uses (Edit / Write / NotebookEdit) without
invoking the verification subagent. The reminder renders as a
`<system-reminder>` block on the next turn telling the model to
spawn `subagent_type="verification"` before reporting completion,
along with a permission to skip if the change is genuinely trivial.
A verification subagent invocation resets the counter; a previously
fired reminder suppresses re-firing until the next reset, so the
nudge isn't spammed every turn.
Subagents don't see the reminder (it's main-thread only). The gate
also no-ops when the verification agent isn't loaded in the current
session (e.g. SDK build with built-ins disabled). Disabled by
`CLAUDE_CODE_VERIFICATION_GATE_OFF=1`; threshold tuned via
`CLAUDE_CODE_VERIFICATION_GATE_THRESHOLD=N`.
Tested: bun test src/utils/verificationGate.test.ts (10 cases)
Confidence: high
Scope-risk: moderate
* feat(agent-tool): tighten subagent routing and ship real coordinator mode
The flat 15-agent routing has three known weak spots that this commit
addresses together because they share AgentTool.tsx call-path edits:
1. **General-purpose default is a vacuum cleaner.** Omitting
`subagent_type` falls back to general-purpose, which has full tool
access and cheerfully takes on review/security/perf/migration work
that a specialist would have done better and cheaper. Add a regex
heuristic over the prompt: when it strongly signals a specialist
(e.g. "code review", "security audit", "root cause", "migrate from
X to Y") and that specialist is available, refuse the default and
throw an error pointing the model at the right type. Disabled with
`CLAUDE_CODE_GP_DEFAULT_STRICT=0`. Skipped in fork and coordinator
modes (they have their own routing).
2. **No bound on verifier-style loops.** The verification prompt says
"On FAIL: fix, resume, repeat until PASS" — if the verifier
incorrectly keeps flagging the same change, the loop only stops
when the token budget runs out. Add a per-session counter for each
built-in agent type. Cap defaults are 5 for verification (loop
target) and 8 for everything else. When exceeded, throw with a
message asking the model to consult the user. Caps tunable per
type via `CLAUDE_CODE_AGENT_LIMIT_<TYPE>=N`; off via
`CLAUDE_CODE_AGENT_LIMITER_OFF=1`. Counts main-thread invocations
only.
3. **Coordinator mode was a stub.** The `coordinator/workerAgent.ts`
that `getBuiltInAgents()` requires when `COORDINATOR_MODE` is on
was an auto-generated Proxy returning nothing useful. Replace with
a real registry: a new `WORKER_AGENT` (full-tool-access generic
delegate matching the existing coordinator prompt's
`subagent_type: "worker"` references) plus the 11 specialists
minus general-purpose, claude-code-guide, statusline-setup. Also
harden the coordinator's own prompt with a "you orchestrate, you
do not implement" rule (no direct Edit/Write/NotebookEdit) and a
specialist roster table so the coordinator picks the right
delegate. Improve the AgentTool error message when coordinator
mode receives a request without `subagent_type`.
Constraint: cannot restrict the coordinator's own tool pool at the
tool layer without invasive changes to main-thread tool assembly —
enforcement is prompt-level, consistent with the rest of the
architecture.
Rejected: a tighter heuristic that requires a domain-specific phrase
(e.g. "review THIS PR") was considered for #1 but rejected — too
narrow to catch the routing problems we actually see in fleet logs.
Tested: bun test src/tools/AgentTool/specialistRouter.test.ts
bun test src/tools/AgentTool/invocationLimiter.test.ts
bun test src/coordinator/workerAgent.test.ts
(29 cases total, all pass)
Confidence: medium
Scope-risk: moderate
Directive: if specialistRouter false-positives in production, prefer
tightening the regexes over removing the gate — the failure mode
it catches (general-purpose taking specialist work) is worse than
the failure mode of an extra retry.
* docs(desktop): add local Chrome DevTools MCP testing guide
Document the workflow for driving the desktop UI end-to-end via
Chrome DevTools MCP without going through Electron. The pure-browser
dev path normally fails — CSP blocks non-loopback origins, the
H5-disabled CORS gate blocks cross-origin browsers, and the desktop's
loopback exemption clears the H5 token so even the loopback path
401s. The stable bypass is a Vite proxy that same-origins API
traffic to the renderer.
This commit covers the docs side only:
- `docs/desktop/10-local-mcp-testing.md`: full how-to with Quick
Start, MCP control snippets, can/can't test matrix, the exact
reproduction recipes for each `feat/agent-routing-hardening`
change (sentinel, gp-strict, limiter, verification gate, routing
observability), a Windows-specific `electron:dev` workaround, and
a fault dictionary mapping the symptoms each blocker produces back
to its cause.
- `docs/desktop/index.md`: link the new section in the docs index.
- `.kiro/steering/desktop-mcp-testing.md`: concise rule file with
`inclusion: manual` so it loads via #-mention only when relevant,
not as default-included context.
The Vite proxy itself is intentionally NOT committed here. The docs
explain it as a dev-only change the developer adds locally and
either reverts or commits to a separate `feat/desktop-browser-dev-proxy`
branch — the production renderer relies on Electron IPC for the
server URL and must not see the dev proxy in main.
Tested: docs render via VitePress local preview; steering file
loads via Kiro `#desktop-mcp-testing` mention; instructions
verified against the live setup that produced
artifacts/desktop-bootstrapped.png and artifacts/desktop-agents-page.png.
Confidence: high
Scope-risk: narrow
* feat(desktop): unblock pure-browser dev MCP smoke with proxy + forceH5
The standalone-browser dev workflow (vite + Chrome DevTools MCP)
fails three ways without these escape hatches:
1. CSP `connect-src` only allows loopback origins, so the renderer
can't talk to the API server cross-origin even on a LAN.
2. With H5 access disabled the server returns 403 to all
cross-origin browser requests.
3. With H5 access enabled the desktop's loopback exemption
(`requiresH5AuthForServerUrl` returns false for `localhost:1420`)
calls `setAuthToken(null)`, so `buildSessionWebSocketUrl` omits
`?token=`, and the session WS handshake 401s into a 1006 close
reconnect loop. The UI shows "处理中..." but the message never
reaches the server — `wsManager.connections` stays `{}`, server
stdout is silent, and no `POST /api/sessions/.../message` ever
fires.
Two dev-only changes break that deadlock without affecting
production:
- `desktop/vite.config.ts`: add `server.proxy` for `/health`,
`/api`, `/ws`, `/local-file`, `/preview-fs` to `127.0.0.1:3456`.
Same-origins all backend traffic so CSP and CORS pass. Vite dev
only — Electron production path is unaffected because Electron
resolves the server URL via IPC at runtime.
- `desktop/src/lib/desktopRuntime.ts`: add a `?forceH5=1` query
param check in `initializeBrowserServerUrl`. When present, treat
the URL as needing H5 auth even if it's loopback, so the H5 token
survives bootstrap and gets attached to the session WS URL. In
production the param is never set, so behavior is identical.
Workflow with both in place:
http://localhost:1420/?serverUrl=http%3A%2F%2Flocalhost%3A1420
&forceH5=1&h5Token=<TOKEN>
Verified end-to-end via WS sniffer + server log:
- ws://localhost:1420/ws/<sid>?token=... opens
- recv {"type":"connected","sessionId":"..."}
- server logs "[WS] Client connected" + "Starting CLI for ..."
- Sending a "use Task with subagent_type=Explore" prompt produced
a `tool_use_complete` for the Agent tool with
`input.subagent_type="Explore"` exactly as routed.
Also rewrites `docs/desktop/10-local-mcp-testing.md` to record:
- both patches as required steps (was missing the forceH5 piece)
- exact PowerShell commands to mint a one-shot H5 token
- the WebSocket sniffer script and three-signal diagnosis recipe
for "is the backend actually processing"
- the Item 5 (routing observability) gap: the change in
`src/tools/AgentTool/UI.tsx` only affects CLI/Ink rendering;
the desktop's separate `ToolCallGroup.tsx` doesn't surface
`subagent_type` and would need its own follow-up patch.
Tested: ran `bun run src/server/index.ts` + `bun run dev` in the
desktop/, opened the documented URL via Chrome DevTools MCP,
sent a Task-with-explicit-subagent_type chat message, observed
the full WS frame sequence and server log lines listed above.
Confidence: high
Scope-risk: narrow
Directive: do not generalize the forceH5 escape hatch to a
permanent feature — when it's needed, the dev-mode
loopback-clears-token branch is doing the right thing for
Electron and should not be removed.
* feat(desktop): show subagent_type badge next to Agent tool header
The CLI/Ink change in src/tools/AgentTool/UI.tsx surfaces routing
in the terminal transcript as `<description> → <subagent_type>`,
but the desktop renderer is independent (`ToolCallGroup.tsx` /
`AgentCallCard`) and was dropping that signal entirely. Diagnosing
"why didn't it use code-reviewer?" required reading analytics or
guessing.
Add a small `→ <subagent_type>` chip next to the Agent header in
AgentCallCard. The badge mirrors the CLI behavior:
- Renders only when `input.subagent_type` is a non-empty string;
general-purpose / fork / omitted defaults stay quiet.
- Carries `title="subagent_type: <name>"` for hover detail.
- Uses the same border-pill treatment as other secondary
transcript chips so it doesn't compete with the description.
Verified end-to-end via the local Chrome DevTools MCP path: a
real chat with `subagent_type=Explore` renders `Agent → Explore
查找specialistRouter相关文件` in the live transcript, screenshot
saved to artifacts/desktop-routing-arrow-highlighted.png.
Tested: cd desktop && bun run test -- --run -t "subagent_type"
2 tests pass:
- renders subagent_type as → Explore badge next to Agent header
- omits subagent_type badge when input has no subagent_type
Confidence: high
Scope-risk: narrow
* feat(desktop): MCP tool visibility, per-tool toggle, marketplace install
Surface MCP capabilities so the agent never under-uses a configured server,
and add a one-click install path for new servers.
Tool visibility & control:
- GET /api/mcp/:name/tools returns the live tool catalog (handles disabled /
needs-auth / host-preflight-failed / connected states)
- POST /api/mcp/:name/tools/:tool/toggle hides a single tool from the agent,
persisted to ~/.claude.json disabledMcpTools (user scope, all projects);
fetchToolsForClient filters on it and the cache is invalidated on toggle
- Desktop MCP detail page gains an Overview/Tools tab strip (both edit and
read-only views) listing name, description, annotations, JSON schema, plus
an inline enable/disable switch with a "hidden from agent" marker
Marketplace:
- Curated builtin catalog (14 servers across 8 categories) plus opt-in remote
catalog sources cached under ~/.claude/cc-haha/mcp-marketplace.json
- Endpoints: list / refresh / add-source / remove-source
- Desktop marketplace page with category grouping, install dialog (with
required-env prompts), and source management
Plugins:
- Plugin list rows get inline enable/disable + uninstall, matching the MCP
settings UX, instead of requiring users to open the detail panel
- Extract shared ToggleSwitch component
Tested: bun test src/server/__tests__/mcp.test.ts (tools list + per-tool
toggle + marketplace), cd desktop && bun run lint, mcpSettings vitest, and a
browser smoke pass via the local Chrome DevTools MCP flow (tool toggle
persistence, marketplace render, plugin enable/disable + uninstall dialog).
Not-tested: full bun run verify gate
Scope-risk: moderate
* fix(agent-tool): rephrase gp-strict error to stop poisoning sessions
Live testing surfaced an unintended side effect of the gp-strict
redirect message. The original wording embedded attribute-style
fragments like `subagent_type="code-reviewer"`. When that error
came back to the model as a tool result, the model copied the
shape into a TEXTUAL `<tool_use name="Agent">{...}` block instead
of issuing a real tool call — and every subsequent Agent call in
that session degraded to text. Confirmed by inspecting the
session JSONL via /api/sessions: messages 4..N in the affected
session were `assistant: text(...)` carrying simulated tool_use
XML, not `tool_use` content blocks. Reproduced with two unrelated
models (claude-haiku-4.5 and mimo-v2.5-pro) on the same poisoned
session, and crucially DID NOT reproduce in a fresh session with
either model — so the failure is the message text inducing
text-form mimicry, not the model itself.
Fix:
- Extract the redirect text into `formatSpecialistRedirectMessage`
in specialistRouter.ts so it has its own test surface.
- Rephrase as prose that names the parameter and value without
any `key="value"` assignment-and-quotes form. New shape:
"This task looks like a job for the code-reviewer specialist
rather than the general-purpose default. Re-call Agent with
the subagent_type parameter set to code-reviewer. ..."
- Wire AgentTool.tsx through the helper so the throw site is one
line.
Add a regression assertion to specialistRouter.test.ts that the
formatted message contains NO `key="value"` shapes, no XML-ish
open tags, and no `"subagent_type":` JSON fragments — across all
ten specialist names. This is the property the live failure
violated; lock it.
The invocation-limiter message uses shell-style `KEY=N` syntax
(not `key="value"`), did not exhibit the same behavior in
testing, and is left untouched.
Tested: bun test src/tools/AgentTool/specialistRouter.test.ts
17 pass (13 existing + 4 new), 61 expect() calls
Confidence: high
Scope-risk: narrow
Directive: any future change to redirect/error text returned
to the model should preserve the no-key="value" property.
* feat(orchestration): require copying project tool rules into sub-agent prompts
Live test on the desktop `+` orchestration mode showed mimo-v2.5-pro fan
out three sub-agents in parallel for a multi-task PR review prompt — but
none of the dispatched prompts mentioned the project's mandated codegraph
MCP tool. The sub-agents fell back to plain Bash + git diff + grep, so the
project's preferred code-exploration tooling went unused.
Root cause: the orchestrator's `Propagate project conventions` rule was a
soft "for example, restate codegraph rules" bullet. The model read it as
optional. Built-in specialist system prompts (code-reviewer, security-
reviewer, debugger, etc.) explicitly enumerate Bash/grep/git as inspection
tools and never mention codegraph; CLAUDE.md is loaded but the system
prompt outweighs it. The orchestrator was the only place the gap could be
closed, and it skipped it.
This commit upgrades the rule from optional to REQUIRED in the
orchestration system prompt, names the concrete check ("scan CLAUDE.md,
AGENTS.md, .claude/rules"), and tells the orchestrator to copy matching
rules verbatim. Read-only research agents (Explore, Plan) are called out
explicitly because they run with omitClaudeMd:true and absolutely depend
on the orchestrator to forward project tool conventions.
A new ORCHESTRATION_PROPAGATE_RULES_MARKER export plus a regression test
in conversations.test.ts lock the imperative phrasing so it can't be
quietly downgraded back to a soft suggestion.
Wiring is unchanged: built-in code-reviewer/security-reviewer/commit-pr
already inherit CLAUDE.md and have MCP tools available. This is purely a
prompt-strength change, only active when the user enables the desktop
`+` orchestration toggle.
Tested: bun test src/server/__tests__/conversations.test.ts (new test
plus the two existing orchestration-prompt tests pass).
Confidence: high
Scope-risk: narrow
* fix(verification-gate): preserve reminder flag across verification reset
Caught by an orchestrator-mode code-reviewer dispatch on this branch.
When a verification subagent invocation reset the edit counter, the early
return in getVerificationGateState() hardcoded reminderAlreadyFired:false,
discarding any reminderFired flag we'd accumulated walking back from the
tail. Concretely: if a verification_gate_reminder fired AFTER the verify
(i.e. newer than verify in the transcript), the reverse walk would set
reminderFired=true on its way to the verify boundary, then throw it away
on the early return. The gate would then inject a fresh reminder every
turn while the post-verify edit count stayed above the threshold,
ballooning the prompt.
Fix: carry the accumulated reminderFired through the early return. The
reminder we saw is necessarily newer than this verify (we're walking
backwards from the tail and only got here after seeing it), so it's the
valid signal that the model was already nudged for the current edit batch.
Tests: extend verificationGate.test.ts with a "preserves a reminder fired
AFTER a verification reset (no spam loop)" case that fails on the old
hardcoded false and passes with the fix. The pre-existing "ignores a
reminder that fired BEFORE a verification reset" test still passes because
in that ordering the reminder is older than the verify, so reminderFired
stays false on the way back.
Tested: bun test src/utils/verificationGate.test.ts (11/11 pass).
Confidence: high
Scope-risk: narrow
* chat(i18n): rename `协调模式` to `编排模式` (zh / zh-TW)
Brings the simplified- and traditional-Chinese label for the desktop `+`
menu's orchestration toggle in line with the English (Orchestration mode),
Japanese (オーケストレーションモード), and Korean (오케스트레이션 모드)
labels — all five locales now use the same `orchestration` word family.
`协调` was accurate but vague; `编排` matches the term Chinese
developers already see in LangChain, CrewAI, AutoGen, and Anthropic docs,
and tells a user reading just the label what the toggle actually does
(plan → fan out → synthesize) rather than a generic `coordinate`.
UI-text only. No behavior, store, WS message, or i18n-key changes.
* feat(desktop): welcome-screen task cards with one-click orchestration
Adds four quick-start task cards to the new-session welcome screens — both
EmptySession (the no-session-yet entry) and ActiveSession's empty state
(when a fresh session tab has no messages yet). Each card pre-fills the
composer with a starter prompt for a common workflow:
- Pre-merge code review (auto-enables Orchestration mode)
- Investigate a failing test (auto-enables Orchestration mode)
- Write unit tests (no orchestration)
- Explain unfamiliar code (no orchestration)
Cards flagged `orchestrate: true` automatically turn on Orchestration
mode for the session that gets created (or for the live session in
ActiveSession's empty state), so a new user sees fan-out behavior on
first contact instead of having to discover the `+` menu first. This is
the intended discovery surface for the orchestration directive shipped on
this branch.
Plumbing:
- WelcomeTaskCards.tsx is a shared component used by both welcome
surfaces, so card list, copy, and styles stay in one place.
- EmptySession owns its own composer textarea, so card click flips
component state directly (setInput + draftOrchestrate flag) and the
flag is applied via setSessionCoordinatorMode after createSession()
resolves and before connectToSession() — chatStore replays the
persisted toggle on connect, so the CLI launches with the right
--append-system-prompt.
- ActiveSession's session is already live, so a card click dispatches a
`cc-haha:composer-prefill` window event that ChatInput listens for
and applies to its composer state when the sessionId matches the
active tab. Orchestration cards call setSessionCoordinatorMode
directly, which sends `set_coordinator_mode` over WS and triggers a
CLI restart with the orchestration directive — verified live: the
server logs `[WS] Restarted CLI for <id> with runtime override`
immediately after the click.
- Cards never disable an existing Orchestration toggle. Non-orchestration
cards simply leave the toggle alone, so users who pre-enabled
Orchestration via the `+` menu don't have it silently turned off.
- Hidden on phone-sized H5 viewports (composer is already dense there).
i18n: ten new keys under `empty.tasks.*` with localizations for en,
zh, zh-TW, jp, kr (the same five locales the orchestration label
already covers). The orchestration hint reads `将通过编排模式分派给
专家子代理处理` in zh — aligning with the
`协调模式`→`编排模式` rename shipped in the previous commit.
Tests: five new tests in EmptySession.test.tsx covering: cards render on
desktop, hidden on mobile, click pre-fills the composer, orchestration
cards persist coordinator mode after submit, non-orchestration cards
leave coordinator mode untouched. `bun run vitest` 22/22 pass.
`bun run lint` (tsc --noEmit) clean.
Tested live in browser: clicked the Pre-merge review card → composer
filled with the 78-char zh prompt → localStorage carries
`coordinator-modes[<sessionId>]: true` → server log shows the CLI
restarted with `--append-system-prompt`. Screenshot:
artifacts/desktop-welcome-task-cards.png.
Confidence: high
Scope-risk: narrow
* chore(scripts): one-click MCP smoke setup for Windows
Wraps the recurring 4-step setup from docs/desktop/10-local-mcp-testing.md
into a single idempotent script. Same workflow we'd otherwise type by hand
every time we want to drive the desktop UI from Chrome DevTools MCP.
scripts/dev-mcp-test.ps1 does:
1. Health-check the API server on :3456 — fail fast with the start
command if it's down.
2. Health-check Vite on :1420 — fail fast with the start command if
it's down.
3. Confirm Vite's /health proxy is wired through to the server (catches
stale vite.config.ts).
4. Ensure http://localhost:1420 is in H5 allowedOrigins (PUT only when
missing).
5. Regenerate a fresh H5 token via /api/h5-access/regenerate.
6. Build the full URL with serverUrl + forceH5=1 + h5Token, copy it to
the clipboard, and print it.
7. With -Open, also Start-Process the URL in the default browser.
8. With -Quiet, only print the URL on stdout (for piping into other
tools, e.g. un run mcp:test:open chaining).
Surfaced via two npm scripts:
bun run mcp:test # generate URL + clipboard
bun run mcp:test:open # also open in default browser
Deliberate non-goals:
- Does NOT start server / Vite. Those are persistent dev processes the
developer wants to control lifecycle for; auto-starting them from a
one-shot script makes "is it still running?" questions ambiguous and
leaves zombies on script exit. The script tells the user the exact
command to run if either is down.
- Does NOT touch Electron. This workflow is the documented browser path
for Chrome DevTools MCP smoke; the Electron path doesn't need any of
this and runs through bun run electron:dev.
- Does NOT modify vite.config.ts proxy or desktopRuntime.ts forceH5
bypass. Both are already on this branch, and validation of those two
being correctly wired is what step 3 covers.
Doc: docs/desktop/10-local-mcp-testing.md grew a new "step 0 一键脚本"
section pointing at the script and naming the two npm targets, ahead of
the original 4 manual steps so future maintainers find the script first.
Verified by running it: server + vite both up, allowedOrigins pre-existed
so no PUT needed, fresh token issued, URL copied to clipboard, browser
launched into the welcome screen with the four task cards rendering
correctly. Screenshot in artifacts/desktop-welcome-task-cards-final.png.
Confidence: high
Scope-risk: narrow
* feat(desktop): zero-token "Recent activity" panel with hand-off button
Closes the painful gap where opening a new chat in a project means
re-explaining "what was I just doing" to the model. The user explicitly
called out that they don't want the obvious fix (auto-feeding the
previous transcript or an LLM-generated summary) because that burns
tokens on every new session.
This ships a different shape: derive the activity summary on the server
from on-disk state (session JSONL + git working tree), render it as a
read-only panel on the welcome screen so the user sees it, and only
move bytes into the model when the user explicitly clicks "Continue
from here" — and even then, only a 4-5 line hand-off paragraph (~60
tokens), not the previous transcript.
Server (zero LLM, all derivation):
- GET /api/projects/recent-activity?workDir=<abs>&excludeSessionId=<id>
- projectActivityService.getRecentActivity reads:
- sessionService.listSessions to find the most recent meaningful
session (skips empty messageCount=0 sessions and any
excludeSessionId, e.g. the just-created Untitled tab in
ActiveSession's empty welcome state, so the panel surfaces the
ACTUAL previous work session).
- Streams that session's JSONL and derives:
* lastUserMessageExcerpt (160 chars max, mid-word ellipsis)
* filesEdited list — Edit/Write/NotebookEdit tool_use file_paths,
de-duplicated in first-appearance order, capped at 8.
Hard-capped at 50k JSONL lines so giant transcripts don't hang
the welcome render.
- getRepositoryContext (existing, cached) for branch + default
branch + dirty state, plus two cheap git rev-list/status calls
for ahead/behind counts and exact dirty file count.
- Strict timeouts on each git command so the panel never blocks UI.
- Wired into router.ts under case 'projects'.
Frontend:
- RecentActivityCard reads via projectsApi.recentActivity.
- Two paths into it:
1. EmptySession (sidebar "New session" → user picks workDir):
shows once a workDir is chosen. "Open this session" switches
to the previous tab; "Continue from here" prefills the
hand-off into the empty composer's textarea.
2. ActiveSession's empty welcome state (sidebar "在 X 中新建会话"
creates a session immediately): excludeSessionId={activeTabId}
skips the brand-new empty session, hideContinueSessionButton
hides "Open this session" since the user is already on an
empty tab. "Continue from here" dispatches the existing
cc-haha:composer-prefill window event so ChatInput picks it
up — same plumbing the welcome task cards use.
- Auto-refreshes every 60s — git state changes between renders
(commits in another terminal, etc.) get picked up without UI work.
- Hidden on phone-sized H5 (composer is dense enough already).
Hand-off paragraph contents (i18n'd, ~60-100 tokens):
Last session on <branch>: "<title>".
Files touched: <up-to-5-files> (+N more).
Local is ahead of upstream by N commit(s).
N file(s) have uncommitted changes.
Please continue from there. Pick up by ...(describe the next step).
Token cost summary:
- Render: 0 tokens. Pure on-disk derivation, never sent to a model.
- "Open this session": 0 tokens. Just a tab switch.
- "Continue from here": ~60-100 tokens, ONLY if the user types more
and presses send. The user sees the prefill in the textarea before
sending and can edit or delete it.
i18n: 18 new keys (empty.recentActivity.*) localized for the same five
locales as the orchestration toggle (en/zh/zh-TW/jp/kr).
Live-tested via Chrome DevTools MCP on this branch:
- Opened cc-haha welcome path.
- Server returns correct shape: branch=feat/agent-routing-hardening,
aheadCount=6, dirtyCount=13, lastSession with 10 messages and
correct title.
- excludeSessionId correctly skips the empty just-created session and
surfaces the previous "请审查我当前分支..." session.
- "Continue from here" click prefills the textarea with the 152-char
Chinese hand-off paragraph; localStorage and WS state untouched
(zero token cost confirmed).
- Screenshots: artifacts/desktop-recent-activity-card.png and
artifacts/desktop-recent-activity-handoff.png.
bun run lint passes (tsc --noEmit clean).
Confidence: high
Scope-risk: moderate (new endpoint, but read-only and isolated; new UI
panel, but additive only — empty workdir or no prior sessions just
hides the card)
Tested: live MCP smoke per above.
Not-tested: server-side unit tests for projectActivityService (planned
follow-up; live integration validated the happy path end-to-end).
* fix(desktop): compact recent-activity card so composer stays in view
Live MCP smoke caught a layout regression. Welcome screen vertical
budget on a 923px viewport is roughly:
79 tab bar
+ ~250 hero block (logo + title + subtitle)
+ ~165 task cards
+ ~222 composer (ChatInput in hero variant)
+ ~64 flex padding
≈ 780px before activity panel
The first cut of RecentActivityCard rendered a 235px column block
(title row, chips row, italic excerpt row, file-chips row, action row).
Together with the task cards that pushed total content to ~410px
beyond what fits, and ActiveSession's empty-state wrapper used
flex flex-1 + justify-center without overflow handling — so the
sibling ChatInput got pushed below viewport bottom (composer.bottom
1112 on a 923 viewport, visible:false confirmed via DOM measurement).
Two changes:
1. Compact the card to ~127px (down 108px, roughly half) by collapsing
to a single horizontal row: icon | title + chips column | action
buttons. The italic last-user-message excerpt and the file-name
chip strip both go away — they're nice-to-haves, not load-bearing.
The data they carried is preserved: filesEditedCount stays as a
chip with a tooltip showing the first 5 file basenames; the lead
excerpt is still in the hand-off paragraph the "Continue from here"
button injects, so the user gets it where it counts (in the
composer) instead of as ambient ornament on the welcome screen.
Action buttons collapse their labels under sm: breakpoints to keep
the card single-row even in narrow side panels.
2. Add min-h-0 + overflow-y-auto to ActiveSession's isEmpty wrapper.
Belt-and-suspenders: future taller content (extra cards, longer
localized strings) now scrolls inside the welcome region instead of
shoving the composer offscreen. Composer remains a flex sibling and
stays anchored at the bottom regardless of content height above it.
Verified live in MCP browser:
recentActivity height: 235 → 127px
composer.bottom: 1112 (offscreen) → 907 (visible, on a 923 viewport)
textareaVisible: false → true
Tested across two projects with very different activity profiles
(cc-haha: short title + 0 files edited; layout-editor: long title +
20 files edited + 1876 messages). Both render in single horizontal
row.
Screenshot: artifacts/desktop-recent-activity-compact.png
bun run lint passes (tsc --noEmit clean).
Confidence: high
Scope-risk: narrow (CSS / layout only, no behavioral change).
---------
Co-authored-by: 你的姓名 <you@example.com>
Add a WhatsApp adapter backed by Baileys linked-device auth, plus desktop QR binding UI, server config endpoints, sidecar startup wiring, tests, and documentation.
Constraint: Uses WhatsApp Web linked-device auth, not Meta WhatsApp Business Cloud API.
Tested:
- cd adapters && bun run check:adapters
- bun test src/server/__tests__/adapters.test.ts
- cd desktop && bun run check:desktop
- bun run check:native
- bun run check:persistence-upgrade
- bun run check:docs
Not-tested:
- Live WhatsApp QR pairing, because no WhatsApp account/device was exercised here.
- bun run check:server, because the existing src/server/__tests__/conversations.test.ts timeout still fails independently.
Confidence: medium
Scope-risk: moderate
Real-world testing showed the unsigned app just needs the original 0.3.2
one-liner after the "damaged" prompt (System Settings "Open Anyway" also
works). Drop the Sequoia / "don't trash it" / clear-DMG-first / script
walkthrough noise and match the historical release-note style.
- release-notes/v0.4.0.md: macOS = drag in + `xattr -cr ...`, Windows one
line, Linux kept short.
- docs/desktop/04-installation.md: same trim, remove duplicated FAQ entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rewrite release-notes/v0.4.0.md to match the historical style (no emoji,
Highlights/Fixes/Notes), keep it user-facing only (drop release-process
notes), and correct Linux (supported since the Tauri builds, not new).
- README.md / README.en.md: replace Tauri references with Electron, list
macOS / Windows / Linux, and point first-launch approval to the guide.
- Rewrite docs/desktop/04-installation.md for Electron: Electron asset
names, Linux section, and the unsigned-macOS flow (clear the DMG quarantine
before double-clicking; drop the WebView2/right-click-open leftovers).
- install-macos-unsigned.sh: keep the same-folder DMG flow, no online download.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the local pre-push quality gate with a reminder-only hook, keep manual quality commands documented, and update the contract test to prevent reintroducing a blocking push gate.
Tested: bash .git/hooks/pre-push </dev/null
Tested: bun test scripts/git-hooks/install.test.ts scripts/pr/quality-contract.test.ts
Tested: bun run check:policy
Tested: bun run check:docs
Tested: git diff --check
Confidence: high
Scope-risk: narrow
MiniMax's official M3 model page and release notes describe M3 as supporting up to a 1M-token context window with a guaranteed minimum of 512K. Keep the built-in MiniMax preset aligned with that official default instead of inheriting the older 204.8K MiniMax generation limit.
Constraint: Official MiniMax M3 docs state the M3 API supports up to 1M tokens context window.
Rejected: Keep 204800 and rely on manual provider overrides | that makes the built-in MiniMax preset wrong for new users.
Confidence: high
Scope-risk: narrow
Directive: Do not copy MiniMax-M2.7's 204.8K window onto MiniMax-M3 without re-checking official M3 docs.
Related: https://www.minimax.io/models/text/m3
Related: https://www.minimax.io/blog/minimax-m3
Tested: git diff --check
Tested: bun test src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers-real.test.ts src/server/__tests__/provider-runtime-env.test.ts src/server/__tests__/providers.test.ts src/utils/__tests__/context.test.ts
Not-tested: bun run check:server blocked before execution by expired quarantine entries: server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
Remote main includes PR #694, which updates the MiniMax preset to MiniMax-M3 and removes deprecated built-in MiniMax context-window entries. Merge that provider baseline before applying the transcript context-window estimate fix so the local line keeps upstream provider defaults.
Constraint: Remote main advanced independently with PR #694 while local main carried unreleased commits.
Rejected: Reset local main to origin/main | would discard local-only main commits.
Confidence: high
Scope-risk: moderate
Directive: Preserve MiniMax-M3 provider preset changes when touching provider context-window logic.
Tested: bun test src/server/__tests__/conversations.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/provider-runtime-env.test.ts src/utils/__tests__/context.test.ts src/utils/__tests__/contextBudget.test.ts
Not-tested: bun run check:server blocked by expired quarantine entries: server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
Complete the Electron replacement boundary before merging by removing the renderer-side Tauri host fallback, tightening H5/browser access so only desktop navigation is tokenless, and moving desktop release publication to a tag-driven GitHub Actions matrix with a single final publish job.
Constraint: H5/browser capability access must not gain tokenless access through localhost or retired Tauri origins
Constraint: Desktop release artifacts must be built by GitHub Actions from version tags, not treated as local build outputs
Rejected: Keep localhost browser origins trusted for convenience | local browser contexts can access loopback services and must use the H5 token path
Rejected: Publish from each matrix job | partial releases can be created before all platforms finish
Confidence: high
Scope-risk: broad
Directive: Do not reintroduce Tauri origins or localhost browser origins into the trusted desktop origin set without a reviewed security design
Tested: bun test src/server/__tests__/h5-access-policy.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/diagnostics-service.test.ts src/server/middleware/cors.test.ts
Tested: bun test scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts
Tested: bun run check:desktop
Tested: bun run check:native
Tested: git diff --check
Not-tested: bun run check:server is blocked by expired quarantine entries server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
Add MiniMax-M3 as the new default for the minimax provider preset and
remove deprecated M2.5/M2.1/M2 entries. MiniMax-M2.7 and the highspeed
variant are kept for users who pinned the previous generation.
Updated places:
- providerPresets.json: default models -> M3, modelContextWindows trimmed
- modelContextWindows.ts: built-in window list mirrors the new set
- provider-presets / providers-real / thinking tests updated to assert
the new default
- .env.example and third-party docs (zh + en) recommend M3
- scripts/repro-issue-247-real.ts default model bumped to M3
Co-Authored-By: Octopus <liyuan851277048@icloud.com>
Introduce the Electron desktop shell alongside the existing React renderer and local Bun server boundary. The migration keeps the DesktopHost contract explicit across Tauri, Electron, and browser runtimes while adding Electron main/preload services for dialogs, shell, notifications, updates, tray/window lifecycle, terminal, preview WebContentsView, app mode, and release/package validation.
The commit also carries the latest local main desktop command updates, including agent slash entries and hidden-by-default markdown thinking details, so the packaged Electron build matches the current main UX surface.
Constraint: React renderer, local Bun server, REST/WebSocket, and sidecar boundaries must remain reusable during the migration
Constraint: macOS dev packages are ad-hoc signed and cannot prove Developer ID notarization or Gatekeeper release launch
Rejected: Browser-only smoke validation | it cannot exercise native dialogs, keychain prompts, notification behavior, or packaged app startup
Confidence: medium
Scope-risk: broad
Directive: Do not remove Tauri host support until signed Electron release artifacts pass native OS smoke on macOS, Windows, and Linux
Tested: bun run check:desktop
Tested: cd desktop && bun run check:electron
Tested: CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron📦dir
Tested: bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron
Tested: Computer Use read packaged Electron app window at desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app
Not-tested: Developer ID signed/notarized Gatekeeper launch
Not-tested: Real OS notification click-to-session action
Not-tested: Windows and Linux packaged app smoke on real hosts
Daily pushes should catch policy and path-aware local failures without making every contributor wait for full coverage. The hook now runs a new quality:push entrypoint that reuses the PR quality gate while skipping coverage; verify, quality:pr, and CI still retain the full coverage gate for PR readiness.
Constraint: Forks and local contributors need a faster default push path
Rejected: Remove coverage from quality:pr | PR readiness and CI still need the ratcheted coverage signal
Confidence: high
Scope-risk: narrow
Directive: Keep pre-push on quality:push; use verify or quality:pr when coverage evidence is required
Tested: bun test scripts/pr/quality-contract.test.ts scripts/git-hooks/install.test.ts
Tested: bun run check:policy
Tested: bun run quality:push
Not-tested: Live provider smoke; intentionally remains opt-in
The desktop app needs a Codex-style top-right opener that follows the agent's actual working directory instead of the source repository alone. This records the approved scope before implementation: detected local IDEs only, Finder/Explorer fallback, no external terminal, no branch/worktree mutation, and no Computer Use runtime dependency.
Constraint: The opener must match the active session cwd or materialized worktree so users inspect the same files the agent edits.
Constraint: First version must only display detected IDEs and must always keep file-manager fallback available.
Rejected: Reuse Computer Use app listing | would couple a basic open-project control to Python/runtime setup.
Rejected: Show missing IDEs as disabled menu entries | user explicitly wants only detected IDEs displayed.
Confidence: high
Scope-risk: narrow
Directive: Do not add external terminal targets or persistent opener preferences without revisiting the product scope.
Tested: Spec self-review for placeholders, contradictions, and path-resolution scope.
Not-tested: Runtime implementation not started in this commit.
The previous H5 preview no longer represented the current phone browser flow, so the README gallery now uses a composed image from the latest mobile screenshots while preserving the existing asset path and gallery proportions.
Scope-risk: narrow
Confidence: high
Tested: Viewed generated docs/images/desktop_ui/12_h5_access.png and confirmed dimensions are 2960x2100
Not-tested: Full docs build; image-only README asset replacement
The generated logo asset carried both an opaque square canvas and an inner rounded tile, so the desktop empty state and sidebar rendered a visible nested background. Rebuilt the canonical app icon with transparent outside corners, one white rounded badge, and a centered mark, then regenerated the Tauri icon set and docs raster assets from that source.
Constraint: Existing desktop views consume /app-icon.png directly with no wrapper background to remove.
Rejected: Add CSS masking around each img usage | would leave native app icons and docs assets with the same nested canvas problem.
Confidence: high
Scope-risk: narrow
Directive: Keep desktop/src-tauri/app-icon.png as the canonical 1024 RGBA source before regenerating platform icons.
Tested: cd desktop && bun run build; npm run --loglevel=error docs:build; git diff --check; sips alpha and size checks; iconutil icns size ladder; Chrome DevTools light and forced-dark screenshots.
Not-tested: full bun run verify gate, per prior instruction to skip local gate for this logo pass.
The desktop app and documentation need to present the new logo consistently, so the canonical 1024px source icon now feeds the Tauri icon family, desktop public assets, README images, and VitePress brand imagery.
Constraint: Tauri bundle icons are generated assets and must be refreshed from the source icon rather than relying on README or public image replacement alone
Rejected: Rename logo paths | existing README, desktop, and docs references can keep stable paths and pick up the new files directly
Confidence: high
Scope-risk: moderate
Directive: Regenerate desktop/src-tauri/icons whenever desktop/src-tauri/app-icon.png changes
Tested: cd desktop && bun run build; npm run --loglevel=error docs:build; git diff --check; iconutil expanded icon.icns size ladder
Not-tested: full local quality gate per request
The README is now the first screen most users see before deciding whether to download the app, so it leads with the desktop workflow, recent app features, and current screenshots while keeping CLI-from-source setup compact.
Constraint: Homepage should still mention the leaked Claude Code source origin.
Rejected: Keep the expanded CLI quick start on the homepage | it pushed sponsor and desktop content too far down.
Confidence: high
Scope-risk: narrow
Tested: bun run --silent check:docs
Tested: README image reference checks for Chinese and English files
Not-tested: GitHub README rendering after push
The desktop docs now explain the intended H5 setup path for personal and team use: enable H5 in Settings, generate a one-time token, configure allowed origins, and use LAN or a reverse proxy to open the mobile browser chat surface.
Constraint: H5 is opt-in browser access, not a public multi-tenant auth system.
Rejected: Hide the setup details in the implementation spec only | users need operational guidance for token handling, CORS, and reverse proxy routing.
Confidence: high
Scope-risk: narrow
Directive: Keep this page aligned with Settings labels and the token/CORS behavior before documenting broader public hosting.
Tested: bun run check:docs
Tested: Live local smoke with temporary HOME: /health 200, H5 verify without token 401, H5 verify with token 200, configured Origin echoed by CORS.
The implementation needs to land through isolated service, auth, runtime, settings, and mobile-shell tasks so subagents can work without colliding with the desktop default path.
Constraint: Follow the approved opt-in H5 design and keep Tauri/local desktop behavior unchanged
Constraint: Use subagent-driven execution for implementation work
Rejected: One broad implementation pass | too much shared-file risk around AppShell and auth middleware
Confidence: high
Scope-risk: moderate
Directive: Execute Task 4 before Task 5 because both touch AppShell
Tested: Plan self-review for spec coverage, placeholders, and type naming consistency
Not-tested: Runtime behavior; this commit is implementation planning only
The H5 surface needs a small personal/team workflow instead of a public login system, so the spec keeps the first version to an explicit Settings switch, generated token, browser connection screen, controlled CORS, and mobile chat shell changes.
Constraint: Preserve the existing desktop/Tauri local flow by default
Constraint: H5 is for personal and team controlled environments, not public multi-tenant hosting
Rejected: Full account login and short-lived session exchange | too much scope for the requested first version
Confidence: high
Scope-risk: moderate
Directive: Do not implement H5 by simply exposing the current desktop API without token, origin, and mobile-shell boundaries
Tested: Spec self-review for placeholders, contradictions, scope, and ambiguity
Not-tested: Runtime behavior; this commit is design documentation only
Contributors and coding agents need one local command that both reports and enforces the quality contract. This change turns the PR gate into the shared verification entrypoint, adds path-selected local lanes, tightens coverage accounting around changed lines, and documents the repair loop in contributor and agent-facing guidance.
Constraint: Ordinary PR verification must stay non-live and runnable without provider credentials
Constraint: Coverage policy updates in this commit require maintainer approval before push/merge
Rejected: Keep quality guidance only in docs | agents need executable scripts and AGENTS.md instructions to follow the loop consistently
Confidence: high
Scope-risk: broad
Directive: Do not bypass `bun run verify` for production changes; fix failed lanes and coverage reports instead of lowering thresholds
Tested: bun run check:policy
Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run verify
Not-tested: live provider baseline; no provider credentials were required for this non-live PR gate
The repository now has a measurable PR quality path instead of a loose set of
manual checks. Coverage, quarantine governance, provider smoke, desktop smoke,
and workflow wiring all produce durable reports that contributors and maintainers
can inspect without reconstructing terminal output.
This also fixes the desktop smoke current-runtime path so browser-driven smoke
runs use the desktop default active provider instead of forcing the official
current model, and records that runtime decision as an artifact.
Constraint: Default PR gates must remain non-live and contributor-safe while live model checks stay explicit.
Constraint: Release packaging is still GitHub Actions based, so release preflight must run before the build matrix.
Rejected: Make live provider or desktop smoke mandatory on every PR | secrets, quotas, and model availability are maintainer-controlled.
Rejected: Let PRs lower coverage baselines in the same change | base-branch ratchet comparison must remain authoritative.
Confidence: high
Scope-risk: moderate
Directive: Do not relax coverage or quarantine policy without a maintainer approval label and a fresh quality report.
Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run quality:gate --mode pr
Tested: bun run quality:gate --mode baseline --allow-live --only provider-smoke:* --provider-model nvidia-custom:main:nvidia-custom-main --artifacts-dir /tmp/quality-gate-live-smoke
Tested: bun run quality:gate --mode baseline --allow-live --only desktop-smoke:* --provider-model current:current:current-runtime --artifacts-dir /tmp/quality-gate-desktop-smoke-fixed
Tested: git diff --check
Not-tested: Full live release mode with multiple providers in hosted CI; provider credentials and quota remain maintainer-controlled.
Computer Use is useful when explicitly needed, but exposing its MCP tools by default creates unnecessary desktop-control surface for users who want coding-only sessions. This adds a shared disable path for CLI flags, environment, and desktop settings while keeping preauthorized app state in one config file.
The same change also preserves Windows and WSL shell startup behavior by applying the MSYS argument-conversion guard only on WSL-bound launches.
Constraint: Computer Use MCP must not be exposed to the Coding Agent when disabled
Constraint: Desktop settings and CLI sessions need to read the same persisted Computer Use config
Rejected: Environment-only disable switch | desktop users need a persistent Settings control
Rejected: Remove Computer Use setup entirely | enabled sessions still need the existing built-in MCP path
Confidence: high
Scope-risk: moderate
Directive: Keep every new Computer Use entrypoint wired through loadStoredComputerUseConfig or the CLI disable flag before adding MCP tools
Tested: bun test src/utils/computerUse/gates.test.ts src/utils/computerUse/preauthorizedConfig.test.ts src/server/__tests__/computer-use-api.test.ts src/utils/shell/wslInterop.test.ts desktop/src/pages/ComputerUseSettings.test.tsx
Tested: bun run check:server; bun run check:desktop; bun run check:docs; bun run check:policy; bun run check:native; git diff --check
Tested: SKIP_INSTALL=1 ./desktop/scripts/build-macos-arm64.sh; codesign verify; hdiutil verify; built CLI Computer Use E2E exposure and disable checks
Not-tested: Full screenshot/control action after granting macOS Screen Recording permission on this machine
The WeChat and DingTalk IM docs were behind the actual desktop adapter flow, especially around QR binding versus user pairing and how users discover slash commands when no platform menu exists. This records the setup path with screenshots and nudges paired users toward /help from the bot itself.
Constraint: DingTalk does not expose the same menu setup path documented for Feishu, so command discovery must work through chat text.
Rejected: Document commands only in VitePress | users may be on mobile when they need the commands.
Confidence: high
Scope-risk: narrow
Directive: Keep IM command docs aligned with adapters/common/format.ts when adding commands.
Tested: cd adapters && bun test common/ dingtalk/ wechat/
Tested: cd adapters && bunx tsc --noEmit
Tested: bun run check:docs
Tested: bun run check:adapters
Not-tested: Live WeChat or DingTalk message delivery with real platform accounts
DingTalk can receive interactive card callbacks, but the card UI still depends on a published template, so the adapter now supports a template-backed card path with text commands as the reliable fallback. Telegram and WeChat use the same allow-once, allow-always, and deny semantics so manual authorization behaves the same across platforms.
Constraint: DingTalk button rendering requires an operator-provided interactive card template id
Rejected: Treat the existing AI streaming card template as a permission card | it cannot guarantee visible action buttons
Confidence: high
Scope-risk: moderate
Directive: Do not remove the text approval fallback unless DingTalk card template provisioning is guaranteed
Tested: bun test adapters/common/__tests__/permission.test.ts adapters/dingtalk/__tests__/permission-card.test.ts adapters/telegram/__tests__/telegram.test.ts adapters/common/__tests__/config.test.ts src/server/__tests__/adapters.test.ts
Tested: bunx tsc -p adapters/tsconfig.json --noEmit
Tested: bun run check:adapters
Tested: cd desktop && bun run build
Tested: bun run check:server
Tested: bun run check:docs
Tested: git diff --check
Not-tested: bun run quality:pr is blocked by existing CLI core approval policy; see artifacts/quality-runs/2026-05-03T13-19-56-232Z/report.md
DingTalk uses QR registration to store client credentials, then reuses the existing IM pairing and session bridge. The merged main implementation keeps the existing WeChat QR binding path intact while adding DingTalk as a peer IM platform. The default IM workdir now falls back to the local user working directory so a newly bound chat can start immediately even when recent-project history is empty.
Constraint: Local main already carries WeChat IM, so the merge keeps WeChat config, QR binding, sidecar args, and unbind behavior intact while adding DingTalk.
Rejected: Keep empty defaultProjectDir as project-picker-only | newly bound IM users can hit a dead end with no recent projects.
Confidence: high
Scope-risk: moderate
Directive: Keep IM platform unions synchronized across config, pairing, sidecar args, desktop settings, and docs.
Tested: bun test common/ dingtalk/ wechat/; bun test src/server/__tests__/adapters.test.ts; cd adapters && bunx tsc --noEmit; bun run check:policy; bun run check:server; bun run check:desktop; bun run check:adapters; bun run check:native; bun run check:docs
Not-tested: quality:pr full gate was blocked by existing local main CLI-core diff requiring allow-cli-core-change maintainer approval; live post-fix DingTalk second-message delivery was not repeated after the merge.
WeChat needs a QR-paired path instead of bot-token setup, so the adapter layer now includes the iLink protocol calls, desktop pairing UI, server-side bind/unbind APIs, and shared IM command behavior. Empty project history falls back to the user's default work directory so mobile /new works without pre-opening a desktop project.
Constraint: Tencent iLink login returns a URL that the desktop UI must render as a QR image locally
Constraint: IM adapters should keep /new, /projects, status, permission, and default workdir behavior consistent across WeChat, Feishu, and Telegram
Rejected: Require users to paste absolute project paths for first WeChat sessions | mobile onboarding should work from the default user working directory
Confidence: high
Scope-risk: moderate
Directive: Do not change WeChat polling back to overlapping intervals; getupdates is a long-poll endpoint and must remain serialized
Tested: Real WeChat QR scan, inbound /status, outbound reply, and unbind E2E
Tested: bun run check:adapters
Tested: bun run quality:pr
Not-tested: Re-scan live WeChat after the default-workdir fallback tweak; covered by adapter config tests and PR gate
Superpowers plan/spec files are local execution artifacts, and this repo already ignores docs/superpowers/. Remove the previously tracked plan files from the index so future quality-gate work does not mix local planning output into product commits.
Constraint: Local Superpowers files should remain available on disk but not be versioned.
Rejected: Add another ignore rule | docs/superpowers/ is already ignored, and the problem was tracked files bypassing ignore rules.
Confidence: high
Scope-risk: narrow
Directive: Keep generated quality reports and Superpowers planning artifacts out of repository commits.
Tested: git diff --check --cached
Tested: git ls-files -ci --exclude-standard
Not-tested: runtime gates because this is repository hygiene only.
Contributors need a visible path for local verification instead of relying on design notes or maintainer memory. Add a GitHub-facing CONTRIBUTING entrypoint, bilingual docs pages, README links, and VitePress sidebar navigation for PR gates, live model baselines, provider selection, reports, and release checks.
Constraint: Live baseline providers are local machine state; docs must tell contributors how to list and choose their own providers without maintainer UUIDs.
Rejected: Keep the instructions only in the quality-gate design doc | too hidden for clone-and-contribute workflows
Confidence: high
Scope-risk: narrow
Directive: Keep quality-gate commands documented wherever contributor onboarding links are exposed.
Tested: bun run check:docs
Tested: bun run quality:pr
Not-tested: live baseline after the docs-only wording change
The sponsor section now reflects the active partners only, with
clickable referral copy and local logo assets so GitHub renders the
same layout in both Chinese and English READMEs.
Constraint: Chinese and English README sponsor sections must stay synchronized
Rejected: Keep LegionProxy as a lower-priority sponsor | user requested removing it
Confidence: high
Scope-risk: narrow
Tested: git diff --cached --check
Not-tested: GitHub rendered README preview
Add the sponsor entry to both README variants so GitHub visitors see the same partnership information in Chinese and English. The logo is kept in the docs image tree so the table can render without relying on a remote asset.
Constraint: Sponsorship section should use the table layout shown by the user
Rejected: Text-only sponsor row | it missed the logo-focused layout requested by the example
Confidence: high
Scope-risk: narrow
Directive: Keep README.md and README.en.md sponsorship entries aligned when adding or editing sponsors
Tested: git diff --cached --check
Not-tested: GitHub web rendering after push
Desktop sessions can switch provider and model while a CLI subprocess is already alive, so the server now serializes runtime restarts and marks provider-managed launches to prevent stale settings env from overriding the selected provider. Provider settings also write API key env consistently and clear stale managed keys before syncing.
This includes the related desktop/docs brand asset refresh and keeps the desktop locale default in Chinese, with tests updated to match the current provider semantics.
Constraint: Session-scoped model selection must win over cc-haha/settings.json and inherited ANTHROPIC_* values.
Rejected: Store the selected model as a global provider activeModel | chat runtime selection is per session.
Confidence: high
Scope-risk: moderate
Directive: Do not remove CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST without validating Desktop provider switching against stale settings env.
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/conversations.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/providers-real.test.ts
Tested: cd desktop && bun run test src/stores/settingsStore.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
Not-tested: Full desktop production package/signing.
Local main was already updated to origin/main at 8f0e46e, and this merge brings those buddy/docs/env changes onto feat/dev-desktop. The only conflict was .github/workflows/build-desktop-dev.yml, where both branches added the workflow; the resolved version keeps the mainline body and retains the libfuse2 Linux dependency needed for AppImage-oriented tooling.
Constraint: Merge was already in progress from local main at 8f0e46e
Rejected: Abort and re-run merge from scratch | existing index already isolated the single conflict cleanly
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep libfuse2 in the desktop dev build workflow unless Linux packaging is revalidated without it
Tested: git fetch origin main; git diff --check; manual resolution of build-desktop-dev workflow conflict
Not-tested: Root TypeScript typecheck because existing generated desktop/src-tauri/target and extracted-natives assets are included and fail independently of this merge
- security: XSS sanitization with DOMPurify in Markdown/Mermaid/PermissionDialog;
path whitelist in filesystem API; fake keys in test/config files
- perf: fine-grained Zustand selectors in Sidebar/StatusBar/ContentRouter;
50ms throttle on streaming deltas; React.memo + useMemo in MessageList;
useRef for frequent keyboard shortcut state; AbortController 30s timeout
- leaks: WS session TTL timers (5-min cleanup on close); batch splice for
sdkMessages/stderrLines; folderPath validation in cronScheduler
- quality: optimistic update rollback in settingsStore; error state in
providerStore/teamStore; i18n for all hardcoded English strings
- docs: desktop architecture and features docs updated; VitePress nav fixed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This folds together the desktop-side fixes needed before broader rollout.
Session resume no longer deadlocks waiting on init, Mermaid and inline image
output render inside chat, task and sub-agent state stay visible during
execution, local build/release paths are safer, and Feishu/Telegram now expose
lightweight mobile commands (/help, /status, /clear) without adding a new
adapter-specific protocol.
Constraint: Desktop releases must publish updater artifacts from non-draft GitHub releases
Constraint: IM commands need short, phone-friendly responses and low operational complexity
Rejected: Add a dedicated IM command API surface | re-used existing slash commands and session/task REST endpoints to keep adapters thin
Rejected: Wait for task_update push events in WebUI | added low-risk polling because the current frontend ignores that event path
Confidence: medium
Scope-risk: broad
Reversibility: clean
Directive: Keep IM command replies terse and mobile-first, and merge local fallback slash commands when server-provided lists are partial
Tested: cd desktop && bun x vitest run src/components/chat/MermaidRenderer.test.tsx src/components/markdown/MarkdownRenderer.test.tsx
Tested: cd desktop && bun x vitest run src/components/chat/composerUtils.test.ts src/pages/ActiveSession.test.tsx src/stores/chatStore.test.ts
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "SDK init arrives only after the first user turn" --timeout 60000
Tested: cd adapters && bun test common/ feishu/ telegram/
Tested: cd adapters && bunx tsc --noEmit
Not-tested: Full GitHub Actions release run on all three desktop platforms
Not-tested: Local DMG packaging end-to-end on Apple Silicon
Not-tested: Real Feishu/Telegram device sessions against a live adapter process
- Add MiniMax-M2.7 as the recommended default chat model
- Add MiniMax-M2.7-highspeed as a faster alternative
- Update base URL to api.minimax.io (international) with note about api.minimaxi.com for China
- Add model comparison table in documentation (both CN and EN)
- Add API key registration link to MiniMax Open Platform
Remove .claude/ and docs/superpowers/ from git tracking as they are
local-only config and planning files. Also remove stale screenshots
and update docs/channel content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>