798 Commits

Author SHA1 Message Date
你的姓名
ddc3570562 test: add tests for image-gen plugin, options API, and config modal
- MCP server tests (21 cases): initialize, tools/list, list_providers
  with capabilities, generate_image validation, edit_image capability
  check, SSRF protection (IPv4/IPv6/protocol), model capabilities
  matching, unknown tool, ping
- Server API tests: GET/POST /api/plugins/options with schema filtering,
  sensitive value masking, missing field validation
- Desktop component tests: PluginConfigModal render, fetch, save with
  masked sensitive field skip, error handling, cancel flow
- Fix MCP server async message handling: track pending promises and
  wait for completion before process exit

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 15:12:40 +08:00
你的姓名
4fbf567874 fix(plugins): address PR review findings for image-gen plugin
HIGH fixes:
- Complete SSRF protection: add IPv6 private (fc00::, fe80::),
  IPv4-mapped IPv6 (::ffff:10.x), and 0.0.0.0 to blocklist
- Validate provider baseUrl against private IP ranges at load time
- Extract shared validateUrlSafety() for reuse

MEDIUM fixes:
- Skip unchanged masked sensitive fields on save (prevent '********'
  from overwriting real API keys)
- Filter POST /api/plugins/options to only schema-declared keys
- Whitelist img src to http/https/data protocols only
- Improve model capabilities matching: exact → prefix → contains
- Cache providers at startup instead of per-tool-call

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 14:47:11 +08:00
你的姓名
87c49aa7a4 merge: resolve release-notes conflict with main
Combine main's Workspace LSP release notes with image-gen plugin
feature notes. Keep main's editor-lsp-foundation content and add
new sections for image-gen MCP plugin, desktop plugin config, and
inline image rendering.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 14:38:43 +08:00
你的姓名
ec0197adfa feat(desktop): render image blocks from tool results inline in chat
Tool results containing MCP image content blocks ({ type: 'image',
data, mimeType }) are now rendered as inline images in the chat UI.

Changes to ToolCallBlock.tsx:
- Add extractImageBlocks() to parse image and image_url blocks
- Render image blocks as a responsive grid above the text output
- Support both MCP image blocks and OpenAI-style image_url blocks
- Result pane auto-expands when image blocks are present

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 13:59:54 +08:00
你的姓名
4c128e0764 fix(plugins): address code review findings for image-gen plugin
HIGH fixes:
- SSRF protection: block private/link-local IPs and file:// URLs
  in edit_image image_url parameter
- Mask sensitive values in GET /api/plugins/options response

MEDIUM fixes:
- Clamp n parameter to 1-10 to prevent API bill exhaustion
- Validate prompt is non-empty string before API call
- Return empty array when no providers configured (clear error)
- Add 1MB buffer limit for JSON-RPC line accumulation
- Fix PluginConfigModal useEffect re-fetch loop on parent re-render

LOW fixes:
- Remove unused requestId variable

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 13:00:44 +08:00
你的姓名
58dd7ac816 feat(plugins): add image-gen MCP plugin with multi-provider fallback
Add image generation plugin that supports OpenAI-compatible APIs (Agnes,
GPT-image-2, Gemini image, nano-banana, DALL-E, Flux, etc.) with
automatic provider fallback and parameter compatibility retry.

New plugin:
- plugins/image-gen/ — MCP server (zero-dependency ESM, stdio JSON-RPC)
- 4 tools: generate_image, edit_image, list_providers, list_models
- Up to 3 configurable provider slots with fill-in-the-blank UI
- Each provider: full params → minimal params retry (spriteflow pattern)

Desktop UI enhancements:
- PluginConfigModal for visual userConfig configuration
- Configure button in PluginDetail when plugin has userConfig
- GET/POST /api/plugins/options API endpoints
- Sensitive fields use password input + secure storage

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 12:56:34 +08:00
你的姓名
7f990fb6fe fix(desktop): satisfy settings store mock typing
Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-14 12:25:55 +08:00
你的姓名
d13f29e097 feat(workspace): enable automatic LSP diagnostics
Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-14 12:02:40 +08:00
小橙子
c9cb42b780
Merge pull request #47 from 706412584/worktree-prompt-too-long-recovery
Fix desktop window controls layout
2026-06-14 01:50:27 +08:00
你的姓名
8d16b43113 fix(desktop): keep tab strip from pushing window controls
Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-14 01:38:16 +08:00
你的姓名
cd9117d1e0 Merge branch 'worktree-prompt-too-long-recovery'
Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-14 01:27:44 +08:00
你的姓名
f06d88607f fix(desktop): update about page product name
Show Code Council in the About tab so the UI matches the desktop product branding.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-14 01:27:07 +08:00
小橙子
395599974c
Merge pull request #46 from 706412584/worktree-solo-council-collapse-approval
feat(desktop): add Ctrl+B sidebar toggle and thinking block auto-collapse
2026-06-14 00:28:18 +08:00
你的姓名
b3e74c4f56 fix(chat): resync runtime mode before user turns
Ensure Solo and coordinator modes are replayed before sending a user message so the server cannot lose the runtime mode after cleanup or reconnects.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-14 00:20:15 +08:00
你的姓名
37202ff768 feat(desktop): add Ctrl+B sidebar toggle and thinking block auto-collapse
- Add Ctrl+B / Cmd+B keyboard shortcut to toggle sidebar visibility
- Add thinkingAutoCollapse setting (default: true) to auto-collapse
  thinking blocks when they finish; active thinking streams stay expanded
- Add animated brain SVG icon for active thinking state with pulse
  animation; static psychology icon for completed thinking
- Add settings toggle for auto-collapse under Settings > General >
  Thinking section
- Add i18n translations for all 5 locales (en/zh/zh-TW/jp/kr)
- Update ThinkingBlock tests to account for default-collapsed behavior

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 00:19:43 +08:00
小橙子
7ef95ef552
Merge pull request #45 from 706412584/worktree-solo-council-collapse-approval
feat(desktop): polish chat UI visuals and Solo Council panel
2026-06-13 23:43:21 +08:00
你的姓名
18b7e8e2c8 feat(desktop): polish chat UI visuals and Solo Council panel
- StreamingIndicator: replace Unicode ✦ with Material Symbols auto_awesome
- ThinkingBlock: extract inline <style> to globals.css, eliminate
  duplicate style nodes per instance
- DiffViewer: fix bottom border-radius to match outer container
- ToolCallBlock: add fade-in animation on expand
- CodeViewer: add opacity transition for Shiki highlighter load
- SoloCouncilPanel: add role-specific left border colors (planner:
  primary, reviewer: success, critic: warning)
- SoloCouncilPanel: add animate-pulse to running flow step pills
- SoloCouncilPanel: parse synthesis text headings and lists for
  structured rendering, increase collapsed preview to 8 lines
