[pull] main from QwenLM:main - #536
Merged
Merged
Conversation
…10485) * fix(sdk): stop emitting a duplicated hashbang in the serve-mcp bin esbuild already carries the entry point's own `#!/usr/bin/env node` into the bundle, so the extra `banner` stacked a second hashbang onto line 2 of `dist/daemon-mcp/serve-bridge/bin.js`. That is valid to esbuild and a `SyntaxError` to node, which makes the published `qwen-serve-mcp` bin fail to start through both `node <file>` and the shebang. The bin has never been runnable since it was introduced, and `@qwen-code/sdk@0.1.8` ships it broken. Drop the redundant banner and assert at build time that the bin starts with a hashbang and that `node --check` can parse it, so neither failure mode can be published again. Nothing else emits a hashbang banner in this repo; every other bin already relies on its entry point's own. * fix(sdk): harden the serve-mcp bin guard and pin the artifact in tests Two review follow-ups on the duplicated-hashbang fix. `assertExecutableBin` ran `node --check` through `execSync` with a command string, so the path went through `/bin/sh -c` — and `JSON.stringify` is JSON quoting, not shell quoting. A checkout under a directory containing `$(…)`, a backtick or `$VAR` executed the embedded command as the build user and then failed the build with a misleading `does not parse`. The argv form of `execFileSync` needs no quoting at all. Nothing in the suite covered the guard, so a later edit that swallows its error or drops the call would leave every test green while the next banner regression publishes. Extract the bin's esbuild options into a module the build script and a new test share, and assert on the emitted bytes: exactly one hashbang, on line 1, and `node --check` parses. That reds on a banner regression whatever happens to the guard. A second case pins the shipped `dist/` artifact, catching a banner re-added at the call site rather than in the shared options; `npm ci` builds it through the root `prepare` script, and it skips on a tree without a build rather than failing. * fix(sdk): declare the module goal for the serve-bridge bin tmpdir check The bin test's node --check ran against a bare temp dir, where the ESM parse goal rests on Node's module-syntax detection — default-enabled only from 22.7, while the package supports >=22.0.0. On Node 22.0-22.6 the check dies with 'Cannot use import statement outside a module' even though the built bin is correct. Write {"type": "module"} beside the bundle so the goal is explicit across the whole engines range; the outfile stays bin.js to match the shipped bin mapping. Verified with --no-experimental-detect-module: the case passes with the fixture and reds without it. * fix(sdk): derive the shipped-bin path from the package manifest The dist/ case's skipIf guard read a hardcoded restatement of the bin.qwen-serve-mcp mapping, so relocating the bin output — updating package.json and scripts/build.js but not the test constant — would turn the only suite-level check of the shipped bytes into a permanent silent skip. Resolve the path through package.json's bin map instead: on a consistent rename the case follows the manifest and keeps running, while a tree without a built dist/ (QWEN_SKIP_PREPARE installs) still skips rather than fails.
* feat(cli): hot-reload modelProviders without session restart Add a SettingsWatcher listener (modeled on registerMcpHotReload) that reloads the model registry via Config.reloadModelProvidersConfig() when the merged modelProviders change, so /model picks up new providers without restarting the CLI (#10568). providerProtocol stays boot-frozen because it is requiresRestart in the settings schema. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): retry modelProviders hot-reload when the rebuild throws Advance the last-applied snapshot only after reloadModelProvidersConfig succeeds; the watcher's Promise.allSettled swallows listener errors, so advancing first would silently drop the edit until the next unrelated change. Also log under a dedicated MODEL_PROVIDERS_HOT_RELOAD namespace instead of MCP_HOT_RELOAD. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): gate modelProviders hot-reload on applied registry state Review-gate findings on the hot-reload listener: - Diff against the registry's applied modelProviders (new Config.getModelProvidersConfig(), backed by ModelRegistry) instead of a listener-local snapshot, mirroring how registerMcpHotReload diffs against getSettingsMcpServers(). Out-of-band registry rewrites (provider-template updates, ACP session reloads) can no longer desync the gate and silently skip a later edit. - Reconcile once at registration so an edit that lands between startWatching() and listener attach (during loadCliConfig) is applied instead of silently kept out of the registry. - Surface a throwing reload via AppEvent.LogError like the MCP listener; applied state is unchanged on failure so the next event retries. - Emit a one-shot restart notice when providerProtocol drifted from its boot value, so the restart-only half of a combined edit is never silent. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): diff providerProtocol drift against applied registry state Second review-gate pass: - Compare the on-disk providerProtocol against the registry's APPLIED map (new Config.getProviderProtocolConfig() passthrough) instead of a registration-time snapshot, so a combined edit landing before the listener attaches can no longer keep the restart notice permanently silent, and an out-of-band protocol reload (ACP) no longer triggers a spurious one. - Make the drift notice edge-triggered: a second independent drift (drift -> revert -> drift) notifies again; the check runs before the modelProviders gate so protocol-only events still update the edge state. - Pin ModelRegistry's applied-state copies with regression tests (reloadModels dropping the copy would silently rebuild the registry on every settings event). - Document the hot-reload vs restart boundary in model-providers.md. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): stub model-provider config getters in llm.test.tsx mocks main() now registers modelProviders hot-reload, whose reconcile() calls Config.getProviderProtocolConfig()/getModelProvidersConfig(); the inline config mocks in llm.test.tsx lacked both, failing 12 main-function tests with "config.getProviderProtocolConfig is not a function". Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): refresh active model provider after hot reload Open the debug console for hot-reload notices that previously went through the unrendered LogError event, and refresh the active content generator after a modelProviders reload so current-provider connection edits take effect without a restart. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): rearm provider protocol hot reload notice Track the last providerProtocol map that produced a restart notice so a suppressed protocol-only edit can still be reported on the next hot-reloadable settings event. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): keep hot-reload auth refresh non-interactive The watcher-triggered `refreshAuth` call carried interactive-auth semantics: for a QWEN_OAUTH session whose cached credentials are unavailable (expired/rotated refresh token, transient network error) it fell through to `authWithQwenDeviceFlow` — an unrequested device-auth prompt mid-session that also stalls ACP/headless runs. Pass `isInitialAuth=true` as boot does (`performInitialAuth`), so unavailable credentials reject into the listener's existing notice branch instead. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): pin hot-reload notice branches for auth refresh and MCP failures - Add a test where `refreshAuth` rejects after a successful registry reload, pinning the "Failed to refresh the active model provider" notice branch (removing the try/catch now fails the suite). - Extend the MCP reconcile-throws test to also assert `AppEvent.OpenDebugConsole`, so reverting that catch to a bare `LogError` emit fails the suite. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): retry failed hot-reload auth refresh on later events After a successful modelProviders registry reload, a rejected refreshAuth left a half-applied state: the reload had already advanced applied state, so the equal() gate skipped every later unchanged event and the auth refresh was never retried until another modelProviders edit or restart. Track a listener-local retry flag set in the refreshAuth catch; on subsequent events whose modelProviders are unchanged, re-attempt only refreshAuth (never the registry reload, preserving the out-of-band rewrite gate contract) and clear the flag on success. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): remove out-of-scope protocol hot-reload notices Keep modelProviders hot-reload focused on the applied registry and active client refresh. Remove the providerProtocol drift latch, dead notice plumbing, and tests for behavior that requires a restart. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(cli): fix stale test comment after notice path removal The refreshAuth catch no longer emits a notice (removed in 2e564b3); update the test comment to describe the actual failure path — debug-logged and retried on later events. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): keep hardware cursor aligned after layout changes Read the input cursor position after Ink recomputes layout so history growth and shrinkage cannot leave stale terminal coordinates. Keep clear-and-sync output geometry consistent when leaving fullscreen, preventing an extra upward cursor move during overflow recovery. Co-Authored-By: GPT-5.6 Codex <codex@openai.com> * perf(cli): snapshot lazy cursor coordinates per flush Resolve active cursor coordinates once at the start of each output flush and reuse the numeric snapshot throughout change detection and rendering. Discard the snapshot after every flush so later layout changes still resolve against fresh Yoga coordinates. Add coverage for repeated reads within a flush and freshness across consecutive flushes. Co-Authored-By: GPT-5.6 Codex <codex@openai.com> * test(cli): consolidate cursor regression fixtures Reuse one shared lazy-cursor fixture across the fullscreen-exit and layout-reflow regressions. Document why prepared cursor snapshots must be invalidated at every output-state reset. Co-Authored-By: GPT-5.6 Codex <codex@openai.com> * chore(cli): clarify cursor patch invariants Document that lazy cursor getters depend on non-null nodes always resolving coordinates. Normalize generated-side hunk offsets after the snapshot protocol comment so the Ink patch remains internally consistent. Co-Authored-By: GPT-5.6 Codex <codex@openai.com> --------- Co-authored-by: GPT-5.6 Codex <codex@openai.com>
* fix(memory): preload recall documents Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(integration): remove ACP recall timing race Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(integration): remove flaky ACP recall timing check Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* perf(ci): shard release quality checks Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): include generated templates in release artifact Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): wait for paginated loads in CI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): allow slow boot imports in CI Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(release): ratchet test-file discovery for every test:ci workspace --passWithNoTests is required so a shard that receives no files can exit 0, but it also lets a workspace that lost every test file pass in all three shards while zero tests execute; the monolithic test:release exited 1 in that case and blocked the release. Add a contract test in the test:scripts lane (quality_scripts) that fails when any test:ci workspace discovers no test file, so the release is blocked again. Discovery mirrors vitest's default include, which every workspace's test:ci config uses or narrows, so zero matches here means zero discoverable tests under vitest. Red probe: moving packages/channels/telegram's single test file out of the tree fails the new test; restoring it goes green. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthe3hev7k * fix(release): keep cancelled runs out of the quality failure gate The quality aggregate used if: always(), so a cancelled release run re-ran the result loop, read the component 'cancelled' results as not-success, and exited 1 — turning quality.result into 'failure'. That opens notify_failure's pre-existing needs.quality.result == 'failure' gate, filing a "Release Failed" issue labeled autofix/approved and dispatching autofix for a run an operator stopped on purpose. Before this PR the monolithic quality job carried no always(), so cancelled runs left quality.result='cancelled' and stayed silent. Use !cancelled() instead: cancelled runs skip the aggregate (result 'skipped', still non-success, so publish stays fail-closed), while any failed component still runs the loop and fails the gate. Pin the new gate and the notify_failure failure clause in the contract test. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthe3hev7k * refactor(release): reuse release_test_env anchor in integration jobs This PR introduced &release_test_env for the five quality jobs but left integration_none and integration_docker with literal copies of the same three OPENAI_* variables. Reuse the anchor so the seven release-test jobs share one env definition; parsed output is byte-identical for every job. Also record the PR's final release.yml size (59855 bytes) in .size-baseline: the sharding rewrite grew the file 1977 bytes past the recorded 57878, inside the 4096 allowance but past the ratchet, which exists to be bumped in the same PR as real growth. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthe3hev7k * fix(release): prune node_modules when packing build outputs The Pack Build Outputs find matched every directory named dist under packages/integrations, so npm ci's nested dependency dist dirs (72 in the current tree, e.g. packages/cli/node_modules/markdown-it/dist) were tarred into release-quality-build and re-downloaded/extracted by all five consumer jobs. Prune node_modules before matching dist, and pin the prune in the release-workflow contract test. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthmo4kw80 * refactor(release-test): reuse getWorkspacePackageJsonPaths in shard test The 'fans workspace tests into three complete Vitest shards' test hand-rolled workspace-glob expansion (plus its own !-exclusion set), duplicating the tested shared helper in scripts/workspaces.js that the zero-test ratchet already uses. Swap in getWorkspacePackageJsonPaths so both contract tests validate one implementation; behavior is unchanged (both implementations enumerate the same 26 workspaces at this ref). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthmo4kw80 * refactor(release-test): extract shared test:ci workspace selection helper The shard-completeness pin and the zero-test ratchet both selected their workspace sets through byte-identical blocks (read root package.json, getWorkspacePackageJsonPaths, filter on test:ci). Extract the selection once at describe scope so both release-gating ratchets keep gating on the same workspace list. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtht3lw98c * test(release): pin vitest as the last test:ci command in shard lanes test:release:workspaces appends --shard/--passWithNoTests to each workspace's test:ci body, so the flags only reach vitest when the vitest invocation is the last command in the chain; a trailing command would receive them while vitest ran the full suite in all three shards. Assert the last command starts with `vitest run`; commands before it (e.g. sdk-typescript's typecheck:public-surface) stay accepted. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtht3lw98c * test(release): pin needs edge, five-result mapping, and per-job skip gate R1-17: assert every checkout consumer declares prepare in needs, so the pinned ref expression cannot silently un-resolve to the event ref. R1-4: assert the aggregate's env is the exact five-result mapping and the verify loop references all five *_RESULT vars, so dropping or remapping an entry fails here instead of publishing over a failed component. R1-6: assert each of the five component quality jobs keeps its own force_skip_tests gate, not just the aggregate. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthxdxgy8k * fix(release): log packed outputs and fail closed on silent under-pack Pack Build Outputs now prints every path it packs and refuses to tar when find contributes nothing beyond the two hardcoded paths. A dropped `-o` would turn the two -prune clauses into one conjunction matching nothing; before this guard that packed an incomplete artifact with an empty log, moving the symptom to downstream consumers as missing-dist errors. Pin the guard and print in the pack-step contract test. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthxdxgy8k * fix(release): pin consumer artifact contract, dist origin, and retention R1-5: the build-once contract was pinned only on the producer side. Assert every consumer job (typecheck, workspace shards, scripts) keeps the quality_build needs edge, downloads release-quality-build via actions/download-artifact to the runner.temp path, and unpacks it with the pinned tar invocation after the download. R1-10: the repo-root dist that Pack Build Outputs ships only exists as a side effect of check:serve-fast-path-bundle (the check runs the esbuild bundle with outdir dist; scripts/build.js never writes it). Document the dependency with a step-level comment and pin the ordering plus the documented contract in the workflow test. R1-11: retention-days: 1 broke "Re-run failed jobs" more than a day later, where the succeeded producer is not re-run and consumers must still download its artifact. Raise retention to three days, update the design doc re-run story, and pin the value next to the other upload contract pins. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmthzj3978p * fix(release): skip quality aggregate when prepare never ran The aggregate's !cancelled() gate overrides the implicit needs success() check, so on forks — where prepare is repository-gated and the five component lanes follow it into skipped — the aggregate would still run, see five non-success results, and exit 1. That turns a benign all-skipped fork dispatch into quality=failure and can open notify_failure's "Release Failed" issue. Gate the aggregate on needs.prepare.result == 'success' (prepare is now declared in its needs list). Same-repo releases keep the existing fail-closed aggregation; fork runs stay fully skipped as on main. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtiej7n89k --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(web-shell): add session workflow cockpit
* feat(web-shell): add session workflow cockpit demo
* fix(web-shell): integrate cockpit with shell layout
* fix(web-shell): unify cockpit with app theme
* fix(web-shell): finish cockpit demo experience
* fix(web-shell): integrate cockpit navigation
* style(web-shell): normalize the session workflow cockpit's design tokens
The new cockpit CSS reused ad hoc border-radius (10 distinct values),
color-mix opacities, and #fff literals instead of the app's existing
--radius-sm/md/lg/xl scale, several of them papered over with
!important where the real issue was selector specificity. This
consolidates them onto a small local token set and fixes the
specificity so !important is no longer needed. Also aligns the two
completion-progress bars to --success-color (they were blue while
every other "done" signal in the same view, and the sibling
PlanExecutionView's equivalent bar, use green), and replaces a
hardcoded Chinese string in the Chat/Cockpit/Workflow nav toggle with
a proper i18n key so it renders correctly for non-Chinese locales.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat(session-workflow): complete experimental cockpit data flow
* style(web-shell): size the plan dialog to its graph and unify node edges
The plan DAG lays out fixed 240px lanes with 56px gaps, so its natural
width is 852px at three lanes and 1148px at four. The tasks dialog was
pinned to `lg` (720px), which meant any plan past two lanes scrolled
sideways inside the panel while the viewport still had room.
Add an `auto` dialog size that tracks content between a 560px floor and
a min(100vw - 2rem, 1120px) ceiling, and use it for the tasks dialog.
Responsive rather than fluid: wide graphs still scroll, but only once
the panel would outgrow the screen. `w-max` beats DialogContent's base
`w-full` through tailwind-merge, which the added tests pin -- if that
resolution ever changes the panel silently stops tracking content.
Node state styling had two problems. Status set `border-color` while
selection stacked an always-blue box-shadow ring on top, so selecting a
completed (green) or blocked (amber) node gave it a green border and a
blue ring at once. And nothing on `.node` transitioned -- the file had a
single transition rule in 466 lines -- so every state change snapped.
Drive both from one `--node-accent`/`--node-ring` pair per status, so
selection reads as the same edge turned up instead of a second colour,
and add border-color/box-shadow transitions with prefers-reduced-motion
honoured.
The blue connector dots are left alone deliberately: they belong to the
edge system, which is the same blue, so colouring them by status would
break that reading.
Note that DialogShell.module.css still defines `.panel` and `.sizeSm`
through `.sizeXl`, but DialogShell.tsx references none of them -- width
is entirely Tailwind on DialogContent. That dead CSS predates this
change and is left as-is.
Not verified visually: Playwright browsers are unavailable in this
environment and jsdom has no layout engine, so the width behaviour is
confirmed only at the class level.
* fix(web-shell): polish session workflow review UI
* fix(web-shell): polish session workflow interactions
* fix(web-shell): polish session workflow interactions
* fix(web-shell): keep complex cockpit plans readable
* fix(web-shell): refine cockpit layout and dag geometry
* refactor(web-shell): unify session workflow views
* fix(web-shell): make the plan graph legible under load
Three defects in the dependency graph, all reachable from the demo plan in the
PR description:
- Every layer-spanning edge routed at the same `routeY`, so a plan with two or
more long dependencies drew them on top of each other. Each now gets its own
lane, ordered by span length so the longest sits furthest out and the lanes
nest rather than cross. The canvas reserves exactly the room they measured.
- `locateFocusTodo` called `viewport.scrollTo` from inside a
requestAnimationFrame, where a throw cannot be caught and takes the
surrounding render down with it. Guarded, with a scrollLeft fallback.
- `running` and `completed` both mapped to --success-color, which are the two
states a reader most needs to tell apart, and `blocked` mapped to
--warning-color, so a healthy plan of not-yet-started nodes rendered as a wall
of warnings. Completed is now the only green, in-flight work is the only blue
and the only thing that animates, and blocked/ready are neutral. Status also
carries a glyph so it survives greyscale and colour-blindness.
The cross-layer routing test asserted a golden path string; it now asserts the
behaviour it is named for. That also stops a failure there from skipping its
mockRestore calls and leaking the rect spy into the next four tests.
* refactor(web-shell): tighten the workflow page hierarchy and graph reading
Focused pass over the current layout, not a restructure.
Hierarchy. The page stacked three header bands before a single node was
visible: the h1 plus meta, the graph's own "Plan execution" caption, and the
graph's overview strip. The meta row also repeated the step and agent counts
that the overview strip showed about 100px below it. The graph takes a
`hideTitle` option for hosts that already title the region, and the meta row
carries identity only, leaving one header band and one set of numbers.
Vertical priority. The graph sized to its content inside a card with
overflow:hidden, so it lost the fight for height to the panels below it and
clipped once a plan grew. It now takes a guaranteed slab of the viewport and
scrolls inside itself in both axes. The flexible sizing is inert where the host
lets the section size to its content, which is what the inline message and
dialog uses do.
Reading a dense graph. Pointing at a step now mutes every edge that does not
touch it, so "what feeds this, and what waits on it?" is one glance instead of
tracing lines by eye. Edges rest in a neutral tone and only the traced chain
takes the accent colour.
Attention. The detail panel named the failing step and offered its output, but
never said where in the plan the failure sat. It now carries the step ID and
its upstream and downstream IDs — the same identifiers blockedBy and todo_id
use, so they lead back to the right node.
* fix(web-shell): explain a skipped plan graph and trace it from the keyboard
- Past MAX_RENDERED_PLAN_EDGES the SVG layer is skipped entirely. The nodes
still list what they depend on, but every line vanishing with no explanation
reads as a rendering failure. Say what happened and why.
- Tabbing through the graph now traces the same dependency chain a pointer
does. The highlight was previously reachable only by clicking a node to pin
it, which made the fastest way to read a dense graph mouse-only.
- Suppressing the caption on a plan with no dependencies left an empty heading
row that still cost the section's row gap. Don't draw the row at all when it
would hold nothing.
* fix(cli): freeze approved workflow revision
* test(cli): enter plan mode for revision restore
* fix(cli): preserve reviewable workflow revision
* fix(cli): close workflow runtime boundaries
* test(cli): type workflow runtime mock
* fix(cli): synchronize workflow runtime state
* fix(core): split replaced workflow plans
* fix(web-shell): resolve the R1 review round on the session workflow path
Addresses every Critical from the round-1 review, plus the Suggestions that
delete dead code or are one-line corrections.
Criticals:
- Relabel plan approvals by option id, not kind. An exit_plan_mode approval
emits two allow_once options; sharing one label gave restore_previous
(which restores the pre-plan mode, YOLO included) and proceed_once the
identical button.
- Define the four workflow.dependencies.* keys in both locales; the attention
detail rendered raw keys.
- Clear a stale hover id when its todo leaves the plan, so a removed node no
longer dims the whole dependency graph.
- Scope the query container to the overview strip so the plan DAG's intrinsic
width can still size the auto-width dialog.
- Drop the loading term from the workflow gate; a background settings
revalidation was evicting the user from an established Workflow view and
deleting its deep link.
- Lower the cockpit button reset to zero specificity so .primaryButton and
.backButton keep their declared font metrics and colors.
- Re-derive the Session Workflow gate in workspaceReload so a settings-file
edit reaches live sessions instead of staying masked until daemon restart.
- Apply the post-write effective value to live sessions; a user-scoped write
shadowed by a workspace value was pinning sessions to a non-effective value.
- Clear the previous incarnation's terminal summary on both resume paths, so a
crash mid-resume cannot restore run N-1's stats as the interrupted run.
- Make the setMode and rewindToTurn revision tests exercise the clear they
name; both passed vacuously under the capture gates.
Suggestions: remove the showPlanPreview dead switch, the unreachable
getLatestTodos mode, two dead i18n keys, a duplicate .in_progress rule, a
duplicate afterEach and a byte-identical duplicate test; keep CJK in the
avatar-initials fallback; floor the progress percentage; name the progressbar
and group the overview for assistive tech; guard the progress transition with
prefers-reduced-motion; unclip the activity-row focus ring; restore
prototype-level mocks in a finally block.
* fix(serve): apply the effective session-workflow value only on the primary route
The workspace-qualified route only accepts workspace scope, so the written
value is already the effective one — reading it back via loadSettings broke
workspace-qualified-rest.test.ts (spy saw enabled:false when the spy-mocked
persist never landed). The user-scope shadowing that motivated the effective
read only occurs on the primary route, which keeps it.
* fix(core): checkpoint of in-progress R2-1/R2-5 fixes; tests follow
- todoWrite: treat a sessionWorkflow plan revision captured while still in
PLAN mode as a pending draft, so pre-approval membership refinement no
longer clears the revision or strips the workflow marker (R2-1)
- agent: clear stats/recentActivities in the resident-controller
hot-continuation patch, mirroring background-agent-resume.ts (R2-5)
* fix(cli): checkpoint of in-progress R2-2/R2-3/R2-4 fixes; tests follow
- Session.setMode: only clear the active todo plan revision on
PLAN-involving transitions; non-plan mode switches (default/auto-edit/
yolo) must not disarm an approved workflow mid-execution (R2-2)
- acpAgent workspaceReload: also diff the reloaded sessionWorkflow flag
against the live UI-pinned override, not just stale this.settings.merged
(R2-3)
- workspace-settings route: push the post-write effective value (system
scope can shadow a workspace write) instead of the raw written value
(R2-4)
* fix(web-shell): checkpoint of in-progress R2-6/R2-7/R2-8/R2-15 fixes; tests follow
- App: route openScheduledTasks/openGoals/openSplitView through showChat()
so leaving the cockpit strips the ?view=cockpit deep link (R2-6)
- SessionWorkflowCockpit: reorder header status ladder to attention ->
running -> completed -> waiting, mirroring plan-node state precedence
(R2-7); widen initials() ranges to kana+hangul and align comment (R2-15)
- PlanExecutionView: also centre the focused todo vertically and pass
{left, top} to scrollTo, with a scrollTop fallback (R2-8)
- css: extend clipped focus-ring fix to attention rows (part of R2-14)
* test(cli): pin R2-2/R2-3 semantics (mutation-verified)
- Session.setMode: approved workflow plan revision survives a non-plan ->
non-plan switch (default -> auto-edit); fails when the PLAN-gated clear
is reverted to clear-on-any-change (R2-2/R2-10)
- acpAgent workspaceReload: re-derives the Session Workflow gate from the
reloaded file, including when the file contradicts a UI-pinned override;
no-op reload does not re-pin; fails when the override-aware diff clause
is reverted (R2-3/R2-9)
* test(cli): pin R2-4 effective-value push on the qualified settings route
- makeHarness persistSetting now really writes Workspace-scope settings, so
the route's loadSettings read-back is deterministic (previously a pure
spy, leaving the assertion at the mercy of the host's user settings)
- new case: a system-scope (fleet) pin of experimental.sessionWorkflow
shadows the workspace write; live sessions must receive the effective
false, not the raw written true. Fails when the route is reverted to
pushing the raw value (mutation-verified).
* test(core): pin R2-11 cold-resume clearing of stats/recentActivities
Seed a completed Session Workflow agent's meta with stats + recentActivities,
hold the first continuation turn open, and assert the restarted run's meta no
longer carries run N-1's terminal summary. Fails when the cold-resume
clearing patch is reverted (mutation-verified).
* test(core): pin R2-5 hot-continuation clearing of stats/recentActivities
Spy on patchAgentMeta across a resident-controller continue and assert the
running patch carries stats:undefined + recentActivities:undefined keys.
Fails when the hot-path clearing is reverted (mutation-verified).
* test(web-shell): pin R2-8 vertical locate + R2-12 floored progress
- locate test now mocks clientHeight and asserts scrollTo receives
{left, top, behavior}; fails when `top` is dropped (mutation-verified)
- new 2-of-3 fixture asserts '66%' + aria-valuenow=66; fails under
floor->round (mutation-verified)
* fix(cli): guard same-value Session Workflow writes from re-clearing plan state
* fix(web-shell): keep the submit guard armed against stale request rejections
* fix(web-shell): strip cockpit deep link when a controlled host takes over the view
* test(cli): pin R2-10 setMode exit-plan revision clear (mutation-verified)
* test(web-shell): pin R2-13 cockpit eviction on disabled revalidation (mutation-verified)
* fix(cli): skip Session Workflow gate clears when live sessions already see the pushed value
* test(cli): pin Session Workflow reload no-op against a stale settings view
* fix(web-shell): mark the locate button as plan-interactive for host keyboard handlers
* fix(web-shell): add the data-plan-interactive marker on the locate button
* fix(core): write subagent Session Workflow revision mutations through Config overrides
* fix(web-shell): rethrow main-chat approval submission rejections so the overlay re-arms
* fix(web-shell): strip a stale cockpit deep link when a controlled split takes over from chat
* fix(core): keep the Session Workflow gate check a pure read
* fix(core): write InProcessBackend per-agent revision mutations through to the base Config
* fix(core): cast the per-agent runtime context through unknown in the write-through test
* fix(cli): pin the session Session Workflow gate at construction instead of tracking live settings
* fix(cli): apply the reloaded Session Workflow gate to live sessions unconditionally
* fix(cli): serialize the Session Workflow settings write, readback, and live push per workspace
* fix(web-shell): include edge identity in the plan graph measure-skip signature
* test(cli): report the effective Session Workflow gate in workspace reload test mocks
* fix(core): write workflow dir-scoped and schema Config overrides through to the base revision
* fix(web-shell): count the Active agents strip from the same execution status source as the plan node badges
* chore(ci): refresh the cd-cua-driver.yml size baseline
main's #9587 grew cd-cua-driver.yml to 42519 bytes without updating
.github/workflows/.size-baseline, so every branch merged after it fails
the workflow-size ratchet. Record the real size.
* fix(cli): apply the reloaded approval mode to live sessions unconditionally
* fix(web-shell): keep cockpit entry focus under StrictMode effect replay
* fix(web-shell): tally cockpit attention stats from the queue's own surface
The Attention page's stats strip counted failed/cancelled agents from
linkedAgentTasks — every tool call with a task entry — while the queue
below it derives from per-todo attention. The two disagreed in both
directions: a failure under a completed step (whose attention is forced
off) read "Agent failures: 1" directly above the "Nothing currently
needs your attention" empty state with no affordance to open it, and a
persisted sub-agent evidenced only by subTools queued with an open
button while the strip read 0 because no environment task entry exists
for that shape.
Extract the attention status walk from getPlanNodeStateFromIndex into
attentionAgentStatuses (one observed status per agent, keeping the
actionable failed/cancelled observation when a live task and a persisted
transcript tool describe the same agent) and export it as
getAttentionAgentStatuses. The cockpit now tallies failed/cancelled
statuses over exactly the todos the queue shows, so the strip and the
queue can no longer contradict each other.
* feat(web-shell): add workflow inspector
* fix(cli): reject stale workflow plan approvals
* test: cover workflow approval transitions
* feat(web-shell): prioritize workflow step progress
* fix(core): drop preserved todo deps removed by the same update
* fix(web-shell): unify strip and inspector active agent tally
* fix(web-shell): repair theme, type, and state defects in session workflow UI
Styling pass over the session workflow cockpit, inspector, and plan graph.
- Restate font-size/font-weight on every heading. preflight.css resets
headings to `font: inherit`, so the cockpit session title, the empty-state
title, and every inspector section heading were rendering at body text.
- Stop painting chips with --muted. It is the same value as --card in the
dark theme, so the inspector's count badges and idle status pills were
invisible on the cards they sit on.
- Give the sticky summary a shadow that survives the light theme. It was
mixed from --background, which is white there.
- Split hover from selection in the step and attention lists. Both painted
the same background, so hovering erased any sign of the selected row.
- Add the missing `height` to two lucide icons. Overriding `width` alone
leaves the 24px height attribute in place.
- Style disabled deliverables. They kept a pointer cursor and a hover tint.
- Raise the type floor to 11px. The inspector had 8px and 9px labels, well
below anything else in the app.
- Widen the activity timestamp column. workflowClock() goes through
toLocaleTimeString, so a 12-hour locale renders eight characters.
Consistency work behind the same pass:
- Add a shared --status-{running,done,attention,idle}-{fg,bg} palette to
App.module.css and read it from all three stylesheets, which previously
carried four near-duplicate status mappings that disagreed on their mixes.
- Match the inspector progress bar to the one PlanExecutionView already
draws for the same number.
- Replace the <b>/<i>/<em> style hooks with named classes, dropping the
font-style resets they needed.
- Use --font-mono and the radius tokens instead of hand-rolled values, and
collapse three prefers-reduced-motion blocks into one.
No behaviour or markup semantics change; tests and data-testids are untouched.
* fix(web-shell): give the workflow activity time a machine-readable value
- <time> carried only the localized display string from workflowClock(), so
nothing could read the instant it stands for. Emit dateTime alongside it.
- Replace the literal "!" attention glyph with AlertCircleIcon, which is the
icon vocabulary the rest of this view already uses.
* fix(cli): apply reloaded approval mode only when the file value changed
workspaceReload applied the disk approval mode to every idle live session
whenever it differed from the session's live mode. Approval mode has
runtime-only writers (approved exit_plan_mode, ACP session/set_mode, the
sessionApprovalMode ext; core Config.setApprovalMode never persists), so a
session legitimately diverges from the file mid-workflow, and any reload
(e.g. after an unrelated settings save) flipped an approved-plan session
back into PLAN between turns, destroying its bound revision and stop-guard
trust; the mirror direction discarded runtime plan drafts.
Track the last file-derived mode on the daemon (seeded from the boot
settings) and apply only when that baseline changes, keeping the R8-1
guarantee that a genuine disk flip reaches live sessions even after a
this.settings cache swap. Also fold a missing or invalid
tools.approvalMode to AUTO - the fresh-session default - so deleting the
key propagates to live sessions instead of pinning a stale privileged mode
until daemon restart.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): notify settings observers when the live Session Workflow push fails
When the Session Workflow persist succeeded but the live push failed
(bridge channel closed, push timeout), both settings routes returned 500
and skipped every change-notification side effect, leaving every other
observer stale on the pre-write value while the file already carried the
new one - the exact file-vs-live divergence the write chain exists to
prevent.
Split the persist+push result into an outcome so the routes broadcast
settings_changed and invalidate the serve-features cache whenever the
on-disk value changed, while the requester still receives the push
failure 500.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* refactor(web-shell): drop the unrelated ToggleGroupItem ref conversion
Nothing in this branch passes a ref to ToggleGroupItem — the only reference
the diff added was the row registering it in the ref-compat table. The
conversion is a standalone hardening of a shared primitive whose consumers
are AgentsManagerPage, SkillsManagerPage, and McpManagerPage, none of which
belong to Session Workflow. Removing it keeps those three pages out of this
PR's regression surface; the change is worth making on its own.
* fix(core): let todo updates reverse dependency chains with preserved edges
The blockedBy preservation added for active plans re-injects stale edges
that survive an update, so reordering or reversing a dependency chain
closed a cycle with incoming explicit edges and validateTodos rejected
the entire call with a misleading "must not contain a cycle" error — the
exact failure class preservation was meant to eliminate (only the
unknown-dependency case had been fixed).
After building the preserved candidate list, fall back to the edges the
caller actually sent when validation fails with the cycle error: the
incoming list already passed validateToolParams before execute, so it is
a sound, pre-diff-equivalent fallback.
Test: a chain a<-b<-c reversed to a->b->c now persists the caller's
edges instead of failing mid-execute (todoWrite.test.ts).
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
* fix(cli): pin the Session Workflow gate at session publication, not construction
The override pin ran at `new Session(...)` construction time, but the
session joins `this.sessions` only after `await
registerCreateSubSessionTool(config)`. A `workspace/session-workflow`
write landing in that window was lost for the session being published:
the construction-time pin saw no override yet, and the ext handler's
live-session loop could not see the unpublished session. The
same-value guard then turned a retry into a no-op, so the session
silently kept its settings-derived gate while the daemon reported the
feature enabled.
Move the pin to run synchronously immediately before
`this.sessions.set(sessionId, session)`: writes arriving during tool
registration have already recorded the override and are observed by the
pin, while writes after publication find the session in the map. No
await sits between the pin and the publication.
Test: hold registerCreateSubSessionTool open (not just refreshAuth),
fire the ext write in that window, and assert the published session
carries the pinned provider (acpAgent.test.ts).
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
* fix(cli): converge approval-mode reload on the boot parser and restricted-mode default
The reload fold validated with strict APPROVAL_MODES.includes, but every
ACP session boots through parseApprovalModeValue, which trims,
lowercases, and maps the legacy auto_edit/autoedit aliases. Reload
therefore silently disagreed with the settings file for every
boot-accepted non-canonical spelling: an 'autoedit' file seeded the
baseline to AUTO, hiding a genuine autoedit→auto change from live
sessions, and a case-only 'plan'→'Plan' edit folded to AUTO and snapped
sessions out of PLAN, destroying bound plan revisions.
The fold also collapsed a missing/invalid key to AUTO for every session,
but safe/bare sessions derive DEFAULT at boot regardless of the file
(loadCliConfig ignores tools.approvalMode for them). Deleting or
corrupting the key therefore pushed AUTO into restricted sessions —
silently stripping their approval restriction.
Fix: export parseApprovalModeValue and route the fold through it
(unparseable values still fold to the fresh-session default), converge
safe/bare sessions on DEFAULT per session in the reload loop, and seed
the daemon baseline with DEFAULT for restricted daemons so it mirrors
boot.
Tests: alias and case-variant reloads without APPROVAL_MODES splices,
plus safe-mode and bare-mode reloads across unchanged, flipped, and
deleted keys (acpAgent.test.ts).
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
* fix(core): preserve agent summary after failed revive
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(web-shell): keep workflow tab during approval
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): advance the reload approval-mode baseline only on full convergence (#8583)
workspaceReload advanced sessionApprovalModeFileValue unconditionally,
including sessions the per-session apply never reached (busy sessions
skipped by the isIdle guard, sessions whose setApprovalMode threw, and
sessions whose reload rejected). Every later no-edit reload then computed
no file change and those sessions stayed on the stale mode until the file
changed again or the daemon restarted.
Advance the baseline only when every live session actually converged (no
skips, no apply failures), so the next no-edit reload re-attempts and the
per-session !== previousMode guard applies the value only to the sessions
still missing it.
* fix(web-shell): keep the floating drawer autofocus to genuine opens (#8583)
The floating artifact drawer mounts already-open, and its autoFocus
fired on ANY mount-while-open — including the docked->floating hand-over
(viewport resize, split view) of an already-open panel mid-session,
yanking focus from the composer into the drawer's focus trap and eating
keystrokes.
Autofocus only when the mount follows a genuine closed->open: a ref
tracks the panel's open state at the last render where the drawer was
not mounted and stays frozen while it is mounted, so re-renders never
flip the prop under the drawer's mount-time autofocus wiring.
* fix(cli): give the AgentTool test mock the full SubagentManager surface (#8583)
AppContainer's mount effect runs config.initialize() in an un-awaited
IIFE; the real initialize warms the tool registry, constructing AgentTool
against the test's Partial<SubagentManager> mock, which lacked
getAvailableModelGrades. refreshSubagents then rejected with a TypeError
that surfaced as unhandled rejections failing the whole packages/cli run
(the verification gate rejection for this PR).
Complete the mock and pin the surface with a test that drives the real
initialize and awaits refreshSubagents, so a missing method fails that
test instead of leaking.
* fix(cli): keep reload from converging sessions on an approval mode boot rejects (#8583)
foldReloadApprovalMode folded a present-but-unparseable
tools.approvalMode to AUTO, while boot rejects the same file outright
(loadCliConfig has no catch around parseApprovalModeValue). A corrupted
key therefore silently escalated the approval gate for every live
session not already on AUTO; the blessed fold test arranged the session
to sit on AUTO, so the escalation branch never ran.
The fold now returns undefined for present-but-invalid values so reload
keeps live sessions on their current modes until the file is corrected,
matching boot's rejection; a missing key still folds to AUTO so a key
deletion reaches live sessions. Add a test with the session on DEFAULT
that fails if the invalid fold to AUTO returns.
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
* fix(web-shell): close the inspector drawer only when the cockpit opens (#8583)
expandWorkflowGraph closed the floating artifact drawer unconditionally
even when openCockpit() silently aborted on its approvalOverlayActive /
requireActiveSessionForLocalCommand guards — the guarded action's side
effect ran without the action, dropping the interactive workflow surface
with no cockpit to show for it.
openCockpit now reports whether it opened, and the drawer only closes
when it did. Add a regression test that fails if the drawer closes
while a pending approval gates the expand action.
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
* fix(web-shell): scope cockpit button reset to its own buttons (#8583)
Drop the blanket .cockpit/.emptyCockpit descendant button reset: its
0,1,1 specificity silently outranked the single-class styles it
envelops (.backButton typography, PlanExecutionView nested rows) and
its :focus-visible form stacked a second outline over the graph's
deliberate node focus rules. preflight.css already normalizes buttons,
so scope the focus ring to the cockpit's own buttons instead.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(web-shell): anchor tasks dialog tools separately from workflow surfaces (#8583)
The shared planAgentTools memo served the ArtifactPanel workflow tools,
the tasks dialog, and the cockpit, but re-anchored to floatingTodosState
for everyone whenever the tasks dialog opened. After a user message the
floating plan is empty while the tagged session-workflow plan persists,
so the cockpit/inspector behind the dialog lost their linked agent tools
until the dialog closed. Give the tasks dialog its own memo anchored to
floatingTodosState and keep planAgentTools on sessionWorkflowTodosState.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): track reload approval-mode convergence per session (#8583)
* fix(web-shell): keep inspector styles from defeating their own buttons (#8583)
* fix(core): stamp Session Workflow revisions approved on plan exit (#8583)
todoWrite derived a bound plan revision's approved/pending status from
`this.config.getApprovalMode() !== PLAN`. Plan-revision state is
session-global (write-through shims forward mutations to the root Config)
while approvalMode stays per-wrapper: a subagent whose definition
frontmatter declares `approvalMode: plan` gets a Config wrapper with its
OWN PLAN mode and no mode-change route, so inside it an already-approved
revision read as a pending draft — membership enforcement silently
disappeared and a divergent todo_write could rewrite the session-shared
plan without clearing the stale binding.
Stamp the approval on the revision itself: setApprovalMode now marks the
bound revision approved when the PLAN -> non-PLAN transition comes from an
approved exit_plan_mode (fromApprovedPlanExit), and todoWrite reads the
stamp instead of the wrapper's mode. Pending drafts stay unstamped, so
PLAN-mode refinement keeps its drafting semantics.
* fix(serve): fan user-scope Session Workflow writes to sibling workspaces (#8583)
A user-scope write of experimental.sessionWorkflow through the legacy
POST /workspace/settings route persists to ~/.qwen/settings.json and
flips the effective gate for every workspace on the host, but the live
push only reached the primary bridge — each sibling workspace runtime
owns its own bridge/agent and received neither the
workspaceSessionWorkflow command nor the settings_changed fan-out, so
its sessions kept deriving the stale gate (revision/membership
enforcement running against the wrong value in both directions) until
daemon restart.
Add an updateSiblingSessionWorkflows fan-out that the legacy route
invokes after the primary push succeeds, only for user-scope writes.
Each non-primary runtime re-derives its OWN post-write effective value
(a workspace file can shadow the user write differently per workspace,
so the primary value is not generally correct for siblings) and
receives the workspaceSessionWorkflow command. Sibling pushes are
best-effort: a draining or dead sibling logs and converges on daemon
restart instead of failing an otherwise successful write.
Workspace-scope writes keep the primary-only push — only that
workspace file changed, and re-pushing siblings unchanged values would
risk clobbering a UI-pinned override.
* fix(test): raise AppContainer state-suite timeouts under heavy parallel CI load (#8583)
* chore(sdk): bump the browser daemon bundle budget to 217KB
* fix(workflow): resolve plan review feedback
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…earch (#10612) * feat(web-shell): search conversation content in the sidebar session search The sidebar "Search sessions" box only matched session titles, ids, and git context, so a keyword the user remembers from the conversation itself returned nothing. Add a workspace-scoped daemon endpoint GET /workspace/:id/sessions/search that scans persisted session transcripts for user/assistant message text and returns one summary plus a snippet per matching session. The sidebar keeps its local title/id/git filter as the fast path and merges the debounced server hits into the results, rendering the matched-message snippet under the session label. Closes #10261 * fix(web-shell): address review round 1 on session content search Critical: - Normalize whitespace in the query for both matching and snippet building so a query with a whitespace run can no longer return the entire message as the excerpt (and matches newline-separated text) - Scope WorkspaceSection's content-hit merge to the section's session source, mirroring the sidebar merge - Apply the organization snapshot to search summaries so ghost hits carry pin/group/color like catalog entries - Reset settled hits when the query changes instead of rendering the previous query's hits (and stale snippets) under the new one - Pass ghost hits through the optimistic-pin overlay in both merges Suggestions: - Extract the duplicated content-hit merge into a shared, tested helper - Mirror the user-prompt display projection (authoritative displayText, hook-context part stripped) for search text - Keep snippet windows in the original string space (case-folding shifts length) and clamp them off surrogate-pair splits - Yield and honor aborts while stat-ing large chats dirs; cap the query after trimming (route and hook); correct the maxResults JSDoc - Pin behavior with new tests: mid-scan and stat-loop aborts, HTTP disconnect cancellation, plural route spelling, workspace_mismatch negative paths, sidecar/organization enrichment, merge dedupe/source scoping, hook reset/race/failure/cap paths * fix(web-shell): address review round 2 on session content search - Fold per code point in indexOfCaseInsensitive so supplementary-plane case pairs (Deseret, Adlam, ...) keep matching, and gate the JS-level fold behind a native whole-string pre-filter so the common no-match scan path stays native (~200x faster on MB-sized records) - Depend the search effect on the normalized query so whitespace-only edits neither blank settled hits nor re-fetch the identical query - Reset hits during render (adjust-state-while-rendering) instead of in the passive effect, so no committed frame pairs a changed query with the previous query's hits; gate WorkspaceSection's snippet lookup on an active query the same way as the primary path - Drop a lead surrogate straddling the 200-unit query cap instead of slicing mid-pair and sending a U+FFFD-corrupted query - Keep pinned content-search ghost hits visible: they render in the main list while their hit is active instead of vanishing with the loaded-pinned-row filters (the Pinned section never sees ghosts) Tests: Deseret case-pair matching both directions, end-side surrogate clamp witness, whitespace-only edit, committed-render pairing recorder, surrogate-straddling query cap, pinned ghost hit under excludePinned. * fix(web-shell): address review round 3 on session content search - Unify Greek final sigma (U+03C2 -> U+03C3) on the query, the per-code-point text fold, and the whole-string pre-filter, so Greek text ending in sigma matches every sigma query form; whole-string lowercasing applies the word-final Sigma -> final-sigma mapping that a per-code-point fold does not - Thread the workspace sessions reload token into useSessionContentSearch as an additional reset/refetch key, so a session deleted or archived while a query is active stops rendering instead of being resurrected as a ghost row; the reset keeps the render-phase adjust-state shape with a key-matched return guard Tests: Greek sigma query forms (byte-identical, lowercase, small-sigma), reload-token invalidation in the hook, and a WorkspaceSection case where a settled ghost hit disappears after its session is deleted with the query unchanged. * fix(web-shell): address review round 4 on session content search - Give pinned rows a single owner: content-search ghost classification now excludes sessions the Pinned section renders (pinned page settled or optimistic pin), so a pinned content hit never renders twice; the WorkspaceSection retention is gated through a new isPinnedSectionMember prop wired from both sidebar render sites (R4-1) - Gate the org-mode section renderer's renameFormDisabled on the Pinned section actually carrying the row, mirroring the workspace-section call site — ghost rows never appear there, so rename was silently inoperable for them (R4-2) - Invalidate settled content-search hits on poll-observed catalog membership changes, not only local handler token bumps: both call sites pass a composite key of reload token plus an order-insensitive session-id membership key, so externally deleted/archived sessions drop out without re-firing on every poll tick (R4-4) - Cover the text-side final-sigma arm with a ς-bearing fixture asserted against all three query forms (R4-3) - Pin the previously untested guards: sidebar-level ghost tests for the flat and sectioned paths, exact-once ownership in both pinned-page arms, and ghost-row rename (R3-2); hook revert-direction reset (R3-12)
* feat(opentui): Add composition-root error boundary, runtime, and command host Batch 5 (slice 1/3): the OpenTUI backend has all its pieces (dispatcher, dialogs, live turn, folding model) but no composition root that owns state and wires them. Add the three self-contained leaves first: - OpenTuiErrorBoundary: React boundary mirroring the ink shared ErrorBoundary (recordForExitEcho + consumeLastRenderError) for the @opentui/react tree. - OpenTuiRuntime: process-level lifecycle (runtime sidecar, dual-output bridge, remote-input watcher, memory-pressure monitor, ordered shutdown). - OpenTuiAppHost: concrete OpenTuiCommandHost + SessionSwitchHost — owns command history with faithful useHistory parity, pending/btw/session state, shell allowlist, idle/processing, and delegates the live transcript and modal confirmations to shell-supplied seams. Host/runtime/boundary are additive and unwired (behavior unchanged); dialog mounting and the app shell follow. * feat(opentui): Add composition-root dialog mount and app shell Complete the Batch 5 backend composition root: the exhaustive dialog mount routes every OpenTuiDialogRequest to its component, and the app shell assembles the command bridge (host + dispatcher + gateway), dialog mount, and error boundary, wiring the composer through the gateway so dispatch outcomes open dialogs, reach the live-turn seam, or quit. * feat(opentui): Reserve the update-notification slot in the app shell Adds a fixed banner slot below the transcript, hidden while a dialog is open, to mirror the ink DefaultAppLayout so populating the parity gap (G-3) later does not re-open the shell layout. * refactor(opentui): Keep command loading in slash-dispatch, drop its dead dispatch chain executeSlashCommand and its resolution/mapping helpers were superseded by OpenTuiSlashDispatcher and the slash gateway; no production path reached them. slash-dispatch now exports only the loader that commands-dispatch and the composer still consume. * fix(opentui): Make the composition-root seams fail safe The confirmation bridge auto-denied only the empty command list, so the non-empty list the dispatcher actually awaits left a promise nothing would ever resolve — hanging the dispatch loop and the gateway busy flag for the rest of the session once the shell is mounted. It now denies every request, matching the action-confirmation branch, and the pending-resolver slot it never used is gone. Composer image paths no longer flatten into an invented `[image] <path>` text part that nothing parses; they travel to the live-turn seam as a structured argument so the entry layer can build real image parts. * fix(opentui): Close the composition-root gaps found in review Extension consent stored a request that no renderer in this batch could answer, so the caller awaited a promise that never settles; route it through the bridge the shell already auto-denies. The host's caller-owned steps (subscribers, onChange, transcript reset) run inside the /resume and /branch commit window, where one throw rolls core back and deletes the branch being displayed, so isolate them. Make the session re-key an explicit shell seam that reports when nothing owns it, wire the permissions dialog's directory edits to real settings persistence, and say so when a settings row opens a sub-dialog the mount does not route. * fix(opentui): Report an arena start the composer cannot receive Enter in the arena model picker is the only way to start a session, and it works by writing the command into the composer the entry layer owns. With no composer owner wired, the dialog closed and the selection vanished with it, so surface that the command was lost instead.
…10724) * docs(opentui): Record the migration status through the composition root The design doc still described the state of 2026-08-28, with only the infra batch landed. Record the five batches now on main, name the seams the composition root leaves to renderer activation, and list the two items deferred to that batch. * docs(opentui): Record the composition-root contracts and correct stale activation scope The design doc described QWEN_TUI_RENDERER as an existing opt-in and the activation batch as carrying runtime fixes that already shipped in #10128. Both drifted from the code while the batches landed, which is the kind of claim a reviewer had to catch on #10696. State the contracts the composition-root review settled so the activation batch inherits them instead of rediscovering them. * docs(opentui): Record measured runtime status and what the batch reviews kept finding Three claims in the design doc described instruments and gates that are not in the tree: the session-replay harness (issue #10005 is still open, nothing measures flicker today) and plain-Node loadability, which 0.5.8 fails on Node 24 — verified locally, not just reported in review. The recurring finding classes are recorded so the activation batch does not re-earn them.
* fix(cli): abort subagent generation on unmount Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(cli): cover idle description input cleanup Protect the null-controller unmount path so navigation before generation cannot regress into a cleanup exception. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(cli): cover late subagent generation completion Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * test(cli): cover mounted subagent generation success Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --------- Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(web-shell): add a Workspaces overview panel Layer B2 of #10399: a full-page table of every registered workspace — name with primary/untrusted badges, path, active session counts, MCP health (unknown while the runtime is not initialized, never zero), branch with a dirty counter, and last activity — with New task and Remove actions per row. Entries: a Manage workspaces row at the end of the Projects section (hidden for a locked sidebar) and an opt-in workspacesOverview footer item outside the default set. Daemon-owned live runtimes are not rows. The sidebar's inline removal state machine moves verbatim into a shared useWorkspaceRemoval hook plus WorkspaceRemovalDialog so both surfaces run the same confirm/busy/force/mismatch/in-progress flow; the sidebar keeps its reconciliation as the hook's onRemoved callback. Cells reuse layer-A plumbing: catalog-store session counts deduplicated with the sidebar's queries, useWorkspaceOverview for MCP, and the git chip's fetch discipline for the branch column. Untrusted rows fetch nothing. * fix(web-shell): align the Workspaces panel's cells and gates with review round 1 Three behavioural fixes: a capabilities-refresh rejection after a daemon-confirmed removal is no longer misreported as a removal failure (the panel's onRemoved guards the refresh the way the sidebar's reconcile does); the MCP fraction excludes disabled servers from its denominator, matching the sidebar chip's connected/enabled convention; and last activity falls back to createdAt like the daemon's getSummaryActivityTime, so a live session that has not published a terminal still counts. Smaller alignments: session counts treat a nextCursor page as a lower bound (the sidebar's union); the opt-in footer entry honours the locked gate and carries a testid; the panel gets its aria-label and heading focus when opened; the dedup comment states what actually dedupes. Coverage the review asked for: the in-progress retry loop (converges, busy mid-retry, dismissed, exhausted), submitting reset across sequential removals, the active-session force block through the panel, the per-row query and facet shape, the New-task double-submit guard and its App wiring, the footer entry, an MCP-cell-scoped unknown assertion, and a cross-workspace facet witness in the e2e spec. * fix(web-shell): stabilize the removal controller and pin review round 2 The one behavioural change: useWorkspaceRemoval reads its options through a ref and memoizes the controller it returns, and App passes createNewSession directly, so the panel's column definitions stop rebuilding on every render — a rebuilt column set is a new component type per cell, which unmounted every row cell and re-fired their git fetches on any App re-render while the panel was open. The rest is coverage round 2 asked for: both remaining retry-loop exits (mid-retry mismatch reconciles, a non-transient error surfaces itself rather than the synthetic exhaustion message), the dismissed flag reset and stale busy-state reset in request(), the reconcile-before-clear ordering (a hung reconcile keeps the dialog open), the allow direction of the panel's force gate, hook-level blockForce at both call sites past the disabled attribute (invoking the button's props directly, as a stale reference would), the global cross-row New-task guard, the wait:true git contract, the truncated-without-cursor lower bound, the unwired footer opt-in rendering nothing, heading focus on panel open, and an honest scope note on the e2e cross-wiring witness. * test(web-shell): pin fresh option reads and submitting across the reconcile Two review-round-3 pins on the removal hook. Options are read through a ref so blockForce observes the connection state at confirm time — a busy dialog whose candidate gains the active session between renders must refuse the forced retry; the new test re-renders the probe with a flipped blockForce and goes red if the ref assignment is dropped. And submitting stays true for the whole reconcile window, keeping every dialog gate locked during the caller's capability round-trip; the hung-reconcile test now asserts it mid-flight and goes red if confirm stops awaiting handleRemoved. * test(web-shell): pin the busy report across a blocked forced confirm force derives from activity, so a block branch that cleared it would silently downgrade the next click to a non-forced attempt and restart the consent flow; the blocked-confirm test now asserts the report and candidate both survive the refusal.
) * feat(review): prebuild the review worktree before any agent runs (#10108) The worktree fetch-pr builds is a bare checkout, and an install plus the prerequisite builds does not fit inside an agent's tool budget: every probe that decided to run a test burned its budget on a doomed install and the round downgraded to a read-only audit. When the review workflow sets QWEN_REVIEW_PREBUILD=1, fetch-pr now runs Agent 7's own `build-test --install --build-only` on the worktree right after writing the plan - the same command, environment, sandbox policy and scoped closure, on the orchestrator's clock with a step-sized budget - and records the outcome in the fetch report (`dependencies`). The plan is rewritten with it before the session ledger is appended, so the run-epoch fence keys on the final write. Fail-open throughout: a prebuild that could not complete records a reason and Agent 7 installs and builds on its own path as before; without the variable the report is byte-for-byte unchanged; an empty diff skips it. Replaces the host-cache design of #10129: nothing is shared across jobs, nothing is keyed, nothing can go stale - npm's own sync check and the PR's own sources are the whole of the correctness argument. * ci(review): keep the prebuild switch under the workflow size ratchet main already sits 3706 bytes over the recorded baseline for qwen-code-pr-review.yml (allowance 4096), so the step comment shrinks to a pointer at lib/prebuild.ts, where the rationale lives anyway. * fix(review): keep the CI prebuild alive under the agent shell tool (#10423) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): reconcile the worktree prebuild with its timeout covers (#10423) The session-shell cover welded for the prebuild sat exactly at the budget, but its clock starts at the fetch-pr spawn while the budget clock starts only inside runBuildTest, so the hang case the budget exists for expired the cover first; the cover now carries a named headroom. An attempt budget that cannot carry the prebuild budget plus the deadline reserve plus a review margin now unsets the opt-in instead of dying mid-`npm ci`, and a local opt-in without the CI-welded cover warns and skips instead of killing fetch-pr at the 120s built-in default. The opt-in grammar narrows to the '1' the cover gate welds for, the probe-overlap invitation names the dist pre-clean window, the wiring suite is hermetic against the ambient switch, and the workflow pins parse the cover JSON, the gate literals, and the write-before-launch ordering. * ci(review): record the prebuild's workflow growth in the size baseline The prebuild opt-in adds two guarded blocks to the review job — the attempt-budget reconciliation and the shell-tool cover — taking qwen-code-pr-review.yml from 173139 to 177690 bytes. That is 4551 over the recorded baseline, 455 past the 4096 allowance, so the ratchet in .github/scripts/check-workflow-size.sh fails the whole Test job. The growth is real and stays: both blocks are shell that has to run in the job's own step, next to the EFFECTIVE_TIMEOUT_MINUTES and QWEN_HOME it reads, and their comments carry the arithmetic that scripts/tests/qwen-pr-review-workflow.test.js pins against PREBUILD_BUDGET_S and PREBUILD_COVER_HEADROOM_S. At 177690 the file is 34% of GitHub's 512000-byte start-runs limit and well under the repo's 470000-byte gate, so the ceiling is not in play — only the ratchet, which is what this line is for. * fix(review): read the full settings merge in the prebuild cover gate (#10423) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): close the prebuild opt-in to project .env files at load time R12-1: prebuildRequested()'s provenance check consults a per-process registry, and a child process inherits a file-sourced value with an empty registry — the refusal cannot fire across the process boundary (probe-verified in the review). Close the class at the source instead: QWEN_REVIEW_PREBUILD joins PROJECT_ENV_HARDCODED_EXCLUSIONS, so a project .env (the reviewed checkout's .qwen/.env included) never writes the key into the environment at all, on initial load and on reload (RELOAD_EXCLUDED_KEYS spreads the same list). CI's opt-in is a real step env and stays untouched; the read-time check remains as defence in depth for user-scope files this process loaded itself. Pinned from both sides: a loader-behavior test (project .env carrying the key never reaches process.env) and a membership pin over the real symbols; removing the exclusion reds both. Also from the review's deferred list: - covers() now accepts the settings-level 0 sentinel — 0 disables the shell timer entirely (shell.ts), which carries any budget; refusing it rejected the one configuration that can never kill the prebuild. - the cover-above-schema-ceiling behaviour R2-6 flagged as unpinned is now pinned against the real schema and through the real loader. - the ambient-QWEN_REVIEW_PREBUILD scrub in fetch-pr.test.ts is hoisted from the report-assembly describe to file scope (R5-1): every suite in the file that drives the handler passes the real env gate. - prebuildRequested's production default binding gets a direct test (R3-4's process-variable arm). --------- 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: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
…est.tsx (#10729) The standalone-chats and session-workflow-cockpit merges both added a 'language' member, leaving it declared twice in the ChatEditorRenderProps interface and twice in the renderChatEditorInto destructuring. esbuild rejects the redeclaration ("The symbol 'language' has already been declared"), which fails the web-shell suite and reds main CI.
…AME (#10671) * fix(autofix): clamp gate test load explicitly instead of via RUNNER_NAME The verification gate launches through an env -i allowlist that drops RUNNER_NAME, so the vitest configs' ECS load clamps (60s test/hook timeouts, maxWorkers 25%) silently deactivate inside the gate: tests run with 15s timeouts, unbounded workers and coverage collection on a host shared with other autofix jobs. Under pool saturation this produced both false rejections (#10171 round 3: 73 load-induced 15s timeouts in files the PR never touched, charged to the round) and gate deaths past the step's 60-minute cap that discarded verified fixes ("verification-gate error": #10171 rounds 1/2/5-7, #10543 five in a row). Pass the clamp values explicitly on both gate vitest invocations (the per-package --changed run and the bite check) so the verdict does not depend on env plumbing or runner naming, and disable coverage: nothing in the gate consumes it, and its collection dominated the overrun (72,000 CPU-seconds of collect in one 1,560s gate leg). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhZA7LdQXZjcjfiPsoZkqZ * fix(autofix): clamp the gate's third vitest leg and pin the clamps Addresses review round 1 on #10671. R1-6 The contracts check runs a web-shell vitest from inside the gate's own `env -i` child, and web-shell's config sets no timeouts at all, so that leg ran at vitest's 5s default on the same saturating host — the false-rejection class this PR removes, surviving in a sibling path. The clamp array moves above the contracts call and is handed to the shared script through AUTOFIX_VITEST_FLAGS; the issue-fix gate invokes the same script where RUNNER_NAME is present and leaves the variable unset, so its invocation is unchanged. R1-3 Nothing pinned that the clamps reach any invocation — every existing assertion is a prefix that ends before the expansion, so dropping it from a leg (or emptying the array, silent without `set -u`) stayed green while the gate reverted to 15s timeouts and coverage on. Three structural pins added on the review runner only, plus a contracts-script case that runs with AUTOFIX_VITEST_FLAGS set and asserts the flags reach npm. R1-4 The array hand-copies the ECS branch of three vitest configs, and inside the gate the CLI flags outrank the config — so raising an ECS ceiling to shelter a heavier test would leave the gate enforcing the old one and rejecting a fix that is green in normal CI. A parity test in scripts/tests/unit-vitest-configs.test.ts re-imports core, cli and acp-bridge under a stubbed ecs-qwen RUNNER_NAME (they read the env at import time) and asserts equality with the array parsed out of the shell script. R1-5 Narrowed the comment's claim, per the finding's own minimum. The residual is real and now named in the script: a handful of test files set their ceiling with a runtime `vi.setConfig` keyed on RUNNER_NAME, which outranks the CLI, so they keep their non-ECS values in here. Closing it needs a gate sentinel on both env -i allowlists plus a change in each file — a separate slice, not folded into this one. R1-1 is declined; see the thread. Its two premises did not reproduce against the lockfile-pinned vitest 1.6.1 under packages/sdk-typescript: the full suite passes with --maxWorkers=25% (37 files, 1747 tests, exit 0), and --maxThreads is rejected by 1.6.1 and 3.2.7 alike, so --maxWorkers is the spelling both majors accept rather than neither. * fix(autofix): pin the clamp witnesses and correct the unclamped-leg record Addresses review round 2 on #10671; re-verifies round 1's fixes with mutation probes. R2-1 The comments justifying the unclamped issue-fix leg rested on a premise that does not hold for that leg: web-shell's vitest config sets no timeouts and has no RUNNER_NAME branch, so the drift test runs at vitest's 5s default wherever it runs. Corrected at all four mirror sites (both scripts and both test comments): the review gate passes explicit clamps; the issue-fix gate and repo-hygiene's docker leg — the previously unnamed third caller — invoke the contracts script without the variable and accept the 5s default. The alternative (exporting AUTOFIX_VITEST_FLAGS in the issue-fix gate step) edits a workflow file this PR has never touched and stays out of scope. R2-2 --maxWorkers=25% is coerced to NaN by vitest 1.x; the lockfile-pinned 1.6.1 under packages/sdk-typescript survives only because its config sets a numeric poolOptions.threads.maxThreads, which tinypool reads before ctx.config.maxWorkers. Pin the shield: a new case derives vitest-1.x workspaces from nested lockfile copies and asserts each keeps the threads pool and a numeric maxThreads, failing with a directive if such a workspace is missing from the config registry. Mutation-verified red on shield removal. R2-3 The export is the only line carrying the clamps across the process boundary into check-autofix-contracts.sh; nothing pinned it. Added the structural pin plus an ordering assertion against the contracts call — deleting the export or moving it below the call now fails the suite. Both mutants verified red. R2-4 The contracts case's fake npm logged $*-joined argv, rendering a joined-blob flag byte-identically to separate words; the [*]-for-[@] mutant survived. The shim now logs one bracketed line per argv word and the four expectations in the case were updated; the mutant now fails. R1-3/R1-4/R1-5/R1-6 (round-2 commit) re-verified with mutation probes: dropping either invocation's expansion, dropping the assignment, emptying the array, drifting --testTimeout to 61000, and dropping the flag expansion inside the contracts script each turn an existing witness red. R1-1 remains declined: the deterministic crash does not reproduce at this head (the leg passes with sdk's shield present), but the round-2 rationale was wrong and is corrected on the thread; the residual risk is the shield R2-2 now pins. * test(autofix): pin both sides of the gate's AUTOFIX_VITEST_FLAGS transport Addresses review round 3 on #10671; both pins mutation-verified. R3-1 Nothing pinned the VITEST_LOAD_CLAMPS definition above its consumers: the existing pins are position-blind (toContain here, the parity regex in unit-vitest-configs.test.ts matches anywhere), so moving the array below its consumers left every pin green while bash expanded the then-unset array to zero words under the gate's `set -eo pipefail` without `-u` — AUTOFIX_VITEST_FLAGS goes empty and the package and bite legs lose all four clamps, silently reverting to the incident conditions. Added an explicit ordering pin against the star-join, the first consumer in script order, which pins the definition above every consumer. Outright deletion was already caught by the parity test's existence assertion; the move was the only surviving hole. Move mutant verified red (61668 < 34052 fails). R3-2 The remove side was pinned nowhere: moving `unset AUTOFIX_VITEST_FLAGS` above the contracts call (or deleting it) strips the export the drift leg inherits at child-spawn time, leaving the web-shell drift test at vitest's 5s default with every establish-side pin green. Added the symmetric ordering pin, contracts call before unset. Move and delete mutants both verified red (34333 < 34241 and 34306 < -1 fail). --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: 易良 <1204183885@qq.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )