Persist transcript usage through session history so reopened desktop tabs can restore header token counts, and resume elapsed timers when reconnected sessions report active status.
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run lint
Tested: bun run check:server
Tested: git diff --check
Not-tested: bun run verify and coverage; scoped local handoff, not PR-ready validation.
Confidence: high
Scope-risk: narrow
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun test src/server/__tests__/sessions.test.ts -t "command breadcrumb"
Tested: bun test src/server/__tests__/searchService.sessions.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Scope-risk: narrow
Confidence: high
Tested:
- bun test src/server/__tests__/memory.test.ts
- cd desktop && bun run test -- src/__tests__/memorySettings.test.tsx
- cd desktop && bun run test -- src/components/chat/MessageList.test.tsx -t "active tool input"
- cd desktop && bun run check:desktop
- bun run check:server
Add a Codex-style global search dialog (Cmd+K / sidebar button) that
full-text searches across all session transcripts, replacing the old
title-only sidebar filter.
Backend: rewrite searchService.searchSessions as a two-phase engine —
ripgrep finds candidate files + matched lines, then those lines are
parsed to keep only user/assistant text, re-confirmed against the
cleaned text to drop JSON/UUID/base64 false positives, and windowed into
highlighted snippets. Results carry real session titles (new
sessionService.getSessionTitleAndMeta reusing the list title
precedence), project path, mtime, role and match counts; falls back to a
JS scan when ripgrep is unavailable.
Frontend: new GlobalSearchModal (debounced, stale-response-safe, keyboard
nav, role badges, highlighting, recent-chats empty state); Cmd+K now
opens it; the sidebar input is replaced by a search trigger button.
Tests: 15 backend cases (searchService.sessions) + 12 frontend cases
(GlobalSearchModal); existing Sidebar/pages tests updated for the new
trigger.
Prefer the live git diff for currently changed workspace files so DIFF previews do not get stuck on stale transcript snapshots. Refresh an existing workspace preview tab when it is opened again instead of only reactivating cached content.
Tested: bun test src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test src/stores/workspacePanelStore.test.ts --run
Tested: bun run check:server
Tested: bun run check:desktop
Confidence: high
Scope-risk: narrow
Third-party gateways can trickle content deltas (e.g. a large tool_use
input_json_delta) just under the idle-watchdog window and never send
message_stop. Each delta resets the idle timer, so the in-kernel watchdog
never fires and the request hangs forever — the desktop shows "running"
with slowly growing tokens and a permanently-pending trace span (#766).
Add an overall-duration watchdog (CLAUDE_STREAM_MAX_DURATION_MS) that is
armed once and never reset by incoming chunks; the desktop injects 600000ms
via buildChildEnv. Orthogonal to the idle watchdog: idle catches a fully
silent stream, this catches an endless trickle that never completes. CLI
default stays 0 (disabled) for backward compatibility.
Verified end-to-end by replaying the issue's real captured SSE against the
actual kernel: trickle hangs without the cap, aborts cleanly with it.
Keep provider ordering compatible with older desktop store state, keep workspace traversal blocked even when external changed-file roots are registered, and align the quality-contract test with the current AGENTS wording.
Tested: bun test scripts/pr/quality-contract.test.ts
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx src/__tests__/skillsSettings.test.tsx src/__tests__/pluginsSettings.test.tsx src/__tests__/diagnosticsSettings.test.tsx --run
Tested: bun run check:policy
Tested: bun run check:desktop
Tested: bun test src/server/__tests__/workspace-service.test.ts -t 'does not allow relative traversal'
Tested: bun test src/server/__tests__/sessions.test.ts -t 'workspace/file and tree should reject traversal|workspace/diff should reject traversal' --timeout=20000
Confidence: high
Scope-risk: narrow
Fix four root causes in the desktop preview pipeline, surfaced when the
model writes the files the user pointed it at:
- Output chips guessed paths from prose and could point at a missing file.
They are now reconciled against the turn's real changed files: a bare
`index.html` resolves to the `todo-app/index.html` actually written, and
mentions the turn never changed are dropped.
- A standalone single-page index.html got no browser preview (mistaken for a
Vite template). It is now only routed to the source view when a
package.json/vite.config ships in the same change-set.
- Files written outside the session workdir (another folder, or another drive
on Windows) failed to preview with 'Path is outside workspace'. The turn's
changed-file directories are registered as filesystem access roots; html
serves via /local-file and other files via a workdir-relaxed read.
- The visual-selection prompt leaked as a raw bubble on Windows because the
server-appended '[Image source: ...]' line broke replay dedupe. Replay text
is now metadata-normalized before comparison (affects any image message).
Adds unit tests for each: htmlPreviewPolicy, assistantOutputTargets
reconciliation, replay dedupe + stripGeneratedImageMetadataLines, filesystem
access roots, and workspace outside-workdir reads.
Render provider settings through dnd-kit sortable rows, include official providers in the same order model, and persist providerOrder across server and desktop state.
Tested:
- bun test src/server/__tests__/providers.test.ts src/server/__tests__/persistence-upgrade.test.ts
- cd desktop && bun run test -- --run src/stores/providerStore.test.ts src/__tests__/generalSettings.test.tsx -t "providerStore reorderProviders|Settings > Providers tab"
- cd desktop && bun run lint
- cd desktop && bun run build
- bun run check:persistence-upgrade
Not-tested:
- bun run check:server (broad suite hit environment failures during this run: MCP stdio zshrc timeout, adapter dependency gap at the time, and e2e cascade)
Confidence: high
Scope-risk: moderate
When an upstream request was aborted mid-stream (SDK client timeout,
stream idle watchdog, non-streaming fallback timeout, or user
cancellation), the trace fetch hook waited on a clone of the response
body that could hang forever, so the call never left "pending" in the
trace panel — exactly the silent stall that misled the #766 report.
- captureResponseTraceSnapshot reads the body with abort awareness:
reader.cancel() on abort keeps the partial body, with a 2s grace
backstop for runtimes where cancel cannot wake a hung read.
- The fetch hook now records an error-state call on abort with the
abort reason (e.g. the watchdog's stream idle timeout), duration,
partial response body, and an api_call_aborted event; non-abort
capture failures also record an error instead of inferring ok, and
pre-response fetch rejections carry an aborted flag.
- The trace detail panel shows an "Aborted" badge plus guidance for
aborted calls, and labels the new api_call_aborted phase in all
locales.
Tested: bun test src/server/__tests__/trace-capture.test.ts
Tested: bun run check:server
Tested: cd desktop && bun run test -- --run && bun run lint
api_retry heartbeats already reach the desktop status bar, but the
streaming-to-non-streaming fallback had no signal at all: the CLI only
flipped an internal flag, and the one-shot fallback response can take
minutes with zero incremental output, so the UI showed a bare spinner
the whole time.
- CLI: yield a {type:'system', subtype:'streaming_fallback', cause}
message at both fallback sites (stream error/watchdog and 404 stream
creation), mirroring the existing api_error -> api_retry path through
query.ts passthrough, QueryEngine SDK output, and the SDK schema.
- Server: translate it to a streaming_fallback ServerMessage;
unrecognized causes normalize to 'unknown' instead of dropping the
event.
- Desktop: track it as active-turn state (cleared at the same 12 sites
as apiRetry; a fallback notice supersedes the stale retry banner) and
render a neutral pill with the turn timer - expected state, not an
error, so no amber styling and nothing in the diagnostics panel.
- Retry banner now shows "retrying now" once the countdown elapses
instead of sticking at "waiting 0s".
With 62241a31 disabling the non-streaming fallback for desktop CLI
sessions, this notice mainly covers the 404 gateway path (which skips
the disable check), callers that re-enable fallback via env, and
non-desktop SDK consumers.
Constraint: Retries and fallbacks are expected states per the
diagnostics severity standard - lightweight active-turn UI only, no
error-panel entries, no transcript persistence.
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run check:server
Tested: cd desktop && bun run test -- --run
Tested: cd desktop && bun run lint
Not-tested: live provider outage reproduction; verified via unit
coverage of the translation, store lifecycle, and indicator rendering.
The context usage indicator stayed at the pre-compact percentage (e.g.
100%) until the next API response arrived. Two stacked causes:
- The CLI anchors the displayed total to max(local estimate, last API
usage) so the meter never drops mid-turn — but preserved messages
(SM-compact / partial compact) still carried the pre-compact usage,
pinning the meter after compaction. buildPostCompactMessages now
zeroes token usage on preserved assistant copies, the established
stale placeholder that getCurrentUsage() skips, covering every
compaction path in one place. Originals are not mutated, so
transcript and cost accounting are unaffected.
- Right after compaction the CLI is often still busy, so the
indicator's refresh timed out ("Request timed out after 30s") and the
catch kept the stale context on screen with no later retry (auto
refresh is throttled to 10s and stops once the session goes idle).
compact_boundary now bumps a per-session compactCount; the indicator
force-refreshes on that nonce — bypassing the throttle and any
in-flight request that may still hold pre-compact data — and retries
once after 5s if the forced refresh fails.
Tested: bun test src/services/compact/
Tested: cd desktop && bun run test -- src/components/chat/ContextUsageIndicator.test.tsx src/stores/chatStore.test.ts
Tested: cd desktop && bun run lint
Confidence: high
Scope-risk: narrow
Desktop injected a far stricter timeout stack than the terminal CLI, so
healthy-but-slow third-party providers (sensenova/bailian/zhipu) died at
exactly API_TIMEOUT_MS while the UI showed "running" forever:
- API_TIMEOUT_MS is the SDK client's time-to-first-byte budget for
streaming requests; these gateways send zero bytes (no headers, no
SSE ping) until prefill finishes, which takes minutes at large
contexts. Raise the default from 120s to the SDK's own 600s and widen
the configurable range to 30-1800s.
- Widen the desktop-forced stream watchdog idle window to 240s so
silent thinking/prefill phases stop tripping the 90s default.
- Disable the non-streaming fallback for desktop CLI sessions: a
non-streaming request only responds after the FULL generation, so it
can never finish inside the same budget and loops timeout aborts
forever while the UI spins (also avoids double tool execution,
upstream inc-4258). All three knobs respect caller env overrides.
Repro: mock upstream whose SSE stays silent for 150s before a complete
event sequence — terminal env completes; desktop env aborts at exactly
120s (client timeout) or 90s watchdog + non-streaming fallback loop;
the fixed env completes both variants.
Tested: bun test src/server/__tests__/network-settings.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/proxy-network-settings.test.ts
Tested: cd desktop && bun run test -- src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx
Tested: cd desktop && bun run lint
Confidence: high
Scope-risk: medium
Add pending user-message queue controls while a desktop turn is running, replay queued prompts into the transcript when the CLI accepts them, and flush remaining prompts after turn completion.
Tested: cd desktop && bun run test -- src/components/chat/ChatInput.test.tsx
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run check:desktop
Not-tested: bun run check:server remains blocked by unrelated E2E CORS preflight expectation, expected 403 but got 204.
Confidence: medium
Scope-risk: moderate
Reveal-in-Explorer/Finder rejected ~-prefixed paths because no layer
expanded the tilde to the home directory. Expand it in the three path
normalization entry points: server validateOpenPath, frontend
resolveAbsolute, and Electron normalizeOpenPath. Tilde expansion is
platform-aware (~\ only on win32, where backslash is a separator).
Token counts were rendered with five inconsistent formats across the
chat UI (header "181,117 t", in-progress bare "↓ 2514", background
agents "12,345 tokens", compact summary "1.5k", trace "1.2k"), and the
in-progress count was actually stale: the server never set the status
event's tokens field, so the indicator showed the previous turn's value
(hence "first message shows nothing", "sometimes appears, never moves").
- Add shared desktop lib/formatTokenCount; route header, streaming
indicator, background agents, compact summary, and trace through it.
- Header shows compact "124.3k tokens" with the exact count on hover.
- Add common.tokens i18n key (en/zh/zh-TW/jp/kr).
- Estimate the in-progress count from streamed chars (÷4, mirroring the
CLI spinner) via a new streamingResponseChars per-session field, reset
on each send; drop the dead status.tokens/elapsed fields.
- CLI spinner rows use formatTokens to drop the "1.0k" artifact.
Allow local browser origins such as 127.0.0.1, localhost, and ::1 to use the desktop server without H5 token flow, while keeping LAN and public origins behind H5 access rules. Also make Vite SPA healthcheck fallback to the default loopback backend and document scoped verification expectations.
Tested: bun test src/server/__tests__/h5-access-policy.test.ts
Tested: bun test src/server/__tests__/h5-access-auth.test.ts
Tested: bun test src/server/middleware/cors.test.ts
Tested: cd desktop && bun run test -- src/lib/desktopRuntime.test.ts --run
Tested: git diff --check
Not-tested: bun run verify and coverage were intentionally skipped for this scoped local-dev fix.
Scope-risk: moderate
Strip the rotating billing attribution line before converting system
prompts to OpenAI Chat/Responses requests, forward a stable
prompt_cache_key derived from the client session, request stream usage
explicitly on Chat streams, and map cached_tokens back to Anthropic
cache_read_input_tokens with exclusive input accounting.
Show ExitPlanMode approvals as a rendered plan preview in desktop chat,
forward plan feedback and requested prompt permissions through the desktop
WebSocket permission response, and keep permission-mode restoration owned by
the CLI runtime.
Tested: bun run verify
Confidence: high
Scope-risk: moderate
Add historical tool and skill aggregation to activity stats, compact the token summary, and show Codex-style activity insights with plugin/skill ranking.
Tested: bun test src/utils/__tests__/stats.test.ts
Tested: bun run check:persistence-upgrade
Tested: bun run check:desktop
Not-tested: bun run check:server (previous full run exposed unrelated conversations WebSocket timeouts; focused stats and persistence checks passed)
Confidence: medium
Scope-risk: moderate
Ensure Electron sidecar launch and Windows taskkill calls hide console windows, and pass the same hidden-window spawn option through desktop CLI and scheduled-task subprocess launches.
Tested: cd desktop && bun test ./electron/services/sidecarManager.test.ts
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/cron-scheduler-launcher.test.ts
Tested: bun run check:native
Tested: bun run check:server
Not-tested: Windows GUI quit smoke
Confidence: medium
Scope-risk: narrow
Keep live trace polling sensitive to in-place call changes, scope detail section collapse state by trace session, and expose trace rows with list/button semantics.
Tested: cd desktop && bun run test -- TraceList.test.tsx TraceSession.test.tsx
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/trace-capture.test.ts
Confidence: medium
Scope-risk: narrow
Mapped Windows drives can resolve to UNC paths before the CLI starts. Treat the UNC prefix as safe only after the target has already been proven to be inside the allowed working directory, while keeping sensitive paths and out-of-workspace UNC writes behind safety checks.
Tested: bun test src\\utils\\permissions\\filesystem.test.ts
Tested: real local app server/WebSocket session with deepseek-v4-pro and --dangerously-skip-permissions wrote through X:\\project without a permission_request.
Not-tested: full check:server is still red on this Windows checkout due pre-existing unrelated failures in shell env, H5/local-file, MCP, FileRead CJK, and a darwin-only expectation.
Confidence: high
Scope-risk: narrow
UI rebuild (desktop):
- TraceSession: replace 3-column layout with two panes — turn-grouped
timeline tree (draggable splitter, search/filter, keyboard nav) and a
section-flow detail panel (Response / Messages / System Prompt /
Tools / Parameters / Raw), collapse state persists across spans
- Render LLM requests/responses semantically: messages as role-colored
conversation with tool_use/tool_result pairing instead of raw JSON
dumps; Raw fallback via CodeViewer for legacy truncated records
- TraceList: row-style list with model chips, mono metrics, hover
actions; content-visibility rows (no virtualization, WebKit-safe)
- i18n synced across zh/en/jp/kr/zh-TW (+25/-55 keys)
Data & capture (server):
- Capture full bodies: preview cap 2048 -> 240k chars, stream cap
256KB -> 1MB; list API trims previews to keep polling light; new
GET /api/sessions/:id/trace/calls/:callId returns the full record
- Extract per-call token usage at read time (SSE + JSON + proxy
wrapped); mtime-keyed read cache for the polling path
- Fix sensitive-key regex redacting *_tokens count fields, which made
token stats always report 0
Frontend data layer:
- SSE stream reassembly (Anthropic + OpenAI chat) adapted from
claude-tap (MIT, attribution in THIRD_PARTY_LICENSES.md), request/
response body parsers, shared formatters, on-demand call detail
cache; traceViewModel gains tokenUsage/isLifecycleNoise, drops
fullRaw
Tested:
- bun run check:server (1201 pass)
- bun run check:desktop (lint + 1358 tests + build)
- Chromium walkthrough against real local traces: list, session tree,
LLM semantic detail (new format), legacy fallback, tool detail
Persist permission mode changes immediately, but defer restart-only permission transitions until the active desktop turn emits its terminal result.
Tested:
- bun test src/server/__tests__/conversations.test.ts -t "should defer bypass permission restarts until the active turn completes"
- bun test src/server/__tests__/conversations.test.ts -t "permission"
- bun run check:server
Scope-risk: narrow
Tested:
- cd desktop && bun run test -- --run src/pages/TraceList.test.tsx
- bun test src/server/__tests__/trace-capture.test.ts
- bun run check:desktop
- bun run check:server
Scope-risk: broad
Fixes#652.
Add desktop/server output style settings APIs and a General Settings picker
that mirrors the Claude CLI outputStyle sources.
Persist active-project choices to .claude/settings.local.json and global
choices to user settings, and route /config to the local settings UI.
Tested:
- bun test src/server/__tests__/settings.test.ts
- cd desktop && bun run test -- src/components/chat/composerUtils.test.ts src/stores/settingsStoreOutputStyle.test.ts src/pages/SettingsOutputStyle.test.tsx
- cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx
- bun run check:desktop
- bun run check:server
- bun run verify
Confidence: high
Scope-risk: moderate
Ensure custom OpenAI-compatible provider tests explicitly request non-stream responses for both direct connectivity checks and the proxy pipeline. This avoids default SSE responses being parsed as empty JSON by provider validation.
Tested: bun test src/server/__tests__/providers.test.ts --test-name-pattern "requests non-stream OpenAI Chat responses during provider tests"
Tested: bun test src/server/__tests__/providers.test.ts
Tested: bun test src/server/__tests__/proxy-transform.test.ts
Tested: bun run check:server
Confidence: high
Scope-risk: narrow
Prevent desktop prewarm launches from inheriting interrupted-turn resume state, while leaving explicit user-message startup unchanged. Also stop server-side background schedulers during shutdown so app quit cannot leave scheduled task runners alive.
Tested:
- bun test src/server/__tests__/conversation-service.test.ts
- bun test src/server/__tests__/conversations.test.ts -t "prewarm"
- bun test src/server/__tests__/conversations.test.ts -t "permission"
- bun test src/server/__tests__/diagnostics-service.test.ts -t "keeps fatal startup errors visible on stderr while recording diagnostics"
- bun test desktop/electron/services/windows.test.ts desktop/electron/services/tray.test.ts desktop/electron/services/sidecarManager.test.ts src/server/__tests__/server-shutdown.test.ts
- bun run check:server
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
Avoid killing an in-flight desktop SDK turn when runtime config changes during tool execution. Persist the requested runtime immediately, then restart after the turn emits its terminal result.
Tested:
- bun run check:server
- real provider tool-turn model switch smoke
- bun run check:coverage (changed-line coverage passed; unrelated global lanes still fail)
Scope-risk: narrow
Git writes upstream branch config when a worktree starts from origin/main. Use the already-resolved base SHA as the worktree start point so parallel agent worktrees do not race on shared .git/config.
Tested: bun test src/utils/__tests__/worktree.test.ts
Tested: real gpt-5.5 Sub2API run with four worktree-isolated agents
Not-tested: bun run check:server currently fails unrelated WebSocket restart/timeouts
Confidence: high
Scope-risk: narrow
Guard OpenAI-compatible proxy streaming bodies with the configured AI request timeout so a provider that emits partial SSE and then idles cannot leave proxy consumers waiting forever.
This is a proxy-level fix found while investigating #548; it does not claim to close the broader desktop interruption issue.
Tested: bun test src/server/__tests__/proxy-network-settings.test.ts
Tested: bun run check:server
Confidence: medium
Scope-risk: narrow
The models API derives its model list from ANTHROPIC_MODEL and the
ANTHROPIC_DEFAULT_{HAIKU,SONNET,OPUS}_MODEL env vars. A developer who exports
these for a custom provider (e.g. MiniMax) leaked them into the no-provider
fixture: the four collapsed to one model, the default model became the
custom one, and switched-model names fell back to the raw id — failing five
"available models" / "default model" assertions on that machine while passing
on clean CI.
Clear those four vars in the e2e setup and restore them in teardown, matching
the existing CLAUDE_CONFIG_DIR / CLAUDE_CLI_PATH isolation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The diagnostics panel filled with red ERROR/WARN entries during normal use
because the capture layer treated "wrote to console" as "something failed",
with no severity gatekeeping. Much of the noise was also test runs leaking
into the user's real ~/.claude/cc-haha/diagnostics.
- diagnosticsService: default unclassified events to info (not error);
drop writes under NODE_ENV=test when no CLAUDE_CONFIG_DIR is set; document
the console-capture contract (expected states use console.debug/info).
- index: don't install console/process capture under bun test.
- api/diagnostics: an ingested event with missing severity defaults to info.
- oauthRefreshLog (new): token refresh failure is gracefully handled and is
never an error — expected expiry (401/403/revoked) logs at debug, anything
else at warn. Wired into both Haha OAuth services.
- conversationService: classify cli_runtime_exit severity by exit code, so
clean/SIGTERM/SIGKILL exits are info and only abnormal codes stay error
(real "chat died" crashes remain perceivable).
- ws/handler: streaming partial tool-input JSON is normal — debug, not warn.
Tests: oauth-refresh-log + cli-exit-severity unit tests; diagnostics-service
covers the info default and the test-isolation guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use OS-assigned ports for server integration tests and publish the actual Bun server port after startup, preventing local port collisions from failing server checks.
Tested: bun test src/server/__tests__/e2e/business-flow.test.ts
Tested: bun test src/server/__tests__/e2e/full-flow.test.ts src/server/__tests__/conversations.test.ts src/server/__tests__/tasks.test.ts src/server/__tests__/haha-openai-oauth-api.test.ts
Tested: bun run check:server
Confidence: high
Scope-risk: narrow
Separate quarantine review enforcement from server and coverage file selection so expired review dates fail only the governance lane.
Refresh stale server quarantine suites and keep only the live provider test quarantined for non-live PR gates.
Tested: bun run check:policy
Tested: bun run check:server
Tested: bun run check:coverage
Tested: bun run verify
Tested: git diff --check
Confidence: high
Scope-risk: moderate
/agent should behave like a normal session prompt that tells the main model to use the selected Agent tool. The previous forked command path buffered the subagent run behind local command output and forced desktop clients to wait for command completion instead of seeing the normal Agent interaction stream.
Constraint: Desktop /agent must preserve ordinary chat/session semantics and still require /agent <agent> <prompt>.
Rejected: Keep slash_agent SDK event bridging | it preserves the separate forked execution path instead of fixing the product flow.
Confidence: high
Scope-risk: moderate
Directive: Do not move /agent back to context: fork without proving the desktop normal Agent tool stream still works.
Tested: bun test src/commands/agent.test.ts src/utils/processUserInput/processSlashCommand.test.ts src/server/__tests__/ws-memory-events.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx
Tested: git diff --check
Not-tested: bun run check:server is blocked before source checks by expired quarantine entries: server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
The desktop /agent slash path runs before the normal query loop, so the old implementation waited for the forked command to finish and only surfaced the final local-command stdout. Emit foreground slash-agent task events from /agent, drain SDK events while QueryEngine waits for pre-query slash processing, and allow those current-turn events through the WebSocket pre-send mute gate.
Constraint: /agent is a forked slash command that does not produce ordinary assistant deltas until its final stdout is synthesized.
Rejected: Frontend-only loading state | would not expose real forked-agent progress or tool/tool-result updates.
Confidence: high
Scope-risk: moderate
Directive: Keep slash_agent task events scoped to /agent; ordinary slash command stdout and /goal event handling should remain on the existing local-command paths.
Tested: bun test src/QueryEngine.test.ts src/utils/processUserInput/processSlashCommand.test.ts src/server/__tests__/ws-memory-events.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx
Not-tested: bun run check:server is blocked by expired quarantine review entries: server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
Async title refresh runs after assistant output, so provider prompts can be biased by assistant language and English title examples. The title language now resolves from the first meaningful user message before consulting the response-language fallback, then retries once when a generated title violates that language.
Constraint: Desktop response-language settings still control assistant prose, not automatic title metadata when the first user message has a clear language
Rejected: Follow response-language setting for every generated title | Chinese sessions flip to English when replies are configured in English
Rejected: Persist mismatched generated titles after retry failure | preserves a bad UX over the safer first-message placeholder
Confidence: high
Scope-risk: moderate
Directive: Do not change automatic title language precedence without re-testing image/text first-turn sessions and English-response-language sessions
Tested: bun test src/server/__tests__/title-service.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts -t "refreshes the first-turn AI title"
Tested: git diff --check
Tested: real MiniMax-M3 /tmp session 58a47fcd-6f9d-4685-bc73-f9317e3f330e with temporary response language english; assistant replied English and final aiTitle was 标题语言验证
Not-tested: bun run check:server blocked before tests by expired quarantine entries server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
Desktop title generation previously upgraded the first sidebar title from the raw user prompt before assistant output existed. That made image-plus-text sessions vulnerable to keeping a title based only on the user's initial wording. This moves polished title generation to completed turns, reuses the CLI title prompt and transcript extractor, and routes title calls through the configured haiku model with a retry when disabled thinking is rejected.
Constraint: The first user message still needs an immediate placeholder title so the sidebar is responsive.
Rejected: Generate the AI title before assistant output | image-derived context is only available after the assistant turn completes.
Confidence: high
Scope-risk: moderate
Directive: Keep polished automatic titles tied to completed transcript turns, not pre-response user prompts.
Tested: bun test src/server/__tests__/title-service.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts -t "refreshes the first-turn AI title"
Tested: git diff --check
Tested: Real MiniMax-M3 /tmp session f89073ed-3467-492a-b63c-e1886c146a3a with image attachment wrote ai-title "Validate session title generation"
Not-tested: bun run check:server is blocked by existing expired quarantine manifest entries
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
Desktop chat can fall back to persisted transcript estimates when live inspection context is unavailable. That path previously only used built-in model windows, so custom provider settings such as MiniMax-M3 with a 1,000,000-token window still displayed the default 200,000-token estimate. Resolve transcript context windows from the session or active provider runtime env first, then fall back to built-ins.
Constraint: Provider model context windows are stored in provider runtime env, not always in the server process env.
Rejected: Change the built-in MiniMax-M3 window to 1,000,000 | upstream preset correctly keeps MiniMax-M3 at 204,800 by default, while user overrides must remain provider-specific.
Confidence: high
Scope-risk: moderate
Directive: Keep transcript estimates aligned with session/provider runtime env before consulting built-in model defaults.
Tested: git diff --check
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "active provider model context windows|Sonnet 4.6 transcript usage|low-trust media"
Tested: bun test src/server/__tests__/provider-presets.test.ts src/server/__tests__/provider-runtime-env.test.ts src/server/__tests__/providers.test.ts src/utils/__tests__/context.test.ts src/utils/__tests__/contextBudget.test.ts
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 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