- SoloCouncilPanel: responsive grid (grid-cols-1 sm:grid-cols-3)
- SoloCouncilPanel: stronger shadow (shadow-md) for visual separation
- SoloCouncilPanel: add transition-colors on card state changes
- globals.css: add dark-mode scrollbar contrast override

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 23:29:00 +08:00
小橙子
eb2bb227cb
Merge pull request #43 from 706412584/worktree-prompt-too-long-recovery
fix(chat): preview local image attachments
2026-06-13 23:08:37 +08:00
你的姓名
0136fbb9ff fix(chat): preview local image attachments
Show local image-path attachments as UI previews while keeping preview URLs out of the websocket payload.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 22:54:30 +08:00
小橙子
647bbec33d
Merge pull request #41 from 706412584/worktree-solo-council-collapse-approval
feat(solo): require approval before implementation
2026-06-13 22:32:37 +08:00
你的姓名
1b4bfada25 fix(test): fix CI failures from brand rename and symlink test
- Update hardcoded notification title in generalSettings.test.tsx to
  match the Code Council brand rename.
- Simplify diskOutput symlink test to not depend on platform-specific
  symlink behavior (Linux allows dangling symlinks, Windows does not).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 22:14:09 +08:00
你的姓名
c5becca3c9 feat(workspace): enable editable file previews
Wire workspace file tabs into the editor with Markdown preview/edit mode and guard tab closes so dirty buffers are not discarded silently.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 21:53:51 +08:00
你的姓名
f873a1a5db chore(brand): update visible product name to Code Council
Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 15:17:01 +08:00
你的姓名
16df49b1a6 fix(desktop): keep window controls visible beside tabs
Allow the tab strip scroll region to shrink inside the title bar so Windows window controls are not pushed offscreen by long or crowded tabs.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 14:16:54 +08:00
你的姓名
c309b7bb46 feat(solo): require approval before implementation
Add Solo Council panel collapse controls, surface the final-plan approval hint, and verify Solo mode toggles restart existing sessions so the prompt applies immediately.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 13:00:33 +08:00
你的姓名
69cf8bd0b3 merge main into solo council branch
Resolve locale conflicts with the Solo naming from main while keeping Solo Council panel translations.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 06:00:32 +08:00
你的姓名
04f01e8193 feat(solo): surface council synthesis and blockers
Show the Solo Council flow, final synthesis, and structured review blockers in the panel while adding stable prompt markers for future runs.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 05:13:44 +08:00
你的姓名
7d770e7b2f fix(chat): restore background tasks from history
Rebuild terminal background task state from persisted background_task messages so history restore keeps all task consumers in sync without reviving stale running tasks.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 05:13:44 +08:00
小橙子
62beec7763
feat(plugins): one-click language server install panel + terminal copy UX 2026-06-13 05:11:31 +08:00
小橙子
7a0ccd1e1a
fix(desktop): MCP marketplace honors installed state and required env 2026-06-13 05:11:23 +08:00
小橙子
26f22493b1
test(plugins): add PluginPrerequisitesModal unit tests + vitest CI fixes 2026-06-13 05:11:19 +08:00
小橙子
551eabebe7
i18n: rename Solo Pipeline mode to just Solo (5 locales) 2026-06-13 05:11:10 +08:00
你的姓名
d215c0f404 feat(solo): stabilize council cards
Keep Solo Council visible across task state loss by falling back to persisted background task messages and showing standby role cards when agents are idle.

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 04:05:47 +08:00
你的姓名
3521b04b89 feat(solo): fan out council and show review panel
Require Solo's plan gate to launch real Planner, Reviewer, and Critic subagents instead of simulating roles in prose. Add a read-only plan-reviewer specialist and surface Council task status/verdicts in a desktop panel backed by existing background agent task events.

