Commit graph

1126 commits

Author SHA1 Message Date
易良
b7b5ed00e8
fix(cli): prevent monitor turns after task_stop (#7573)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Python / Classify PR (push) Waiting to run
SDK Python / SDK Python (3.10) (push) Blocked by required conditions
SDK Python / SDK Python (3.11) (push) Blocked by required conditions
SDK Python / SDK Python (3.12) (push) Blocked by required conditions
2026-07-23 07:48:55 +00:00
jinye
3617397e5f
feat(core): Align GenAI telemetry with ARMS (#7536)
* feat(core): align GenAI telemetry with ARMS

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): remove estimated token usage splits

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): address GenAI telemetry review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-23 07:35:46 +00:00
callmeYe
b436855a40
feat(core): propagate trusted daemon invocation context (#7279)
* feat(core): propagate trusted daemon invocation context

* test(cli): update ACP startup expectation

* refactor(core): centralize ACP capability env key

* test(cli): update worktree ACP core mock

* test(integration): run daemon context smoke on PRs

* test(ci): update no-AK smoke expectation

* test(core): cover invocation context isolation

* fix(cli): compare ACP capability safely

* fix(docs): restore GitHub action input names

* fix(core): sanitize private ACP capability from child env

* fix(core): reuse private ACP capability env constant

* test(cli): cover malformed trusted invocation context

* test(acp-bridge): assert exact child environment

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-23 06:49:11 +00:00
ytahdn
22cd9c7d7a
fix(web-shell): sync background agent status (#7561)
* fix(web-shell): sync background agent status

* fix(web-shell): harden background agent reconciliation

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-23 06:35:04 +00:00
ytahdn
dc74279103
feat(serve): add workspace-level generation (#7552)
* feat(serve): add workspace-level generation

* docs(serve): document workspace generation capability

* fix(serve): align workspace generation contracts

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-23 05:17:03 +00:00
ytahdn
fee601ae77
feat(web-shell): add selective shadow DOM isolation (#7551)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-07-23 02:54:12 +00:00
jinye
7c73768fa5
perf(startup): lazy-load Google GenAI SDK on first use (#7512)
* perf(startup): lazy-load Google GenAI SDK on first use

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7512)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7512)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-23 02:07:39 +00:00
ChiGao
d064bd7dcf
feat(cli): preserve semantic text when copying VP selections (#7286)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* docs(cli): define semantic copy fidelity scope

* docs(cli): address semantic frame review gaps

* docs(cli): preserve soft-wrap source separators

* feat(cli): preserve semantic selection copy

* fix(cli): address semantic copy review findings

* fix(cli): preserve clipped semantic boundaries

* fix(cli): limit separator carrier joiner to visible width in wrap metadata

The greedy /\s+/ match in wrapTextWithMetadata could capture more
source whitespace than the separator carrier row actually consumed
(e.g. a tab following a space), causing duplicated whitespace in
semantic copy. Limit the match to visibleLine.length characters and
add a mixed space/tab regression test.

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
2026-07-22 14:06:03 +00:00
Dragon
3a2a74a69b
feat(core): restore background agent roster (#7459)
* feat(core): restore background agent roster

* fix(web-shell): add list_agents to TOOL_DISPLAY_NAMES

The new list_agents core wire tool was added to core's ToolNames but not
to the web-shell TOOL_DISPLAY_NAMES map, causing toolFormatting.drift.test.ts
to fail (expected ['list_agents'] to deeply equal []). Add the missing
'ListAgents' display-name entry so the browser panel shows a friendly name
instead of the raw wire name and the drift guard passes.

* fix(cli): reload old-session background agents on failed resume rollback

When /resume fails after core has swapped but before the UI swap, the catch
block rolls core back to the old session via startNewSession(oldSessionId).
However the forward path already called resetBackgroundStateForSessionSwitch,
which cleared the old session's in-memory background agents. The rollback did
not reload them, so list_agents returned empty for the old session (whose
sidecars are still on disk) until the next process start or successful resume.

Reload the old session's paused background agents after rolling core back, so
the restored roster matches on-disk state. Placed after startNewSession so the
loadPausedBackgroundAgents current-session guard is satisfied; best-effort via
.catch so it never blocks the rollback path.

* fix(web-shell): add zh translation for list_agents tool name

The toolFormatting test 'has a zh translation for every tool in the
display-name map' failed with expected ['list_agents'] to deeply equal []
because list_agents was added to TOOL_DISPLAY_NAMES without a matching
toolName.list_agents zh-CN entry. Add the translation to restore parity.

* fix(cli): resolve CI failures for background-agent roster restore

- Add toolDisplayName.ListAgents translations (en, zh, zh-TW, ca) so the
  new list_agents tool has a zh entry; fixes i18n/index.test.ts.
- Add loadPausedBackgroundAgents and consumePendingRecoveredAgentsNotice
  to the acpAgent worktree test config mock, which loadSession now calls
  via #restoreBackgroundAgentsOnResume; fixes acpAgent.worktree.test.ts.

* refactor(core): extract incompatible-isolation blocked reason to a const

Move the incompatible-isolation blocked-reason string out of an inline
literal into a module-level INCOMPATIBLE_ISOLATION_BLOCKED_REASON const,
matching its four sibling reasons so the text is discoverable by
constant-name grep and edited alongside the others.

* fix(core): preserve retained activity state on failed agent revive

Address review feedback on the background-agent roster restore:

- On a failed completed-agent revive, restore UI state with a non-empty
  guard instead of `??`. Because `restorePausedEntry` resets the paused
  entry's `recentActivities` to `[]`, the previous `failedEntry?.field ??
  completedEntry.field` kept that empty array and dropped the pre-revive
  snapshot (the UI Progress section rendered empty). Applied consistently
  to pendingMessages, recentActivities, and pendingApprovals.

Add regression coverage for previously untested paths:

- failed revive preserves pre-revive recentActivities
- terminal-agent cap admits only the newest MAX_RETAINED_TERMINAL_AGENTS
  completed sidecars on restore
- /resume rollback reloads the old session's background agents
- headless resume prepends the recovered-agents notice to the prompt

* test(cli): cover interrupted-turn continuation not consuming recovered-agents notice

Add ACP and headless regression tests asserting an interrupted-turn
continuation does not consume the one-shot recovered-agents notice
(the !isContinue / !continueInterrupted guards), so it is delivered on
the user's next ordinary prompt. Mirrors the existing slash-command
coverage.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-22 12:35:02 +00:00
Edenman
b693fcfe6a
feat(serve): support forced MCP reconnects (#7488)
* feat(serve): support forced MCP reconnects

* test(serve): cover forced MCP reconnect options

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
2026-07-22 09:02:28 +00:00
Dragon
0695074b6c
feat(core): keep completed background agents resident (#7426)
* feat(core): keep background agents resident

* fix(core): harden background continuation boundaries

* docs(core): move per-spawn cleanup comment to subagentDispose

The comment describing the per-spawn cleanup (which stays undefined on
the fork-resume path) had drifted above the launchModel declaration,
where it no longer applied and could mislead readers. Relocate it to the
subagentDispose assignment in the non-fork branch it actually documents.

* fix(core): close finishing window and release resident on error in background GOAL path

- Non-worktree GOAL completion drained the message queue but never called
  registry.beginFinishing(), unlike the worktree path. A send_message racing
  the terminal transition could be accepted (status still running,
  finishingAgents empty) and then orphaned by complete(). Call beginFinishing()
  after the empty drain to reject the racing message instead.
- The completion catch block never reset keepResident, so a throw from
  patchAgentMeta/registry.complete left the runtime resident but finalized as
  failed — a zombie that cleanupRuntime never reclaimed. Reset keepResident in
  the catch so the finally block disposes it.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-22 07:05:33 +00:00
ytahdn
c961f4edf9
feat(web-shell): add rendered file previews (#7467)
Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-22 03:35:52 +00:00
ytahdn
694eea27e7
perf(web-shell): optimize long session rendering (#7408)
* perf(web-shell): optimize long session rendering

* test(web-shell): align long-session expectations

* fix(web-shell): address long-session review feedback

* test(web-shell): cover bounded refresh metadata and auto-pagination status suppression

* fix(web-shell): harden long-session refresh fallback and turn animation (#7408)

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: Qwen Autofix <autofix@qwen-code.dev>
2026-07-22 03:28:47 +00:00
ytahdn
80863c0858
feat(web-shell): show subagent sessions in detail panel (#7380)
* feat(web-shell): show subagent sessions in detail panel

* fix(web-shell): harden subagent session details

* chore(sdk): account for subagent client APIs

* fix(core): keep subagent resume history valid

* fix(subagents): stabilize detail session streaming

* test(cli): update telemetry route count

* fix(subagents): address review feedback

* fix(subagents): harden transcript refresh state

* fix(subagents): address maintainer review

* fix(subagents): preserve compact status details

* fix(subagents): address review feedback round 3 (#7380)

* fix(subagents): address remaining detail review feedback

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-22 02:31:08 +00:00
Heyang Wang
6f5d6dfd65
feat(web-shell): add workspace selector button with add/switch dropdown in composer toolbar (#7390)
* feat(web-shell): add managed workspace selector

Let Web Shell create and select daemon-managed workspaces without
changing ownership of existing sessions.

- Add capability-gated existing and scratch workspace registration
- Validate scratch roots, trust provenance, capacity, and shutdown races
- Serialize workspace mutations, session switching, and refresh results
- Add SDK/WebUI wiring and focused cross-package regression coverage

# Conflicts:
#	packages/web-shell/client/App.tsx
#	packages/web-shell/client/components/sidebar/WebShellSidebar.tsx

# Conflicts:
#	packages/cli/src/serve/capabilities.ts
#	packages/cli/src/serve/routes/workspace-management.ts
#	packages/cli/src/serve/server.test.ts
#	packages/sdk-typescript/src/daemon/DaemonClient.ts
#	packages/web-shell/client/App.tsx
#	packages/web-shell/client/components/dialogs/AddWorkspaceDialog.tsx
#	packages/web-shell/client/components/sidebar/WebShellSidebar.tsx

* fix(web-shell): revalidate workspace before session creation

Prevent a stale workspace selection from bypassing the latest trusted
capability snapshot during lazy session creation.

- Validate the selected workspace before passing it to the daemon
- Fall back to the primary workspace when trust has been revoked
- Add a regression test for the pre-effect race window
- Remove stale branch state and clarify add-workspace ownership

* fix(web-shell): improve workspace removal feedback

Keep workspace removal controls legible and make blocked force removals
visibly inactive.

- Size the action menu independently from its narrow icon trigger
- Add a disabled affordance and suppress destructive hover styling
- Cover the removal menu width override with a regression test

* fix(web-shell): centralize existing workspace registration

Route sidebar and composer entry points through the App-owned dialog so
capability gating and workspace reconciliation remain consistent.

- Forward display names only when the daemon advertises support
- Hide and suppress persistence when registration is runtime-only
- Mark directory registrations with existing-workspace provenance
- Cover both entry points and capability combinations with tests

* fix(web-shell): address review feedback on workspace dialogs and capability docs (#7390)

- Document dynamic_workspace_registration and scratch_workspace_registration
  in the conditional serve-features table so the capabilities-docs-contract
  test passes.
- Gate DialogShell backdrop-click and Escape dismissal on the dismissible
  prop so non-dismissible dialogs ignore both gestures.
- Surface an inline error when an added folder registers but the capability
  refresh fails, mirroring the scratch recovery path.
- Add coverage for the active-session workspace switch and the add-folder
  refresh-failure paths.

* fix(web-shell): address review feedback on workspace dialogs and capability docs (#7390)

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-22 01:10:18 +00:00
jinye
042f6373b7
perf(startup): Load undici lazily behind package-local dynamic imports (#7455)
* perf(startup): load undici lazily behind package-local dynamic imports

* fix(web-search): preload undici before building runtime fetch options

Address review: web search builds fetch options outside the content
generator preload path, so 33 web-search tests (and any standalone
search invocation) hit the requireUndici fail-loud guard. Also redact
and rethrow proxy dispatcher install failures, guard early promise
rejections against unhandledRejection, and pin the guard message with
a test.

* test(cli): cover loadUndici interop and gitUtils proxy path

Address review suggestions: add parameterized tests for the CJS
unwrap normalization used by both core and cli loadUndici helpers,
and verify getLatestGitHubRelease instantiates ProxyAgent when a
proxy argument is passed.

* fixup! test(cli): fix loadUndici test type errors

Export UndiciModule type and loosen test helper typing so the cli
package builds under tsc --build.
2026-07-22 00:25:52 +00:00
jinye
5c5c1017fc
fix(core): Enforce final tool response budgets (#7323)
* fix(core): enforce final tool response budgets

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7323)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7323)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7323)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7323)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7323)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-21 16:09:46 +00:00
Zqc
cf6c51396c
chore(docs,test): batch three small docs and test fixes (#7373)
* docs(sdk): document auto permission mode

* docs(sdk): clarify auto permission mode

* docs(action): fix GitHub Action input names

* test(weixin): cover exported AES key parser

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-07-21 12:50:03 +00:00
Dragon
1fec09423b
fix: support context-inheriting subagents in headless mode (#7378)
* fix(core): support headless fork subagents

* fix(desktop): avoid false background agent events

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-21 12:33:13 +00:00
yuanyuanAli
ff14e65792
feat(web-shell): Add sidebar customization API for branding, navigation, session actions, and footer (#7379)
* feat(web-shell): Add sidebar customization API for branding, navigation, session actions, and footer

Add new sidebar configuration options to WebShellSidebarOptions:

- primaryNav.items: Control which built-in nav buttons are shown (newTask, plugins, scheduledTasks, goals)
- primaryNav.render(): Append custom content after built-in nav buttons
- hideProjectHeader: Hide the 'Projects' header row (search + add workspace)
- sessionActions.items: Control which session action items appear (both inline and dropdown)
- sessionActions.inlineItems: Control which items render as inline hover buttons (supports all action types with icon/text fallback)
- footer.render(): Inject custom UI elements on the left side of the footer
- Move scheduledTasks and goals from footer.items to primaryNav.items

Visibility follows a strict 'hide-first' policy: inline buttons require all three conditions (items includes + inlineItems includes + built-in capability check) to render.

* fix(web-shell): Prevent inline/dropdown duplication and add pin/archive dropdown fallback

- Critical fix 1: Dropdown items now exclude items already shown as inline buttons (added !inlineActionItems.has guard to each dropdown entry and trigger visibility).
- Critical fix 2: pin and archive now have dropdown menu entries as fallback when not configured as inline items, preventing them from becoming inaccessible.
- Narrowed inlineItems type to WebShellSidebarSessionInlineActionItem (excludes details/group which have no working inline handlers), preventing dead buttons.
- Added destructive color styling (var(--destructive)) to inline delete button when not disabled.

* fix(web-shell): Re-export new sidebar types and add visibility matrix tests

- Re-export WebShellSidebarPrimaryNavOptions, WebShellSidebarPrimaryNavItem, WebShellSidebarSessionActionsOptions, WebShellSidebarSessionActionItem, and WebShellSidebarSessionInlineActionItem from client/index.tsx so consumers can import them by name.
- Add session-action-visibility.test.ts: table-driven tests covering the items × inlineItems × capability matrix (default config, empty items/inlineItems, dedup guarantee, pin fallback to dropdown). 7 test cases, all pass.

* fix(web-shell): gate readOnly archive on sessionActionItems, fix footer null safety and docs accuracy (#7379)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-21 11:20:51 +00:00
tanzhenxin
443b81e180
feat(core): add opt-in built-in web_search backed by the DashScope Responses API (#7215)
* feat(core): add opt-in built-in web_search backed by the DashScope Responses API

Claude-Session: https://claude.ai/code/session_01KwsYFzWZ6VLCxVN8MbeFXb

* fix(core): require HTTPS for the web_search backend; fail closed on unresolved agent allow-lists

Review follow-ups on #7215: the endpoint gate now rejects plaintext
endpoints (the side request carries a bearer key), and an agent allow-list
whose names resolve to no registered tool keeps its dead entries instead of
widening to the inherited toolset — an agent restricted to the unavailable
WebSearch now runs tool-less rather than gaining shell/write.

Claude-Session: https://claude.ai/code/session_01KwsYFzWZ6VLCxVN8MbeFXb

* chore(cli): remove test-leaked debug artifacts; gitignore the leaked dirs

The CLI unit-test suites write debug logs relative to the package dir
(custom/, first/, from-env/, workspace/); a merge-commit git add swept
them in. Remove them and ignore the directories until the tests are
pointed at temp dirs.

Claude-Session: https://claude.ai/code/session_01KwsYFzWZ6VLCxVN8MbeFXb

* fix(core): honor web_search's own result budget and salvage in-stream-error partials

- Override maxOutputChars (result limit + envelope headroom) so the
  scheduler's global 25k threshold no longer slices results before the
  tool's section-aware truncation can protect URL evidence sections.
- Route in-stream backend errors through the shared terminal-failure
  tail so results streamed (and billed) before the error surface as a
  partial result, matching the transport-error path.
- Strengthen gate tests: assert gate.ok before webExtractor, exercise
  the https-only endpoint guard, and make the config mock disambiguate
  same-id entries by baseUrl like the real Config.

* fix(cli): treat whitespace-only WEB_SEARCH_API_KEY as unset

Apply the function's set-but-empty-is-unset rule to the API key env
var like every sibling env read, and add loadCliConfig coverage for
the web search settings resolution (env precedence, empty-env
fallthrough, base-URL key selection).

* fix(core): salvage failed-terminal web_search results and name the exact endpoint disqualifier

- Route the failed/cancelled terminal paths through the shared
  terminal-failure tail so search evidence streamed (and billed) before
  the backend gave up is salvaged, consistent with the in-stream-error
  and transport-error paths; regression test included.
- Classify base-URL gate rejections so the startup notice blames the
  actual disqualifier: a plaintext-HTTP endpoint now gets an "use
  https://" notice at both the env-declared and modelProviders sites
  instead of the misleading "non-DashScope endpoint" text.
- Cover WEB_SEARCH in the speculation boundary-tools test and the US
  regional host in the DashScope provider test — both behavioral
  changes this PR introduced without direct test coverage.

* test(cli): cover web search suppression in safe and bare modes

The bareMode/safeMode guard is the escape hatch that keeps web search
(external, billed API calls) off in troubleshooting modes; assert that
an enabled settings config resolves to no web search settings under
--safe-mode and --bare.

* fix(core): parse the search model selector once for both gate paths

A selector written for the modelProviders path ("openai:<model-id>", as
the gate's own OAuth notice suggests) was sent verbatim to DashScope
when WEB_SEARCH_BASE_URL overrode the backend, failing with
InvalidParameter. Hoist the resolveModelId parse above the env branch
so both paths share one interpretation of the selector.

Also cover two review gaps: the Claude extension WebSearch tool mapping
and the ACP startup-warning emission that surfaces WebSearch
misconfiguration notices in the client log.

* fix(core): handle response.cancelled in the web_search terminal-event switch and trim gate env keys

- Add response.cancelled to the terminal-event switch so the
  status === 'cancelled' handler is reachable instead of dead code
- Trim API key env vars in the gate (all three check sites), matching
  the CLI-side whitespace rule from 302cf3bb7
- Add tests: cancelled with/without prior search, whitespace-only env
  key rejection, schema getter month/year embedding

* fix(core): cap opened URLs, suppress failed-item progress, note retry budget (#7215)

* fix(core): reject unresolved selector on env-declared web_search path (#7215)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-21 10:59:36 +00:00
顾盼
cb48309256
fix(mcp): add opt-in model payload filtering (#7413) 2026-07-21 09:49:04 +00:00
Changxiao Ruan
c8b23220a2
fix(transcript): mark dangling tool history incomplete (#7340) 2026-07-21 09:26:56 +00:00
Shaojin Wen
2709d1fccd
feat(web-shell): surface worktree isolation in the new-session empty state (#7365)
* feat(web-shell): surface worktree isolation in the new-session empty state

The worktree-isolated session entry was buried in the sidebar git-branch
pill dropdown, making it hard to discover. Add a visible toggle to the
chat empty state — the de-facto new-session page — that reuses the
existing pending-worktree state machine and lazy session creation, so no
SDK or daemon changes are needed. Enabling it shows the pending badge
with a cancel affordance; the first prompt then creates the session in an
isolated worktree. The toggle is offered only when the target workspace
is trusted and is a git repository, mirroring the sidebar entry gating.

Also simplify the sidebar git pill: drop the now-redundant "New worktree
task" item and make the pill open the changes view directly instead of a
single-item dropdown.

* chore: add PR verification screenshots for the worktree toggle

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(web-shell): capture the new-session empty state in the visuals suite

The worktree toggle lives in the new-session empty state, and every visuals
scenario navigates to /session/:id via gotoSession — so the suite had never
rendered the empty state at all, and the before/after preview reported "no
screenshot changes" for this PR despite the new UI.

Add a `gotoNewSession` harness helper (primes the theme, lands on `/`, asserts
the theme took effect; no replay to settle) and a `worktree empty state`
scenario using the git-ready workspace this PR already made mockable
(`gitStatus` + the /workspaces/:cwd/git route). It captures both states — the
offered toggle and, after clicking, the pending-worktree badge with its cancel
affordance — and asserts the swap, so a regression fails an assertion rather
than only differing in the screenshot. All four captures are byte-stable
across runs (0% pixel diff).

The helper also closes the structural gap: any future empty-state work
(onboarding copy, first-run affordances) now has a way into the preview.

* refactor(web-shell): drop dead worktree session opt; click-test git chip (#7365)

* fix(web-shell): address review feedback on worktree toggle (#7365)

- Move focus to cancel button on toggle enable and back on cancel (a11y)
- Include branch name in git-pill button aria-label (a11y)
- Replace hardcoded flush() ticks with vi.waitFor() in test helper
- Move git-repo mock default from afterEach to beforeEach
- Add test: sidebar New chat clears pending worktree intent

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-21 07:38:33 +00:00
jinye
276094ae67
fix(core): Fence concurrent ACP session writers (#7237)
* fix(core): fence concurrent ACP session writers

Add an ACP-only cross-process writer lease, authoritative reload, append fencing, turn admission, and acknowledged close semantics for persisted sessions.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): add session writer diagnostics

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: address session writer review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: preserve session cleanup failures

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): restore legacy recorder coverage (#7237)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): bound live session close gates

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(serve): isolate slow session close

Keep reclaim cleanup failures from rolling back an acquired writer lock, and let a slow per-session close finish without reaping multiplexed sibling sessions.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-21 07:36:07 +00:00
jinye
636971efad
perf(telemetry): lazy-load the SDK and split OTLP exporter chains by protocol (#7276)
* perf(telemetry): lazy-load the SDK and split OTLP exporter chains by protocol

* fix(telemetry): close lazy SDK init/shutdown races and make load failure non-fatal

Addresses PR #7276 review feedback: shutdown now awaits an in-flight init before tearing down (was racing past the sync flag and leaking a started SDK whose buffered spans/logs never flushed); the dynamic imports now sit inside init's try so a chunk-load failure degrades telemetry instead of aborting daemon runtime startup. Also breaks the sdk<->sdk-impl import cycle via a leaf otlp-urls module, hardens the sdk-node exporter stub for thenable/interop probes with a unit-tested separator-independent resolve, lists the HTTP exporter packages explicitly in the bundle guard, and adds lazy-init lifecycle tests.
2026-07-21 07:35:30 +00:00
Edenman
c33ca7227a
fix(web-shell): restore scheduled task reference interactions (#7313)
* fix(web-shell): restore scheduled task reference interactions

* chore(web-shell): remove PR screenshot artifact

* fix(web-shell): refine scheduled task tag removal

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
2026-07-21 07:10:26 +00:00
Dragon
22963d5777
feat(core): add fork_turns to fork subagents (#7346)
* feat(core): add fork_turns to subagents

* fix(core): preserve nested agent context inheritance

* fix(core): isolate inherited subagent history

* refactor(core): scope fork_turns to fork agents

* test(core): cover zero-real-turns branch in selectForkHistory

Add a regression guard asserting selectForkHistory returns [] when a
numeric fork window finds no real user turns after the synthetic prefix
(e.g. only startup context present). This pins the
realUserTurnIndexes.length === 0 branch so a future refactor cannot
silently return the full history instead of an empty selection.

* docs(core): address fork_turns review feedback

- Explain the curated vs uncurated history split between the fork_turns
  'all' and numeric paths in createForkSubagent.
- Document why includeCompressed is load-bearing in selectForkHistory.
- Gate the 'forks inherit ...' prose in the Writing-the-prompt section
  behind isForkSubagentEnabled so non-interactive sessions no longer
  advertise fork behavior, and lock it with description assertions.

* test(core): cover fork_turns 'all' and getHistoryForForkWindow fallback

Add two integration tests for prepareForkConfig fork-history selection:

- 'all' path: verify getHistoryShallow(true) sources the curated history
  and selectForkHistory(history, 'all') seeds the fork with the full
  history verbatim.
- numeric path: verify the getHistoryForForkWindow?.() ?? getHistory(true)
  fallback still produces a correct bounded window (startup + latest real
  turn) when getHistoryForForkWindow is unavailable.

* fix(core): use uncurated history for fork bounded-window fallback

The numeric fork_turns path falls back to geminiClient.getHistory(true)
when getHistoryForForkWindow is unavailable. Curated history coalesces
the leading startup reminder into the first real user turn, so
getStartupContextLength can no longer detect it as a pure prefix.
selectForkHistory then leaves the startup text embedded in the first
selected turn while the startupContext prefix is prepended separately,
duplicating startup context in the fork's initial messages.

Fall back to uncurated getHistory() instead, which keeps the startup
reminder as its own pure entry that selectForkHistory strips cleanly.
Update the fallback-path test to assert the uncurated call and document
why curated history is unsafe here.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-21 07:08:12 +00:00
易良
10f14547a3
fix(cli): update npm installs safely in background (#7322)
* fix(cli): update npm installs safely in background

* refactor(cli): simplify managed npm updates

* refactor(cli): simplify background update completion

* fix(cli): align managed updates with npm package layout

* fix(cli): harden managed update lifecycle

* fix(cli): invalidate managed updates after reinstall

* fix(cli): validate staged npm updates

* fix(cli): match home env parsing in launcher

* fix(cli): harden managed background updates

* fix(cli): bound managed npm updates

* fix(cli): keep update notices live

* test(cli): avoid ctime race in update test

* fix(cli): gate screen reader update notice

* fix(cli): show update notice on agent tabs

* fix(cli): preserve managed update stderr

* fix(cli): address managed update review

* fix(cli): handle tilde qwen home without homedir

* test(cli): make managed update paths portable

* fix(cli): tolerate unavailable home directory
2026-07-21 06:32:24 +00:00
顾盼
2ad63e7ca3
fix: ask when auto classifier is unavailable (#7331) 2026-07-21 03:27:17 +00:00
qwen-code-dev-bot
45d1eb6aa4
feat(serve): make ACP initialize handshake timeout configurable (#7246)
* fix(channels): exclude discrete messages from replies

* feat(serve): make ACP initialize handshake timeout configurable

Add --initialize-timeout-ms CLI flag to qwen serve, wiring it through
to BridgeOptions.initializeTimeoutMs. The ACP initialize handshake
defaults to 10 s (DEFAULT_INIT_TIMEOUT_MS); containerized deployments
where the child process needs longer can now raise the ceiling without
patching the source.

Fixes #7244

* fix(serve): wire initializeTimeoutMs to fast-path parser and embed bridge

Add the missing NUMBER_OPTIONS entry in fast-path.ts and forward
initializeTimeoutMs in the server.ts inline createAcpSessionBridge
call so the direct-embed / test path also respects the flag.

* fix(serve): address review — fast-path test, timer upper bound, revert #7223, docs (#7246)

* test(cli): add happy-path propagation test for initializeTimeoutMs (#7246)

* refactor(cli): reuse isPositiveIntegerMs for initializeTimeoutMs validation (#7246)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-20 23:07:51 +00:00
callmeYe
414d7d162b
fix(web-shell): respect voice enabled setting (#7345)
* fix(web-shell): respect voice enabled setting

* fix(web-shell): abort capture when voice is hidden
2026-07-20 22:25:35 +00:00
samuelhsin
9e822d6004
feat: support workspace display names (#7179)
* feat(sdk): support workspace display names

* docs: add Web Shell screenshot

* feat(web-shell): add workspace display names

* fix(serve): harden workspace display name updates

* refactor(serve): simplify workspace display names

* fix(serve): validate trimmed workspace display names

* feat(serve): add workspace update API

* docs(serve): clarify workspace display name null handling

* docs(sdk): list addWorkspace in daemon client methods
2026-07-20 15:16:44 +00:00
Shaojin Wen
ff350a16a7
feat(web-shell): add git commit history browser (#7204)
* feat(web-shell): add git commit history browser

Add a read-only Git Log dialog to the Web Shell, accessible via /log
command or the History tab in the Changes dialog.

Full-stack implementation across core, daemon, SDK, and web-shell:
- core: fetchGitLog (paginated commit list) and fetchGitCommitDetail
  (message body + per-file numstat) with 12 integration tests
- daemon: GET /workspace/git/log and /workspace/git/log/commit routes
  with bound + qualified dual registration
- SDK: DaemonGitLog/DaemonGitCommitDetail types and client methods
- web-shell: GitLogDialog with commit list, expandable details,
  SHA copy icon, Load more pagination, and Changes/History tab
  switching in both dialogs

* fix(web-shell): address review feedback on git log browser

- Critical: use first-parent diff for merge commits (diff-tree without
  -c or explicit parent outputs nothing for merges)
- Remove dead embedded prop from GitDiffDialog and GitLogDialog
- Replace span role=button with aria-hidden for copy icon (a11y)
- Add loadMore error feedback instead of silent catch
- Refresh relative timestamps every 60s (useState + interval)
- Remove dead branch param from subtitle i18n call

* fix(web-shell): address R2 review feedback on git log browser

- Use bounded split in parseLogFields (first 7 separators) to prevent
  subject containing literal \x1f from shifting the parents field
- Add SHA hex format validation at daemon route layer (400 for invalid)
- Add rendering branch for detail.available === false (error message
  instead of empty content)

* fix(web-shell): count renamed files in commit detail + test git-log route & dialog

Follows the R2 review-feedback commit (which fixed the bounded parse, the
non-hex SHA 400, and the unavailable-detail render). Remaining items:

- Commit detail counts renamed files. diff-tree is plumbing and does not
  honour diff.renames, so a `git mv` split into a delete + add pair (or an
  empty-path entry) instead of one file — understating filesCount /
  linesAdded / linesRemoved. Run diff-tree with -M and give the inline
  numstat parser the same pending-rename state machine as parseGitNumstat,
  so a rename is one file keyed by its new path. Covered by a real-repo
  rename test, plus a merge-commit test that locks the first-parent diff.
- Tests for the two previously-untested modules: the workspace-git-log
  route (list shape, pagination clamping, sha-required + non-hex 400, trust
  gating) and GitLogDialog (all five list state paths, load-more offset +
  error, detail expand + both failure branches incl. available:false, and
  the relative-time render).

* fix(web-shell): address R3 review feedback on git log browser

- Fix timeAgo '0y ago' for commits ~360-364 days old (Math.max(1, ...))
- Add .catch() to clipboard writeText to prevent unhandled rejection
- Reset loadMoreError on initial re-fetch (daemon reconnect)
- Preserve prev.available in loadMore merge instead of overwriting
- Add ARIA tab semantics (role=tablist/tab, aria-selected) to both
  Changes and History tab bars

* fix(web-shell): address R4 review feedback on git log browser

- Fix vacuous limit-clamp test: seed 3 commits, verify limit=2 returns
  2 + hasMore, limit=0 clamps to 1
- Add CSS var fallbacks for --subtle-bg and --success-bg (dialog
  portals outside App.module.css scope)
- Extract GIT_DIALOG_SWITCH_DELAY_MS constant with doc comment
- Make copy-SHA control keyboard accessible (tabIndex, onKeyDown,
  aria-label instead of aria-hidden)

* fix(web-shell): escape apostrophe in worktree welcome string

* fix(web-shell): address git history review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(web-shell): mock git diff content in app tests

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): harden git log metadata parsing

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-20 15:02:48 +00:00
ytahdn
3586f48171
feat(web-shell): support custom slash command actions (#7267)
* feat(web-shell): support custom slash command actions

* fix(web-shell): apply slash handlers consistently

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-20 11:23:55 +00:00
Zqc
307d6d1e00
docs(cli): remove stale include directories limit (#7326)
Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-07-20 10:41:23 +00:00
jinye
2a9f678521
fix(core): Enforce Plan mode entry boundary (#7248)
* fix(core): Enforce Plan mode entry boundary

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test: Cover concurrent Plan entry siblings

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-20 04:35:22 +00:00
Shaojin Wen
0c271659df
feat(daemon): worktree-isolated sessions for parallel tasks (#7221)
Add support for creating sessions in isolated git worktrees from the
Web Shell, enabling multiple tasks to run in parallel within the same
workspace without polluting the main working directory.

Daemon:
- POST /session accepts optional worktree param, creates worktree via
  GitWorktreeService, relocates session via changeSessionCwd
- Worktree metadata persisted in SessionEntry, BridgeSessionSummary,
  and sidecar file (<sessionId>.worktree.json) for daemon restart
  recovery
- GET /workspaces/:workspace/git supports ?cwd= for worktree-scoped
  git status queries (path.resolve + containment check)

SDK:
- CreateSessionRequest/DaemonSession/DaemonSessionSummary gain
  worktree field; DaemonSessionClient exposes worktree getter
- WorkspaceDaemonClient.workspaceGit() accepts optional cwd param

Web Shell:
- Workspace branch pill dropdown offers 'New Worktree Task' (git repos
  only) with purple GitForkIcon and description
- Git chip turns purple with GitForkIcon for worktree sessions
- Session list shows inline ⑂ badge for worktree sessions
- Empty-state welcome badge explains worktree isolation
- Git status queries target worktree path, not workspace root
- session_cwd_changed event filtered from chat transcript

Design doc: docs/design/2026-07-19-webshell-worktree-sessions.md
2026-07-19 23:47:03 +00:00
jinye
59042ecd22
feat(core): Route Plan-mode shell commands by safety (#7172)
* feat(core): route Plan-mode shell commands by safety

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7172)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7172)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7172)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-19 16:00:22 +00:00
jinye
2998e6fe28
feat(core): inspect persisted conversation branches (#7185)
* feat(core): inspect persisted conversation branches

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7185)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): Drop metadata-only conversation branches

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): Order branches by physical terminal

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): Cover synthetic user subtypes

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-19 15:46:13 +00:00
jinye
6872b48c28
feat(daemon): Advertise ACP preheat readiness (#7200)
* feat(daemon): Advertise ACP preheat readiness

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7200)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#7200)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-19 13:00:32 +00:00
易良
c4da8d086d
fix(cli): allow goal controls during active loops (#7202)
* fix(cli): allow goal control during active loops

* fix(cli): preserve active goal control state

* fix(cli): share active goal command matching

* test(core): cover goal continuation fallback

* test(core): cover goal hook continuation reasons
2026-07-19 11:29:52 +00:00
易良
4e21f82c28
fix(ci): consolidate issue triage ownership (#7180)
* fix(ci): consolidate issue triage ownership

Resolves #4786

* chore(vscode-ide-companion): regenerate NOTICES.txt to fix CI drift

* fix(ci): close triage ownership review gaps

* fix(ci): restore need-info label cleanup

* docs(ci): document issue retriage triggers

---------

Co-authored-by: Shaojin Wen <szujobs@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-19 10:58:40 +00:00
ranzhenyu
319314dcf5
fix(mcp): normalize tool names for strict providers (#6976)
* fix(mcp): normalize tool names for strict providers

Why: MCP tool names accepted by Gemini can contain characters rejected by strict OpenAI- and Anthropic-compatible providers.
What: Normalize MCP registration and restored history names, keep invocation lookups consistent, and preserve exact legacy permission and disabled-tool entries without broadening wildcard rules.
Impact: Existing legal names remain unchanged; normalized or truncated names receive deterministic collision-resistant suffixes and stay within 63 characters.

* fix(permissions): preserve canonical path match mode

Why:
- The new toolAliases parameter shifted canonical path matching into the wrong argument position after merging main.

What:
- Pass an explicit undefined toolAliases value at both virtual shell operation call sites.

Impact:
- Restores TypeScript compilation without changing permission matching behavior.

* fix(permissions): preserve legacy MCP wildcard rules

Why:
- Provider-safe MCP names caused persisted dotted server and wildcard permission rules to stop matching.

What:
- Sanitize wildcard prefixes and server segments before matching.
- Add regression coverage for server rules, wildcard deny rules, and exact-name collision safety.

Impact:
- Restores legacy MCP permission enforcement without weakening exact-name hashing.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-19 00:19:53 +00:00
jinye
86ad532deb
perf(cli): Defer TUI runtime from ACP startup (#7182)
* perf(cli): Defer TUI runtime from ACP startup

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): Preserve state on lazy import failures

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): Colocate API error classifier coverage

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-19 00:10:15 +00:00
Changxiao Ruan
9b6da8e604
feat(webshell): replay ChatRecord history in readonly WebShell (#6999)
* feat(web-shell): add readonly transcript renderer

* feat(transcript): project chat records to daemon transcript

* chore: remove unrelated merge changes

* fix(cli): align transcript replay test mock

* fix(transcript): preserve replay metadata and todo context

Keep vision disclosures and assistant usage visible across tool replay boundaries.

Propagate plan tool-call identity through daemon projection and provide Todo contexts in WebShell so snapshots remain independent. Accept session_source records and cover the cross-layer behavior with regression tests.

* docs(transcript): translate designs and remove benchmark table

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-19 00:09:44 +00:00
jinye
67e581aeba
feat(cli): Add bounded daemon log rotation (#6969)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* feat(cli): add bounded daemon log rotation

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#6969)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#6969)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#6969)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#6969)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#6969)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-18 16:18:58 +00:00
jinye
af6a9b640c
refactor(core): Classify shell safety as read-only, write, or unknown (#7053)
* refactor(core): Classify shell safety as read-only, write, or unknown

Introduce an internal tri-state shell safety fact layer while preserving the legacy boolean fallback and keeping Plan routing unchanged.

Refs #6949

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): Treat gawk indirect calls as unknown

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-18 14:01:33 +00:00
Nothing Chan
357619a5fc
fix(channels): scope pairing and allowlist state by workspace (#7065)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(channels): scope pairing and allowlist state by workspace

PairingStore keyed its on-disk files by channel name alone, under the
global ~/.qwen/channels/ directory. Two workspace-scoped channel
configurations using the same channel name therefore shared pairing
requests and allowlist entries: a sender approved for workspace A was
implicitly approved for workspace B — an authorization-boundary
violation in multi-workspace daemon deployments.

PairingStore now takes the channel's workspace cwd and stores state
under channels/<basename>-<sha256[:12]>/, ChannelBase passes
config.cwd, and the pairing CLI commands gain a --cwd option
(defaulting to the current directory) so list/approve address the same
workspace-scoped store the channel worker uses.

Migration is a conservative one-time grandfather: on first scoped use,
existing legacy global files are COPIED into the scope (so
already-approved senders stay approved and other workspaces can
grandfather the same baseline later), after which the stores diverge —
no ongoing cross-workspace sharing, and legacy content can never
overwrite scoped state.

Fixes #7017

* fix(channels): canonicalize scope identity and gate migration per directory

Address the review findings on #7065:

1. Scope identity now follows the repo's workspace-canonicalization
   contract: getWorkspaceScopeDirName realpaths the resolved path (with
   the same ENOENT fallback as acp-bridge's canonicalizeWorkspace, which
   channel-base mirrors locally to stay dependency-free). Symlinked and
   platform-case-variant spellings of one directory — macOS /tmp/ws vs
   /private/tmp/ws — now address the same store from a daemon worker
   and from the CLI's --cwd.

2. Legacy grandfathering is gated at the scope-directory level instead
   of per file: once the scoped directory exists, legacy files are
   never consulted again. A per-file gate let a legacy allowlist
   silently re-approve senders an operator had revoked by deleting the
   scoped allowlist file, and let an in-use scope absorb a legacy file
   that appeared later. The README now spells out that revocation means
   removing entries, not deleting files.

3. The empty `pairing list` output names the workspace scope and points
   at --cwd, mirroring the approve error, since a scope mismatch
   surfaces there first.

Four new regression tests (symlink collapse, ENOENT fallback, no
resurrection after revoke, no late-legacy absorption) fail on the
previous commit and pass here.

Refs #7017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(channels): state the broad realpath fallback is intentional; make the ENOENT scope assertion meaningful

Two round-2 review notes on #7065:

- canonicalizeWorkspacePath's docblock claimed to match acp-bridge's
  ENOENT-only fallback while the catch swallows every realpath error.
  Keep the broad catch — pairing storage is best-effort and a transient
  FS error must not stop the channel from starting — and document that
  divergence explicitly instead.

- The ENOENT-fallback test's second assertion compared a scope name to
  itself. It now compares against the scope computed from the resolved
  spelling, pinning that the realpath step degrades to a no-op for
  nonexistent paths.

Refs #7017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(channels): always close the migration gate, normalize nonexistent-path scopes, copy atomically

Address the automated round-2 inline findings on #7065:

- The migration gate is now closed on the very first construction even
  when no legacy files existed: the scope directory itself is the
  "migration decided" marker. Previously a workspace that first ran on
  new code before any legacy state existed left the gate open, and a
  legacy allowlist written later by an older version still running
  concurrently would have been absorbed.

- resolvePath now runs every input through path.resolve, so
  trailing-separator and dot-dot spellings of a path that does not
  exist on disk (where the realpath step cannot help) canonicalize to
  the same scope instead of three different ones.

- Legacy files are copied via temp file + atomic rename, so a crash
  mid-copy cannot leave a truncated scoped file behind the now-closed
  gate, and a concurrent first construction cannot observe a
  half-written allowlist.

Adds three regression tests (late-legacy not absorbed after empty
first startup, nonexistent-path spelling collapse, unreadable legacy
file keeps the constructor best-effort); the first two fail on the
previous commit.

Refs #7017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(channels): encode channel names in scoped paths, gate migration per channel, wire tests into CI

Three independently reproduced problems in the workspace-scoping change,
found in external review of d8c155ab3:

- Channel names come from unrestricted config keys and were joined into
  the scoped path verbatim, so a name like `../support` climbed out of
  the scope directory and landed every workspace on one shared file at
  the channels root — silently undoing the isolation this PR exists to
  establish. File names now URI-encode the channel name (mirroring
  GroupHistoryStore), common names encode to themselves, and the legacy
  source path is containment-checked as defense in depth.

- The directory-level migration gate let only the FIRST channel of a
  workspace migrate: one process starts several channels in turn, and
  once the first construction created the scope directory, every other
  channel's legacy state was skipped forever. The gate is now a
  per-channel `<channel>.migrated` sentinel inside the scope directory,
  written even when there was nothing to copy.

- A single unreadable legacy file aborted the whole migration loop and
  the gate still closed, so the other (valid) file was never migrated
  and never retried. Files are now copied independently, best-effort,
  via uniquely-named temp files + atomic rename, and scoped files are
  never overwritten.

Also adds the missing test/test:ci scripts to channels/base (matching
its sibling packages), so the package's 784 tests actually run in CI's
`npm run test:ci --workspaces --if-present` sweep.

Four new regression tests (traversal-name isolation, multi-channel
migration, late-channel migration, unreadable-file independence) all
fail on d8c155ab3 and pass here.

Refs #7017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(channels): read legacy files under the raw name, retry partial migrations, log failures

Round-3 review findings on #7065:

- Legacy sources are read under the RAW channel name again: pre-scoping
  code wrote them unencoded, so looking them up under the encoded name
  made any channel whose name changes under encoding (e.g. "my
  channel") skip its legacy state and permanently lose approved
  senders behind the sentinel. Encoded names remain in use for the
  scoped destinations; the containment check keeps traversal-style raw
  names from reading outside the channels root.

- The sentinel is only written when every present legacy file was
  copied (or already existed). A partial failure (ENOSPC, transient
  I/O) previously closed the gate with incomplete state; now the next
  construction retries the failed file, and per-file stderr warnings
  are emitted so operators can see why senders are missing instead of
  instrumenting the constructor.

- The symlink test cleans up with unlinkSync — rmSync throws EISDIR
  for a symlink to a directory on macOS.

- The pairing CLI gains tests covering --cwd scoping end to end (list
  isolation, empty-scope hint, approve scoping, cross-workspace code
  rejection), plus an explicit return after the mocked-in-tests
  process.exit(1).

The raw-name and partial-retry regression tests fail on 954e76af4.

Refs #7017

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:33:26 +00:00
Shaojin Wen
582fb49603
feat(web-shell): git status chip, visual working-tree diff, and sidebar git status (#7054)
* feat(web-shell): git status chip, visual working-tree diff, and sidebar git status

Bring working-tree Git awareness to the Web Shell (browser daemon session UI):

- Toolbar branch chip becomes a live status indicator: dirty (staged/unstaged/
  untracked), ahead/behind upstream, stash count, detached HEAD, in-progress
  operation (merge/rebase/cherry-pick/revert/bisect), and conflict count, each
  with a non-color cue.
- Read-only "Changes" dialog: working-tree-vs-HEAD file list with per-file,
  line-level, per-side syntax-highlighted diffs; opens via /diff or a dirty
  chip; untracked files expand as fully-added and deleted files still diff.
- Per-workspace git status in the sidebar: a compact icon-only chip per trusted
  workspace (status dot + hover tooltip); click opens that workspace's dialog.

All git access goes through the daemon REST API with per-workspace trust
gating; new SDK status fields are optional and additive (v2).

* fix(web-shell): themed tooltips and git-chip review follow-ups

Tooltips now render on the themed popover surface (bg-popover /
text-popover-foreground / border + fill-popover arrow) instead of the
inverted bg-foreground default, so they read dark-on-dark rather than a
bright box on the dark theme. Fixing the shared primitive corrects the
git branch tooltip in the composer toolbar and sidebar, plus every other
tooltip, at once.

Also addressing review feedback on the git integration:
- Replace the hand-drawn detached/conflict/stash SVG icons with
  lucide-react (CircleDot / TriangleAlert / Layers) per the web-shell
  icon convention.
- Gate the tooltip "Working tree clean" message on an enriched status
  (computedAt) so a branch-only status no longer asserts clean.
- Include the file path in the diff dialog row aria-label so screen
  readers can distinguish files.
- Reset the toolbar git chip on workspace switch so it never shows the
  previous repo's branch/counts while the new fetch resolves.
- Log a sidebar git poll failure only on the success->failure transition
  to avoid spamming a long-lived tab.
- Correct the SDK doc for DaemonWorkspaceGitDiffFile.added/removed
  (0, not undefined, for binary files).

* fix(web-shell): address git-integration review suggestions

Follow-ups from the /review pass on the git integration:

- GitBranchIndicator: include the short SHA in the detached-HEAD tooltip
  title, and add the "Working tree clean" status to the aria-label (gated
  on an enriched status, matching the tooltip) so the two never drift.
- WorkspaceSection: keep the last known git status on a transient poll
  failure instead of blanking the chip for a whole interval.
- App: surface a toast for `/diff` when no workspace is available instead
  of silently consuming the composer input.
- Tests: cover the diff dialog's list-load and per-file load error paths,
  and detectGitOperation's revert/bisect branches.
- Design doc: align the getGitWorkingTreeStatus spec text with the
  decision (transient states return status with `operation`; null is
  reserved for non-repo / git failure).

* fix(web-shell): focus-visible ring for git chip button; align doc poll interval

- Add a :focus-visible outline to .gitBranchChipButton so keyboard users
  get a visible focus indicator (the chip resets UA button chrome).
- Design doc: align the active-workspace poll-interval references at 30s
  to match the implementation.

* fix(web-shell): surface capped diffs, catch row-build failures, cover degradation paths

Address the remaining review findings on the git integration:

- Truncation is no longer silent: fetchGitDiffHunksForFile now returns
  { hunks, truncated } — the parser records files that actually lost
  lines to MAX_LINES_PER_FILE (tracked path), and the untracked
  synthesis reports its byte/line caps. The route forwards an additive
  `truncated` flag on the hunks response (absent when not truncated, so
  older clients and daemons are unaffected), and the Changes dialog
  renders a "Diff truncated" note under the visible window.
- DiffHunks catches an unexpected buildRows rejection (e.g. malformed
  hunk lines) and shows the per-file error instead of leaving an
  unhandled rejection and a silently empty diff area.
- New tests: untracked and tracked truncation at the core caps, the
  route's truncated passthrough (and its absence when clean), the
  branch-only degradation when the working-tree summary throws, the
  malformed-hunks error path, and the Shiki success path (a fake
  tokenizer proving add rows pull new-side tokens and del rows pull
  old-side tokens, not the plain-text fallback).

* fix(web-shell): drop dialog backdrop-blur that froze the page on open

The dialog and alert-dialog overlays applied `backdrop-blur-xs`, which
forces the browser to rasterize and blur the entire content behind the
overlay when a dialog opens. With a long transcript behind it, that
main-thread paint+blur froze the whole page — e.g. clicking the git
branch chip to open the Changes dialog. Keep the bg-black/10 scrim for
separation and drop the blur.

* fix(core): guard synthesizeUntrackedHunk against non-regular files

synthesizeUntrackedHunk opened an untracked path before checking its
type, so an untracked FIFO (listed by `ls-files --others`) would block
on open() forever waiting on a writer — hanging the daemon's event loop
and leaving the Web Shell Changes dialog stuck on a permanent loading
state. lstat-gate on regular files before opening, matching the existing
guard in countUntrackedLines. Adds a FIFO regression test.

* fix(web-shell,core): rename expansion, no-newline marker, chip measurement

Round-5 review Criticals:

- core: key renamed diff entries by the real (post-rename) path and carry
  the old path for display, so renamed rows can be expanded — the synthetic
  `old => new` key was sent to git as a nonexistent literal path. The diff
  dialog renders the rename as `old → new`.
- core: preserve Git's `\ No newline at end of file` marker through the hunk
  parser so a trailing-newline-only edit isn't shown as identical
  removed/added lines (the viewer already renders it as a meta row).
- web-shell: the toolbar's hidden git-chip measurement replica now renders
  the full chip content via the extracted GitBranchChipContent, so the
  expanded width includes the status indicators and the compact/expanded
  toggle no longer oscillates near the responsive threshold.

* fix(build): generate git-commit info even when prepare build is skipped

The review tooling runs `npm ci` with QWEN_SKIP_PREPARE=1 (to skip the
heavy prepare build) and then builds only the changed workspaces. Because
`prepare` exited before generating the gitignored git-commit.ts, a
per-workspace build of packages/cli failed at the unchanged systemInfo.ts
on the missing `../generated/git-commit.js` module. Generate the git-commit
info in the skip path too — it is cheap and never fails hard — so a later
per-workspace build or typecheck finds the module. The non-skip path still
generates it via `npm run build`.

* fix(web-shell,cli): address round-6 review suggestions

- cli: carry the pre-rename path (oldPath) through DiffRenderRow and show
  renamed files as `old → new` in both the Ink and plain-text renderers.
  The rename-keying fix updated the daemon and web-shell dialog but not the
  CLI `/diff` renderer, which silently dropped the old path.
- web-shell: key DiffFileRow by workspace + path so switching workspace
  remounts the row instead of reusing another workspace's hunks/open state
  for a path both workspaces share.
- web-shell: show a loading placeholder in DiffHunks while rows are (re)built
  (e.g. after a theme switch) instead of an empty, jumpily-resized box.
- web-shell: cover the /diff local intercept in App.test.tsx (opens the
  Changes dialog and is not forwarded to the agent).

* fix(web-shell,cli,core): address round-7 review suggestions

- cli: sanitize the rendered filename (and pre-rename oldPath) in the Ink
  DiffStatsDisplay via sanitizeFilenameForDisplay, matching the plain-text
  renderer so a crafted path can't inject into the interactive view.
- cli: apply the read headers before awaiting the per-file diff fetch (as
  handleDiffList does) so error responses also carry no-store/nosniff.
- cli + web-shell: strip Unicode bidi embedding/isolate controls
  (U+202A-202E, U+2066-2069) in the filename/control-char sanitizers so a
  crafted filename can't visually spoof its extension.
- core: guard countStashEntries with an lstat type check before readFile, so
  a symlink-to-FIFO at logs/refs/stash can't block the event loop (the same
  hazard already guarded in the untracked-file readers).
- core: cover fetchGitDiffHunksForFile's transient-state guard with a test
  (the sibling helpers already had one).

* fix(web-shell,cli,core): address round-8 review suggestions

- core: pass --no-optional-locks to the ls-files call in
  fetchGitDiffHunksForFile, matching the other runGit calls so it doesn't
  contend for an optional index-refresh lock alongside concurrent git
  add/commit.
- cli: add a route test asserting a rename's oldPath survives serialization
  end-to-end (keyed by the new path, old path carried alongside).
- web-shell: add a GitDiffDialog test for the hiddenCount>0 "N more files
  not shown" note (every payload previously used hiddenCount: 0).
- web-shell: drop the nonexistent primaryLabel prop from the WorkspaceSection
  test (it is not a WorkspaceSectionProps member).
- docs: correct the plan doc — large-diff virtual scrolling was explicitly
  descoped (core caps + per-file lazy loading), not implemented in Phase 2.

* fix(cli,web-shell): address round-9 review findings

- cli: propagate the pre-rename oldPath through DiffDialog's
  perFileToUnified and render renamed files as `old → new` in the
  interactive diff viewer (the rename-keying fix had updated the daemon,
  the web-shell dialog, and the /diff stats, but not this viewer).
- cli: cover DiffStatsDisplay's rename (`old → new`) rendering and the
  sanitizeFilenameForDisplay path for hostile filenames carrying control
  characters.
- web-shell: guard the GitBranchIndicator test afterEach against
  double-unmounting an already-unmounted root (the localization tests
  assert on getTranslator without calling render()).

* fix(core,cli,web-shell): rename-aware single-file diff (old→new)

fetchGitDiffHunksForFile pathspec-limited the diff to the new path, which
defeats git's rename detection — a renamed file was reported as fully
added (every line +) instead of its actual edit. Thread an optional
pre-rename path through the single-file endpoint (core → route → SDK →
dialog) and diff old→new with -M when it is present, so expanding a
renamed file shows its real content change.

* fix(cli): address round-10 review suggestions

- DiffDialog: split the path-width budget between old and new paths for a
  rename (reserving the " → " separator) so the combined width stays within
  maxPathChars instead of overflowing the row layout.
- textUtils: extend MULTILINE_CONTROL_CHARS_REGEX with the Unicode bidi
  ranges (matching FILENAME_CONTROL_CHARS_REGEX) and add a test that
  sanitizeFilenameForDisplay strips bidi embedding/isolate controls.
- workspace-git-diff route: add a test that ?oldPath= is parsed and
  forwarded to fetchGitDiffHunksForFile.

* test(sdk),docs: cover diff client methods; align design doc

- sdk: add DaemonClient unit tests for workspaceGitDiff() and
  workspaceGitDiffFile(path, oldPath?) — URL construction (incl. urlEncode
  on path/oldPath, with and without oldPath, plus the workspace-qualified
  route) and response deserialization, mirroring the existing workspaceGit()
  test.
- docs: add the oldPath? param to the workspaceGitDiffFile API spec; record
  that the diff client methods now have unit tests (correcting the claim
  that workspaceGit() had none); attribute the bundle-limit bump to
  packages/sdk-typescript/scripts/build.js; clarify ahead/behind are relative
  to upstream (0, and ↑N/↓N not shown, without one).

* fix(web-shell,core): address round-11 review suggestions

- GitBranchIndicator: count conflicted entries as dirty — a merge where every
  changed file is conflicted (staged=unstaged=untracked=0) is still
  uncommitted, so the expanded chip's dirty dot / data-dirty now reflect it.
- core: split the status branch line at the last "..." (the branch/upstream
  separator) so a dotted branch name isn't truncated at the first "...".
- GitDiffDialog: guard DiffFileRow's in-flight fetch against unmount via a
  cancelled ref, matching DiffHunks / GitDiffDialog.
- tests: forward oldPath when expanding a renamed file in the web-shell
  dialog; bidi-strip coverage for the web-shell sanitizeControlChars;
  untrusted-guard coverage on the single-file diff route; conflicted-only
  dirty; branch-line "..." split.

* fix(web-shell,cli): address round-12 review suggestions

- DiffDialog: only render the rename "old → new" when there's room for both
  sides (≥19 cols, so each gets ≥8); otherwise fall back to the new path
  alone, so a narrow terminal no longer overflows the row (the Math.max(8,…)
  floor could exceed maxPathChars).
- GitBranchIndicator test: guard afterEach container.remove() for non-render
  tests run in isolation, and make the compact-mode ↑-suppression assertion
  non-vacuous by giving the fixture an ahead count.
- App: compute the active workspace once (useMemo) and share it between the
  git-status effect and the Changes-dialog entry point, so the chip and the
  dialog can't drift onto different repos.

* fix(core,docs): address round-13 review suggestions

- core: add a rebase-apply detection test (git am / an interrupted
  `rebase --apply` creates rebase-apply, which detectGitOperation also maps
  to 'rebase'); previously only rebase-merge was exercised.
- docs: correct section 5 to describe the actual diff-dialog mechanism
  (diffWorkspaceCwd state, not the stale activePanel design).

* test(core): cover stray no-newline marker before any hunk header

parseGitDiff's pre-hunk guard already skips a "\ No newline at end of
file" marker that appears before any @@ header, so a malformed/truncated
diff can't throw on a null currentHunk and lose subsequent files' hunks;
add a regression test pinning that behavior.

* fix(web-shell): unstick per-file diff loading and skip non-path git poll

- DiffFileRow: reset the cancelled-fetch flag on mount so StrictMode's
  mount/unmount/mount replay no longer leaves it latched at true, which
  dropped the fetched hunks and froze the row on "Loading changes…" despite
  a 200 response.
- WorkspaceSection: skip the git status poll when the workspace cwd is not an
  absolute path. A synthetic fallback workspace carries a display name there,
  which the cwd-qualified route rejects with a 400.

* fix(web-shell,cli): address review suggestions on the git diff surface

- GitDiffDialog: highlight each diff side independently so a small side
  keeps syntax highlighting even when the other side exceeds the size cap
  (the old guard dropped both as soon as either was too large).
- ChatEditor: complete the .gitBranchChipButton reset (font/color/padding/
  margin) so the clickable dirty-tree chip matches the read-only output chip
  instead of picking up UA button styling.
- DiffDialog: cover the interactive rename display (old to new on a wide
  terminal), mirroring the rename tests DiffStatsDisplay and GitDiffDialog
  already have.

* test(web-shell,cli): cover git chip clean/reload/traversal paths, fix doc

- GitDiffDialog: add the missing expect(header).not.toBeNull() guard to the
  three expand-file tests that lacked it, matching the others in the block.
- GitBranchIndicator: cover the known-clean aria-label branch (computedAt set
  and every change counter zero).
- WorkspaceSection: verify a reloadToken change re-fetches git status instead
  of waiting for the next 60s poll.
- workspace-git-diff route: verify a traversal oldPath is forwarded to core
  and surfaced as available:false rather than escaping the workspace.
- Design doc: /diff is handled via setDiffWorkspaceCwd, not setActivePanel.

* fix(core): allow literal `..foo` paths in diff normalization

- toRepoRelativePath: reject only a real climb-out (`..` or `../…`), not a
  literal `..foo` filename at the repo root, which the bare startsWith('..')
  over-rejected, leaving the diff viewer unable to render such a file.
- parseGitDiff: cover the truncatedPaths output set directly (it was only
  exercised indirectly through fetchGitDiffHunksForFile).

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-18 10:06:07 +00:00