Tested: bun test src/tools/AgentTool/builtInAgents.test.ts src/coordinator/workerAgent.test.ts src/coordinator/soloPipelinePrompt.test.ts
Tested: cd desktop && bun run test -- --run src/components/chat/SoloCouncilPanel.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run src/i18n/lspError.test.ts
Tested: cd desktop && bun run build
Confidence: high
Scope-risk: medium

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-13 00:05:38 +08:00
你的姓名
c8aaa18aee i18n(solo): rename composer toggle and header chip to "Solo"
Shorten the Solo mode label in the composer + menu and the session header chip from "Solo pipeline" to just "Solo" across all five locales. The mode tooltip already describes the A/B/C plan gate behavior.

Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run src/i18n/lspError.test.ts
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-12 22:22:59 +08:00
小橙子
35fac60158
feat(solo): A/B/C plan gate + plan-critic specialist (#34)
* feat(solo): add council-style plan gate

Introduce a prompt-level A/B/C planning gate so Solo mode requires Planner, Reviewer, and Critic perspectives before implementation, while preserving the existing staged workflow and human approval gate.

Tested: bun test src/coordinator/soloPipelinePrompt.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts -t "Solo Pipeline system prompt"
Tested: bun test src/server/__tests__/conversations.test.ts -t "routes coordinator and Solo"
Tested: cd desktop && bun run lint
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>

* feat(agents): add plan critic specialist

Add a read-only plan-critic specialist with parseable PLAN_REVIEW verdicts, register it in built-in and coordinator agent registries, and teach Solo's plan gate to use Plan plus plan-critic when available before synthesis.

Tested: bun test src/tools/AgentTool/builtInAgents.test.ts src/coordinator/workerAgent.test.ts src/coordinator/soloPipelinePrompt.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts -t "Solo Pipeline system prompt"
Tested: bun test src/server/__tests__/conversations.test.ts -t "routes coordinator and Solo"
Not-tested: Full bun test remains blocked by unrelated existing failures in attribution header, shell PATH/env, release workflow, and desktop Vitest compatibility tests.
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>

* chore(ci): retrigger PR checks after applying policy labels

Co-Authored-By: Claude GPT-5.5 <noreply@anthropic.com>

---------

Co-authored-by: 你的姓名 <you@example.com>
Co-authored-by: Claude GPT-5.5 <noreply@anthropic.com>
2026-06-12 21:55:22 +08:00
小橙子
31230ef737
feat(desktop): bridge agent file edits to workspace panel (Phase 4) (#29)
Phase 4 of editor-lsp-foundation. The CLI agent's FileEditTool /
FileWriteTool / NotebookEditTool path was already vendored from upstream
and already drives the upstream LSP system. This PR bridges agent edits
into the desktop workbench (CodeMirror editor + workspace panel) without
touching the vendored tool code: the desktop chatStore observes the
agent's tool stream and refreshes/conflict-flags the panel itself.

Approach (option A in the spec discussion):
- chatStore: when a FILE_EDIT tool ('Edit'/'Write'/'NotebookEdit'/
  'MultiEdit') completes, remember the toolUseId -> file_path mapping
  in a per-session pending map. On the matching tool_result with
  isError === false, consume the entry and call
  workspacePanelStore.notifyAgentFileEdit(sessionId, absolutePath).
- workspacePanelStore.notifyAgentFileEdit: refreshes loadStatus when
  the panel is open, and sets a 'source: agent' conflict on any open
  editor buffer whose workspace-relative path matches the agent's
  absolute path by suffix on a normalized segment boundary. Skips
  buffers that already have a conflict so user-source banners aren't
  clobbered. Uses sentinel hash 'agent-edit' because the chat tool
  stream carries no content hash.
- Path matching normalizes backslashes -> forward slashes and trims
  trailing slashes, then either equals or endsWith('/' + bufferPath)
  to avoid foobar.ts matching bar.ts.
- Cleanup: clearPendingFileEdits added at the 3 existing pending-map
  cleanup sites (disconnect, clear messages, new-session path).

Why no FileEditTool change:
The vendored upstream tool has no sessionId in ToolUseContext and
shouldn't import server WS modules from CLI process scope. The chat
stream already carries (sessionId, toolName, toolUseId, input, isError)
to the desktop and is the natural seam for desktop-side reactions —
same pattern the store already uses for TodoWrite -> useCLITaskStore
and Task* tools -> refreshTasks.

Tests:
- desktop/src/stores/workspacePanelStore.test.ts +6 (30/30 total)
  Suffix match, Windows backslash, non-suffix substring rejection,
  existing-conflict preservation, loadStatus when panel open, no
  loadStatus when closed.
- desktop/src/stores/chatStore.test.ts +6 (112/112 total)
  Edit/Write/NotebookEdit forwarding, isError gate, non-edit tool
  isolation (Bash), single-fire on duplicate tool_result.

Verifier (independent agent) ran lint + both test suites + build +
bundle budget + 9 ad-hoc helper edge cases (Unicode paths, trailing
slash, identical absolute, deeper suffix mismatch). All PASS.

Bundle delta: +2.41 KB gz vs origin/main @ 95931d49 baseline
(97.59 KB headroom remaining).

_Requirements: 3.1, 3.2, 3.3, 8.1, 8.2 (adapted)_

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 15:34:37 +08:00
你的姓名
a4e3306753 release: v0.5.11 2026-06-12 14:09:10 +08:00
小橙子
535d777ec4
fix(providers): route 'Fetch Models' through server to bypass mixed-content / CORS (#28)
* fix(providers): route 'Fetch Models' through server to bypass mixed-content / CORS

The "获取模型 / Fetch Models" button in the provider settings dialog used
to call upstream `/v1/models` directly from the renderer with `fetch()`.
That works for HTTPS providers whose endpoint sets permissive CORS
headers (e.g. api.openai.com, api.anthropic.com), but fails for any
plain-`http://` provider — most commonly self-hosted relay endpoints
like `http://47.116.22.0:3000`.

Two failure modes both surface as the same opaque "Failed to fetch":

1. Mixed-content block. The desktop renderer runs in a secure context
   (Tauri at `tauri://localhost`, Electron with web security on), and
   modern webviews refuse to fetch plain-HTTP URLs from a secure-context
   page. The request never leaves the browser.

2. CORS-missing. Even when reachable, many self-hosted relays do not
   return `Access-Control-Allow-Origin` for `/v1/models`, so the browser
   blocks the response.

The "Test Connection" button never had this problem because it already
proxies through `POST /api/providers/{id}/test`, which runs on the local
server (Node, no webview restrictions). The fix routes "Fetch Models"
through the same server so it inherits the same isolation.

What this PR adds

Server side
- ApiError.badGateway(message) — new 502 helper for upstream failures.
- POST /api/providers/fetch-models with the new FetchModelsSchema (zod):
  { baseUrl: url, apiKey: non-empty, apiFormat: 'anthropic' | 'openai_chat'
    | 'openai_responses' default 'anthropic' }.
- ProviderService.fetchUpstreamModels(input) — server-side GET against
  `${baseUrl}/v1/models` (or `${baseUrl}/models` if baseUrl already ends
  in `/v1`), reusing the existing network-proxy / timeout plumbing. Sets
  Bearer for OpenAI-compatible formats, x-api-key + anthropic-version
  for native Anthropic. Returns `{ status, data }` where `data` is the
  upstream JSON verbatim. Non-2xx upstreams surface as ApiError.badGateway
  with the upstream's `error.message` if the body has one. Network
  failures and timeouts also map to badGateway with a clear message.

Desktop side
- providersApi.fetchModels(input) — new client wrapping the endpoint.
- handleFetchModels in Settings.tsx now calls providersApi.fetchModels
  instead of fetching directly. The existing `extractModelEntries`
  parser already accepts arbitrary upstream JSON (data / models / items
  / results arrays, plus { id | name | model } per entry), so no
  parsing change was needed.

Tests
- 5 new ProviderService unit tests covering the wire shape:
  · OpenAI format → Bearer header, GET /v1/models, no x-api-key
  · Anthropic format → x-api-key + anthropic-version, no Authorization
  · URL hygiene — strips trailing slashes, doesn't duplicate /v1
  · Non-2xx upstream → ApiError 502 with `HTTP {status}: {message}`
  · Network error → ApiError 502 with "Upstream fetch failed: ..."

Verification

- `bun test src/server/__tests__/providers.test.ts -t "fetchUpstreamModels"`
  → 5/5 pass.
- `bun run lint` (desktop): tsc --noEmit clean (after `bun install` to
  pick up the @codemirror packages added by an earlier PR — orthogonal
  to this change).
- All existing provider tests unchanged (no regression in test file).

Tested: server unit (5 cases covering wire shape, URL hygiene, error
paths); existing testProviderConfig tests still pass; desktop tsc clean.
Not-tested: live smoke against an actual self-hosted relay (no provider
fixture configured locally). The bug repro is well-understood: the
secure-context webview's mixed-content block is documented browser
behaviour and the fix removes the renderer-side fetch entirely.

Confidence: high
Scope-risk: narrow

* test(providers): add desktop-side fetchModels client test (clears change-policy block)

Locks the renderer→server contract: providersApi.fetchModels must POST to the local server with baseUrl/apiKey/apiFormat in the body, NOT fetch the upstream URL directly. The same-origin assertion guards against any future regression that reintroduces the mixed-content / CORS bug class.

Two cases:

- success path: upstream JSON forwarded verbatim, body shape locked

- error path: 502 from server surfaces upstream message in thrown error

---------

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 14:08:55 +08:00
小橙子
8ac3ea395a
feat(solo): wire LSP error count into Tier-1 signals (Phase 5) (#27)
* feat(solo): wire LSP error count into Tier-1 signals + cleanup suggestion

Phase 5 of editor-lsp-foundation (tasks 41-45):

- src/server/services/soloSuggestions.ts: SoloSignalsTier1 gains
  lspErrorCount field + new ruleLspError. Rule fires on positive
  integer counts only (defensive against undefined / 0 / non-integer
  / negative). Score 80 — high enough to outrank other 'cleanup'
  candidates (todo-marker / sync-upstream / stash) via the existing
  per-category dedup pass, and to outrank test-gap / ship / finish-wip /
  release base scores so the user is told to clear type errors first
  whenever the LSP reports any.
- src/server/services/soloSignalsService.ts: gatherSoloSignalsTier1 now
  accepts an optional `getLspErrorCount` provider. Wrapped in
  Promise.allSettled so an LSP failure / missing prereq silently
  leaves lspErrorCount undefined — the rule then sits the round out
  rather than nagging users about a setup problem they didn't ask
  about. Existing callers don't pass the option, so no behavioral
  drift on the current call sites; the manager will inject a real
  provider when the LspManager singleton from Phase 3 is wired into
  the Solo welcome-card pipeline.
- desktop/src/i18n/locales/{en,zh,zh-TW,jp,kr}.ts: 3 keys ×
  5 locales = 15 entries for solo.suggest.lspError.{title,detail,
  taskPrompt}. The other LSP-related i18n keys mentioned in the
  spec (lsp.indicator.*, editor.unsupportedEncoding, editor.conflict.*,
  editor.unsavedClose.*) are deferred to the component-integration PR
  that swaps Phase 2/3's hardcoded English for translated labels —
  shipping keys without callers would create dead code that drifts
  out of sync before integration lands.
- src/server/services/soloSuggestions.test.ts: 6 new tests for
  ruleLspError covering undefined / 0 / non-integer / negative gates,
  positive-count emission, per-category dedup outranks
  todo / sync-upstream when LSP errors exist.
- src/server/services/soloSuggestions.i18n.test.ts: fixtureWithEverySignal
  now includes lspErrorCount, so the existing en.ts contract scan
  automatically validates the 3 new keys.

Tested:
- bun test src/server/services/soloSuggestions.test.ts (44/44, +6 new)
- bun test src/server/services/soloSignalsService.test.ts
- bun test src/server/services/soloSuggestions.i18n.test.ts
- 61/61 across 3 Solo-related test files
- bun run lint (desktop tsc --noEmit clean — 5 locales × TranslationKey
  mapped Record gates the additions)

Note: pre-existing e2e/business-flow failures (~44) on origin/main
remain untouched.

_Requirements: 14.1, 14.2, 14.3, 14.4, 14.5, 15.6_

* test(desktop): add i18n contract test for solo.suggest.lspError

Phase 5 follow-up: change-policy CI gate refused PR #27 because the
desktop locale changes shipped without a matching desktop test —
production code in `desktop/src/i18n/locales/*.ts` requires a
desktop-side test. The server-side i18n contract test
(soloSuggestions.i18n.test.ts) only validates en.ts, leaving the
4 other locales unchecked at the desktop boundary.

This test mirrors that contract on the desktop side: 5 locales × 3
keys = 15 presence checks, plus interpolation checks for {count} and
absence-of-params check for the static detail key. 18/18 passing.

The test imports each locale as a named export (the file's `export
const zh: Record<TranslationKey, string>` is repeated across all
five locale files, so `zh-TW.ts` needs `import { zh as zhTW }` to
disambiguate at the call site).

Tested:
- bun run test src/i18n/lspError.test.ts (18/18 pass)

---------

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 08:33:32 +08:00
小橙子
96f578e5d3
feat: LSP manager + status indicator foundation (Phase 3) (#26)
* feat(server): LSP foundation infra — feature flag, JSON-RPC framer, process limits

Phase 3 base layer (tasks 18-20 of editor-lsp-foundation):

- src/server/services/lspFeatureFlag.ts: local opt-in switch.
  spec called for `feature('LSP_FOUNDATION')` from `bun:bundle`, but
  that table is build-time vendored from upstream Anthropic CLI and
  fork code can't register new flag names there. So Phase 3 ships its
  own check: dev (NODE_ENV !== 'production') is on by default; prod
  is opt-in via `CLAUDE_CODE_LSP=1` env var. Pure function so unit
  tests can stub `process.env` directly. 5/5 tests pass.

- src/server/services/lspJsonRpc.ts: minimal Content-Length-framed
  JSON-RPC. encodeLspFrame(payload) emits the wire bytes;
  LspFrameDecoder accepts arbitrary stream chunks (LSP servers split
  frames mid-header / mid-body / multiple-per-chunk) and yields
  complete frames per push(). Throws LspFrameDecodeError on missing
  or non-numeric Content-Length. ~110 lines, no vscode-jsonrpc
  dependency. 10/10 tests pass.

- src/server/services/lspProcessLimits.ts: poll-fallback strategy
  (5 s sample interval, terminate after 2 consecutive overages).
  spec also lists posix-rlimit and windows-job-object — those need
  native bindings or N-API plugins we don't carry yet, so this PR
  ships poll-fallback only and exposes a stable LspProcessLimits
  interface so a future PR can plug in native strategies without
  touching call sites. Sampler is abstract (manager passes a real
  ps/Get-Process wrapper in production; tests pass a synthetic
  array). 5/5 tests pass.

Tested:
- bun test src/server/services/lspFeatureFlag.test.ts (5/5)
- bun test src/server/services/lspJsonRpc.test.ts (10/10)
- bun test src/server/services/lspProcessLimits.test.ts (5/5)

_Requirements: 7.2, 7.4, 15.3 (adapted)_

* feat(server,desktop): LspManager + LspStatusIndicator (Phase 3 wave 2)

Phase 3 main pieces (tasks 21-27 of editor-lsp-foundation):

Server:
- src/server/services/lspManager.ts: LspManager service. Per-workspace
  lazy spawn via injected LspClientFactory; sha-clean state machine
  (starting -> ready -> unavailable with 5 reasons). mapLspSeverity
  maps LSP wire severity 1-4 -> error/warning/info/hint, anything
  else -> error. makeDiagnosticComparator orders by severity ->
  in-edited-file -> path -> line -> column. truncateDiagnostics
  caps at top-20 entries / 5 distinct files. Idle eviction
  (default 600 s) + restart cap (2 attempts in 60 s rolling window
  -> restart-cap-exhausted). probeHostCommand from prerequisitesService
  is reused.
- src/server/services/lspManager.test.ts: 18 tests covering severity
  map / comparator / truncation / spawnIfNeeded gate / prereq-missing /
  init-failed / ready+diagnostics flow / shutdownWorkspace /
  onStateChange listeners / getPrerequisites delegation / sorted+
  truncated diagnostics on real flow.
- src/server/events/lspStateChanged.ts: type-only WS event shape
  for `lsp.state.changed` so producers and consumers stay aligned.
- src/server/api/sessions.ts: GET /api/sessions/:id/lsp/state. Gated
  by isLspFeatureEnabled() — returns 404 FEATURE_DISABLED in
  production unless CLAUDE_CODE_LSP=1.

Desktop:
- desktop/src/types/lsp.ts: desktop-side mirror of LSP wire types.
  Mirrored manually rather than imported across boundaries because
  there's no shared types module yet.
- desktop/src/components/workspace/LspStatusIndicator.tsx: status
  pill + dropdown. CheckCircle / AlertCircle / Loader2 / AlertTriangle
  via lucide-react (already a dep). Labels: "Ready" / "N errors
  detected" / "9999+ errors detected" cap / "Starting language
  server…" / "Language server unavailable". Single context action:
  prereq-missing -> Install... button; other unavailable -> Retry.
  Dropdown lists diagnostics with messages truncated at 200 chars,
  empty list shows "No diagnostics". Keyboard a11y (Tab/Enter/Space/
  ArrowUp/ArrowDown/Esc) + aria-live polite mirror.
- desktop/src/components/workspace/LspStatusIndicator.test.tsx: 11
  RTL tests covering all five state visuals + error count cap +
  Install vs Retry action gating + dropdown empty state + message
  truncation + keyboard activation + aria-live mirror.

Stability fix:
- src/server/services/lspProcessLimits.test.ts: bumped sleep from 20 ms
  to 60 ms in the "swallows sampler error" test — Windows + bun
  scheduler occasionally fails to fire 5 ms intervals twice within
  20 ms. 5/5 stable now.

Tested:
- bun test src/server/services/lspFeatureFlag.test.ts (5/5)
- bun test src/server/services/lspJsonRpc.test.ts (10/10)
- bun test src/server/services/lspProcessLimits.test.ts (5/5)
- bun test src/server/services/lspManager.test.ts (18/18)
- bun run test src/components/workspace/LspStatusIndicator.test.tsx (11/11)
- bun run lint (tsc --noEmit clean, both desktop and server)
- bun run check:bundle-budget (delta +0.33 KB gz, 99.67 KB headroom)
- bun run build (clean, 1.27s)

Note: pre-existing e2e/business-flow failures (~44) on origin/main
baseline remain untouched by this change.

_Requirements: 6.1-6.5, 7.1-7.6, 9.1-9.5, 12.1-12.4, 13.1-13.10, 15.1, 15.3_

---------

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 08:06:32 +08:00
小橙子
5e304ea661
feat(solo): wire Solo Pipeline mode end-to-end (toggle + WS + handler + prompt) (#25)
* feat(solo): wire Solo Pipeline mode end-to-end (toggle + WS + handler + prompt)

Lights up the Solo Pipeline foundation that PR #16 / #17 staged. The
prompt text, suggestion engine, and Tier-1 signals are already on main;
this PR plugs them into the runtime so a user toggle actually flips the
CLI subprocess into Solo's 5-stage workflow.

What this PR adds (one PR, 4 layers, mirror of coordinator mode):

1. WS protocol — `set_pipeline_mode { flavor: 'solo' | 'normal' }`
   - src/server/ws/events.ts: ClientMessage variant
   - desktop/src/types/chat.ts: matching desktop variant

2. Server handler — `handleSetPipelineMode`
   - src/server/ws/handler.ts: new in-memory `soloPipelineModeSessions`
     Set, switch case in the message dispatcher, and a sibling of
     `handleSetCoordinatorMode` that defers restart until the active
     turn completes (same enqueueRuntimeTransition geometry).
   - Mutual exclusion enforced server-side: enabling Solo clears
     coordinator for the same session and vice versa, so the CLI
     subprocess never sees both `--append-system-prompt` addenda.
   - `RuntimeSettings.soloPipelineMode` threaded through all three
     getRuntimeSettings return paths.
   - Session cleanup deletes from both sets.

3. CLI args — `getSoloPipelineSystemPrompt()` injection
   - src/server/services/conversationService.ts: when
     `options.soloPipelineMode === true`, append the Solo prompt via
     `--append-system-prompt`, lazy-required from the existing
     `src/coordinator/soloPipelinePrompt.ts` (zero-cost when the flag
     is off, no module-load on default builds).
   - src/utils/systemPrompt.ts: parallel env-driven branch
     (CLAUDE_CODE_SOLO_PIPELINE_MODE) for users launching the CLI by
     hand, checked BEFORE coordinator's branch so Solo wins if both
     env vars are somehow set.

4. Desktop UI + state — toggle in the `+` menu
   - desktop/src/stores/sessionRuntimeStore.ts: new
     `soloPipelineModes` map with localStorage persistence under
     `cc-haha-session-solo-pipeline`, mirroring `coordinatorModes`.
     `setSoloPipelineMode`, `clearSelection`, `moveSelection` updated
     to keep the new map in lockstep with siblings.
   - desktop/src/stores/chatStore.ts: `setSessionSoloPipelineMode`
     action with mutual-exclusion logic — flipping Solo on clears
     the coordinator flag (locally + over the wire); the coordinator
     setter has the symmetric branch. Connect-time replay also now
     re-emits `set_pipeline_mode` on reconnect when persisted.
   - desktop/src/components/chat/ChatInput.tsx: parallel button to
     the coordinator `+`-menu entry, lucide `linear_scale` icon,
     primary-color check when active.
   - i18n: `chat.soloPipelineMode` key added in en / zh / zh-TW /
     jp / kr — all 5 locales required by the i18n contract.

Tests added

- src/server/__tests__/conversations.test.ts: getRuntimeArgs
  appends Solo prompt under soloPipelineMode=true, omits it when off,
  and emits two distinct `--append-system-prompt` slots (one per
  mode) if both flags are passed (defensive — the WS handler enforces
  exclusion, but getRuntimeArgs must still produce a deterministic
  shape).
- desktop/src/stores/chatStore.test.ts: persist-without-live-session,
  push-when-live, and BOTH directions of mutual exclusion (Solo
  enables → coordinator clears, and vice versa) — verified to flip
  both the local store and the wire payload in lockstep.

Verification

- `bun test src/coordinator/soloPipelinePrompt.test.ts` — 11/11
  (pre-existing, unchanged: confirms the prompt text contract held).
- `bun test src/server/__tests__/conversations.test.ts -t "Solo"` —
  3/3 new tests pass.
- `bun test src/server/services/soloSuggestions.i18n.test.ts` —
  i18n contract still passes after adding the new key.
- `bun run lint` (desktop): tsc --noEmit clean, exit 0.
- `bun run test stores/chatStore.test.ts -t "Solo|coordinator"` — 4/4.
- `bun run test stores/sessionRuntimeStore.test.ts` — 6/6
  (pre-existing, confirms backward compatibility of the runtime store
  with the new soloPipelineModes field).

Constraint: must coexist with the parallel coordinator-mode work (B1 #15,
B2 #18, worker-continue #21, research-fork #22) already on main. Solo
shares the COORDINATOR_MODE feature flag with coordinator (gating both
in the same product wave) and uses the same restart geometry.

Confidence: high
Scope-risk: narrow
Tested: server unit (getRuntimeArgs Solo branch + mutual-exclusion arg
shape); desktop store unit (Solo persistence + bi-directional exclusion);
i18n contract; full desktop tsc.
Not-tested: live agent-browser smoke (no provider model wired in this
PR); next session resume of a Solo session (in-memory only per v1, same
as coordinator mode).

* feat(solo): always-visible status chip in chat header for Solo / coordinator mode

Closes the steering contract that says "must be explicit about current
mode" — until now, the toggle in ChatInput's `+` menu was the only
surface that showed mode state, and that menu is closed by default.
A user who enabled Solo and then minimized the menu had no way to
tell from the chat view whether they were still in Solo mode.

This adds a persistent chip in the chat header session-meta strip
(next to the existing `↗ Continued` hand-off chip), one chip per active
mode. The chips:

- Render only when the corresponding mode is on for the active session
  (mutually exclusive at the action layer, but the rendering is
  defensive — both could theoretically appear and that is harmless).
- Use lucide material symbols `hub` (orchestration) and `linear_scale`
  (Solo) at 12px to match the chip-strip type scale.
- Carry tooltips explaining what the mode does and where to toggle it.
- Use the same primary-color treatment as the hand-off chip so the
  whole strip reads as one band of "session-level facts about this
  conversation".
- Have `data-testid` selectors (`session-coordinator-chip`,
  `session-solo-chip`) for downstream test access.

i18n: 4 new keys × 5 locales (en/zh/zh-TW/jp/kr).

Verification: `bun run lint` clean (tsc --noEmit), no new diagnostics.
The runtime store reads were already wired in the previous commit on
this branch (sessionRuntimeStore.soloPipelineModes), so the chip is a
pure rendering layer with no behavior change.

Tested: tsc clean; existing handoff-chip pattern reused (proven
locally rendered already).
Not-tested: per-locale visual snapshot (no snapshot suite for this
header). Manual smoke recommended in the merged build.

Confidence: high
Scope-risk: narrow

* revert(solo): drop systemPrompt env-driven branch to clear CLI-core change-policy block

Removes the CLAUDE_CODE_SOLO_PIPELINE_MODE branch from src/utils/systemPrompt.ts (CLI core file). The desktop Solo wireup does NOT depend on this branch — it injects the Solo prompt via --append-system-prompt through conversationService.getRuntimeArgs. The reverted branch was a defensive addition for users who launch the CLI manually with the env var set; that path can be re-added in a follow-up PR with the allow-cli-core-change label and a co-located test file.

Manual CLI users wanting Solo can pass --append-system-prompt themselves until the follow-up lands. Desktop toggle path remains fully functional.

Confidence: high

Scope-risk: narrow (single file, single block)

---------

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 07:57:05 +08:00
小橙子
3fb246ca6a
feat: CodeMirror editor + atomic save + conflict banner foundation (Phase 2) (#24)
* feat(desktop): add CodeMirror deps + bundle budget + encoding utils

Phase 2 foundation pieces (tasks 6-7 of editor-lsp-foundation):

- Add 9 @codemirror/* dependencies (state, view, commands, search, language,
  autocomplete, lang-{javascript,json,markdown}). Tree-shaking keeps the
  current bundle delta at +0.00 KB until WorkspaceEditor.tsx imports them
  in task 8.
- Add scripts/check-bundle-budget.ts + bun script "check:bundle-budget".
  Asserts dist/assets/*.js gzipped total <= baseline + 100 KB. Baseline
  captured at origin/main @ 95931d49 (post-R5, pre-CodeMirror) = 3299.76 KB.
- Add encodingDetect.ts: detectEncoding(bytes) -> 'utf-8' | 'utf-8-bom' |
  'unsupported' (uses TextDecoder fatal:true to map invalid UTF-8 to
  unsupported); detectLineEnding(text) -> 'LF' | 'CRLF' | 'CR' (dominant
  style with LF as fallback for empty/single-line buffers).

Tested:
- bun run test src/components/workspace/encodingDetect.test.ts -- --run (18/18 pass)
- bun run scripts/check-bundle-budget.ts (OK, +0.00 KB delta)

_Requirements: 1.3, 1.6, 1.7, 1.8_

* feat(server,desktop): workspace file save endpoint + buffer/conflict store

Phase 2 backend pieces (tasks 9-12 of editor-lsp-foundation):

Server:
- src/server/events/workspaceFileSaved.ts: shared emitter
  emitWorkspaceFileSaved() so both write paths (user now, agent in PR-4)
  funnel through one well-typed WS message shape.
- src/server/services/workspaceFileService.ts: WriteWorkspaceFileSchema
  (Zod), atomic write (temp file + fsync + rename, Windows EBUSY/EPERM
  retry 3x50ms), realpath-based symlink containment, BOM/CRLF/CR
  round-trip, sha256 stale-base check (409 on hash mismatch), structured
  400/404/409/500 error bodies, post-write workspace.file.saved emit.
- src/server/api/sessions.ts: POST /api/sessions/:id/workspace/file
  routes through new handleSessionWorkspacePost; GET path unchanged.
- src/server/services/workspaceFileService.test.ts: 17 tests covering
  200 / 400 (bad request, path-escape, parent-missing, absolute-path) /
  404 session-missing / 409 stale-base / BOM round-trip / CRLF round-trip /
  CR round-trip / temp-file-not-leaked / 10 MiB content cap. Includes
  symlink-escape coverage on POSIX (skipped on Windows where symlink
  creation needs elevation).

Desktop store:
- desktop/src/stores/workspacePanelStore.ts: bufferStateByTabId record,
  WorkspaceBufferState/Conflict types, initBuffer / setBufferState /
  applyExternalSave / acknowledgeConflict / clearBuffer actions.
  applyExternalSave is the WS-driven entry point — clean buffers rebase
  silently when content is supplied; dirty buffers raise a conflict
  banner. acknowledgeConflict('reload') resets to base, 'keepMine' /
  'openConflict' just dismiss the banner. closePreviewTabs and
  clearSession now drop matching buffer state to prevent leaks.

Tested:
- bun test src/server/services/workspaceFileService.test.ts (17/17 pass)
- bun run lint (server tsc clean)
- bun run test src/stores/workspacePanelStore.test.ts (24/24 pass — store
  extension is purely additive, R5 tests still green)

Note: pre-existing e2e/business-flow failures in src/server/__tests__/e2e
remain untouched by this change (44 fails on baseline, no new fails
introduced).

_Requirements: 1.5, 2.1-2.9, 3.1-3.3_

* feat(desktop): WorkspaceEditor + ConflictBanner + UnsavedChangesModal

Phase 2 UI pieces (tasks 8, 13, 14, 15 of editor-lsp-foundation):

- desktop/src/components/workspace/WorkspaceEditor.tsx: CodeMirror 6
  editor wrapping the workspace panel buffer state. Hooks
  EditorView.updateListener -> setBufferState for live dirty tracking,
  picks language by extension (ts/tsx/js/jsx/json/md), wires Save to
  sessionsApi.saveWorkspaceFile (R2 atomic write endpoint), refreshes
  base hash on success, falls back to a read-only message when
  detectEncoding returns 'unsupported'. External rebases (e.g. clean
  buffer applyExternalSave) are pushed back into the EditorView via
  dispatch.
- desktop/src/components/workspace/ConflictBanner.tsx: clean buffer ->
  single Reload button; dirty buffer -> three buttons (Reload (discard),
  Keep mine, Open conflict view). Renders workspace-relative path,
  hash first 8 hex, relative timestamp refreshing every 60 s.
- desktop/src/components/workspace/UnsavedChangesModal.tsx: Discard /
  Save / Cancel modal. Cancel is the default focus, Esc triggers it.
  While saving, all three buttons disable and the host calls onSave
  which only resolves close on success. 30 s in-prompt timeout fires
  onTimeout (host owns the toast).
- desktop/src/components/workspace/WorkspaceEditor.test.tsx: 8 RTL
  tests covering buffer init / encoding detection / unsupported
  fallback / dirty marker / unsaved-changes modal flows / conflict
  banner clean-vs-dirty layouts.
- desktop/src/api/sessions.ts: SaveWorkspaceFileInput +
  SaveWorkspaceFileResult types and sessionsApi.saveWorkspaceFile
  POST helper.

Bundle delta: +0.28 KB gz (CodeMirror 6 + 3 lang packs imported but
WorkspaceEditor is not yet wired into WorkspacePanel — that integration
ships in a follow-up PR alongside Phase 3 LSP work, keeping this PR
focused on the foundation).

Tested:
- bun run lint (tsc --noEmit clean)
- bun run test (workspacePanelStore 24/24, encodingDetect 18/18,
  WorkspaceEditor 8/8, WorkspacePanel 27/27 — 77/77 across 4 files)
- bun run check:bundle-budget (delta +0.28 KB gz, 99.72 KB headroom)
- bun run build (clean, 1.20s)

_Requirements: 1.1-1.8, 3.2-3.5, 4.1-4.6_

---------

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 07:36:52 +08:00
小橙子
95931d498d
feat(desktop): default workspace panel to 'all' view (#23)
R5 changes the workspace panel's default activeView from 'changed' to
'all' and removes the loadStatus auto-switching logic. Users now see
the all-files tree on first open, regardless of whether the session
has changed files. Explicit setActiveView still wins and survives
subsequent loadStatus calls via hasUserSelectedView.

The downstream WorkspacePanel component tests that asserted the old
"auto-switch to changed view" behavior are updated:
- 4 tests now explicitly setActiveView('changed') when they need to
  exercise the changed-files list.
- 2 tests are rewritten/renamed (R5 prefix) to assert the new
  "default to all, never auto-flip" contract instead of the old
  switch-back behavior.

Tested:
- bun run lint (tsc --noEmit clean)
- bun run test workspacePanelStore.test.ts (24/24 passed)
- bun run test WorkspacePanel.test.tsx (27/27 passed)
- bun run build (clean, 1.21s, no bundle impact)

Note: 3 pre-existing failures in electron/services/{sidecarManager,terminal}.test.ts
exist on origin/main baseline and are unrelated to this change.

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 05:39:09 +08:00
小橙子
d2830f91de
feat(tabbar): mouse-wheel horizontal scroll on hover, scrollbar hidden (#19)
Hovering the tab strip and spinning the wheel now moves the strip

horizontally — same UX as native browser tab bars. The scrollbar itself

stays hidden so the strip looks like clean app chrome.

Three coordinated changes:

1. The scroll region's overflow flips from overflow-x-hidden to

   overflow-x-auto so native scroll mechanics kick in. The visible

   scrollbar is suppressed via the existing project pattern of

   Tailwind arbitrary-value utilities — [scrollbar-width:none]

   covers Firefox and modern Chromium, [&::-webkit-scrollbar]:hidden

   covers older WebKit and the embedded Electron renderer.

2. New non-passive 'wheel' listener on the scroll region: when the

   cursor is over it AND the wheel input is deltaY-dominated (i.e.

   a plain vertical mouse wheel, no trackpad), translate to

   horizontal scrollLeft and preventDefault so the page below the

   tab bar doesn't ALSO scroll. Trackpad horizontal swipe input

   (deltaX-dominated) passes through untouched so its native

   momentum / direction feel survives.

3. Pre-existing test 'keeps the overflow button flush against window

   controls on Windows' selected the scroll region by class name

   '.overflow-x-hidden' — bumped to '.overflow-x-auto' to match the

   new geometry.

Tested:

- bun run test src/components/layout/TabBar.test.tsx — 27/27 (24

  existing + 3 new):

  * 'exposes the scroll region with overflow-x-auto and a hidden

     scrollbar' — pins the CSS contract

  * 'translates a vertical wheel into a horizontal scroll on the

     tab strip' — fires wheel{deltaY:120}, asserts scrollLeft=120

  * 'passes horizontal wheel input through untouched (trackpad

     sideways swipe)' — fires wheel{deltaX:80,deltaY:10}, asserts

     scrollLeft unchanged

- bun run lint (desktop tsc --noEmit) — clean

Confidence: high. Scope-risk: narrow — single component, two-line CSS

swap + an isolated effect, no API or store changes.

Co-authored-by: 你的姓名 <you@example.com>
2026-06-11 23:02:33 +08:00
小橙子
6b5482e3b7
feat(solo): tier-1 signals + pipeline prompt + i18n keys (foundation pieces 2-3) (#17)
Three more Solo Pipeline mode foundation pieces, all developed in parallel

with the in-flight coordinator-mode optimization on a separate branch

(zero file overlap with that AI's work area). Wiring layer follows in a

single integration PR after coordinator stabilizes.

1. soloSignalsService — Tier-1 signal collector pairing with the pure

   buildSoloSuggestions engine from PR #16. Each signal gathers

   independently under Promise.allSettled so one failing probe never

   blocks the others, with hard timeouts and per-signal caps:

   - stashCount via 'git stash list' (4s timeout)

   - missingTestFiles via on-disk sibling-test detection on dirtyFiles

     only (caps fs.access fan-out at 20 paths)

   - todoHits via TODO/FIXME/XXX/HACK regex over dirtyFile heads (max

     8 hits, max 20 files scanned, 8KB head per file)

   - releaseMismatch detects the three failure modes our release flow

     bites on: notes-missing / version-not-bumped / tag-not-pushed

     (the v0.5.9 push-tag lesson is encoded directly in the third)

   - gitInProgress via fs.access on .git/MERGE_HEAD / rebase-* /

     CHERRY_PICK_HEAD, with worktree gitfile-pointer support

2. soloPipelinePrompt — sibling to coordinatorMode.ts. Owns the static

   5-stage system prompt (plan -> implement -> test -> review (HUMAN

   GATE) -> land), Stage 0 intent triage so the toggle does NOT fire

   on chat / hello, and entry-stage shortcuts ('review' / 'land') so

   suggestion-card clicks can skip directly to the relevant stage.

   Predicate isSoloPipelineMode reads CLAUDE_CODE_SOLO_PIPELINE_MODE,

   gated on the COORDINATOR_MODE bundle feature flag.

3. i18n keys for the suggestion engine — 26 new keys per locale

   across all 5 (en/zh/zh-TW/jp/kr). en.ts is the source of truth,

   the others mirror via Record<TranslationKey, string>.

Tested: 66/66 pass across the four Solo modules:

   - soloSuggestions: 23 (engine, scoring, dedup, foreign-dirty downscore)

   - soloSignalsService: 30 (each signal isolated + filesystem fixtures)

   - soloSuggestions.i18n.test: 2 (cross-module key contract — every

     i18n key the engine emits MUST exist in en.ts, prevents the easy

     'forgot to add the locale string' regression)

   - soloPipelinePrompt: 11 (predicate gating, prompt structural

     invariants — Stage 0 + 5 stages + HUMAN GATE + entry shortcuts

     + git-safety repetition in Stage 5)

Desktop tsc --noEmit clean — all 5 locales satisfy the contract.

Confidence: high. Scope-risk: narrow (additive, isolated; the wiring

layer that consumes these is a separate later PR).

Co-developed alongside another AI's coordinator B1/B2 optimization on

feat/coordinator-b2-task-spec — zero file overlap by design (their

branch touches AgentTool/coordinatorMode internals; this PR adds new

siblings + a separate /coordinator/soloPipelinePrompt.ts file).

Co-authored-by: 你的姓名 <you@example.com>
2026-06-11 22:50:11 +08:00
小橙子
4dd2ebd579
fix(plugins): smart 'Install all' with PATH refresh + per-manager fallthrough (#14)
User report: clicking 'Install all' on the reverse-engineering plugin's

missing-prereq modal showed cascading 'X command not found' errors even

after winget reported success — root cause was that the running terminal's

PATH never refreshed, so the next probe / install command couldn't see the

newly-installed binary. Compounded by the fact that the user's host had

neither winget nor scoop, and the deduped prereq row only carried the

first-encountered server's install map, dropping the powershell 'irm | iex'

fallback that ghidra's manifest declared.

Two coordinated fixes:

1. Server: pluginService.checkPluginPrerequisites now MERGES install maps

   across all servers declaring the same command (deduped by manager+cmd).

   So uvx now surfaces winget + scoop + powershell-irm together in the

   modal, not just the first server's two-step list.

2. Desktop: new smartInstallScript helper builds ONE base64-encoded

   PowerShell wrapper that:

   - Probes each missing command BEFORE running its installer (idempotent

     re-clicks, partial state)

   - For each install option in declared order, first checks whether the

     manager itself is on PATH; if not, skips the option silently with a

     yellow 'manager not on PATH' note instead of letting the user see

     'winget: command not found'

   - After each install attempt, reloads PATH from machine + user

     registry into the running PowerShell process — fixes the root issue

   - Auto-appends winget non-interactive flags so the install doesn't

     hang on license confirmation

   - Walks all install options until one produces the binary on PATH

   - Prints a colored summary (already-installed / installed-via-X / failed)

On Windows, the modal switches to this single-encoded-command path.

On macOS/Linux it keeps the existing per-command injection (POSIX shells

respect PATH updates from install scripts themselves, no equivalent

issue).

End-to-end exerciser: scripts/test-mcp-install.ps1 fetches a plugin's

live prereq state from the local API server, prints the smart-install

plan with manager-availability annotations, and (with -Run) executes

the wrapper for real, then re-fetches to verify post-install state.

Tested:

- bun test src/server/services/pluginService.test.ts: 5/5 (mergeInstallMap)

- bun test desktop/src/lib/smartInstallScript.test.ts: 11/11

- Live dry-run via test-mcp-install.ps1 against reverse-engineering

  plugin: shows winget+scoop+powershell-irm fallback chain for uvx as

  expected (merged from ghidra + lldb + jadx + apktool + frida)

- Desktop tsc --noEmit clean

Confidence: high. Scope-risk: narrow.

Tested: scripts/test-mcp-install.ps1 -Plugin reverse-engineering (dry run)

Not-tested: -Run path against a fresh VM (the install commands themselves

are unchanged data; only the wrapper logic changed).

Co-authored-by: 你的姓名 <you@example.com>
2026-06-11 21:29:07 +08:00
你的姓名
f2ede6c4df release: v0.5.10 2026-06-11 20:55:46 +08:00
小橙子
437b14e5cd
feat(handoff): deep-handoff toggle on welcome card (60 turns / 800 chars / 50k cap) (#13)
Follow-up to PR #12. User asked: can the handoff use even more context?

Defaulting all clicks to a huge tail has real costs (provider context-window

limits, signal dilution, prompt-cache invalidation timing), so instead expose

a per-user opt-in toggle on the welcome card's Continue-from-here group.

When the toggle is on, the next handoff:

- Reuses the cached LLM-generated main+recent (no extra LLM cost)

- Re-derives the verbatim recentRaw from the live JSONL with hard-coded

  enlarged sizing: 60 turns × 800 chars/turn / 50k total cap (~12.5k tokens)

- Ships via the same set_handoff_summary WS message + system-prompt path

Toggle state persists in localStorage (cc-haha-handoff-deep-mode) so a

user who opted in once doesn't have to re-click every session.

Why hard-coded sizing (vs env-tunable)?

  Predictable UX. A user enabling 'deep' should get a known enlarged tail

  regardless of any CLAUDE_CODE_HANDOFF_RAW_* env override they might

  have set. Env knobs still affect the default-mode generation.

Surface area:

- Server: extract readTurnsFromTranscript helper, refactor buildRecentRawSlice

  into a thin wrapper over a pure buildRecentRawSliceWithSizes(turns, sizes).

  New rebuildRecentRawForHandoff(sessionId) public helper used by the WS

  handler when message.deep === true.

- WS protocol: add optional 'deep' boolean to set_handoff_summary message

  (server events.ts + desktop chat.ts).

- WS handler: when deep, swap summary.recentRaw with the deep-rebuilt slice

  before calling formatHandoffSystemPrompt.

- Frontend: RecentActivityCard adds a toggle pill (history_edu icon) before

  the existing button group; threads { deep } through onAutoHandoff.

- EmptySession + ActiveSession: forward options.deep into the WS message.

- 5 locales each get 3 new keys: deepHandoffLabel + on/off tooltip variants.

Tested: sessionSummaryService.test.ts 11/11 pass, including 2 new tests

that lock the deep-mode 60-turn/800-char/50k sizing and prove env

overrides don't bleed into deep mode. Desktop tsc --noEmit clean.

Confidence: high. Scope-risk: narrow (additive optional flag throughout).

Co-authored-by: 你的姓名 <you@example.com>
2026-06-11 20:51:27 +08:00
小橙子
f4940472bb
feat(desktop): UX Phase 1 quick wins + Sidebar refactor (#11)
* feat(desktop): Phase 1 Quick Wins - accessibility and UX improvements

Implement Phase 1 "Quick Wins" for desktop UX optimization:

Accessibility:
- Add ARIA attributes to MessageList (role="log", aria-live="polite")
- Add ARIA attributes to StreamingIndicator (role="status")
- Add aria-label to TabBar scroll buttons
- Localize hardcoded Chinese strings in BrowserAddressBar and BrowserSurface

User Experience:
- Create shared Skeleton component for loading states
- Replace Sidebar text loading with skeleton placeholders
- Refactor TraceListSkeleton to use shared Skeleton component
- Create DOM-based Tooltip component with keyboard shortcut hints
- Add Tooltip to Sidebar "New session" button (⌘N)

Internationalization:
- Add new translation keys for all 5 locales (en, zh, zh-TW, jp, kr)
- Add browser navigation translations
- Add tab scroll translations
- Add chat message log translation

New components:
- desktop/src/components/shared/Skeleton.tsx
- desktop/src/components/shared/Tooltip.tsx

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(sidebar): extract utilities and components from Sidebar.tsx

Extract ~830 lines from Sidebar.tsx into focused modules:
- sidebarUtils.ts (~390 lines): pure utility functions for project grouping, localStorage persistence, preferences, path manipulation, and time formatting
- sidebarComponents.tsx (~340 lines): presentational components (icons, NavItem, ProjectHeaderMenu, SessionRowMeta, etc.)

Sidebar.tsx reduced from 1979 lines to ~1150 lines. TypeScript compilation passes. Existing tests need updating for vi.hoisted API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: 你的姓名 <you@example.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 20:22:36 +08:00