Skip to content

feat(daemon): Support current-session scheduled tasks - #9838

Merged
doudouOUC merged 10 commits into
QwenLM:mainfrom
doudouOUC:docs/scheduled-task-current-session-entrypoints
Aug 26, 2026
Merged

feat(daemon): Support current-session scheduled tasks#9838
doudouOUC merged 10 commits into
QwenLM:mainfrom
doudouOUC:docs/scheduled-task-current-session-entrypoints

Conversation

@doudouOUC

@doudouOUC doudouOUC commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds explicit current-conversation binding to both scheduled-task creation entrypoints while preserving their existing defaults. The Scheduled Tasks form keeps Dedicated task conversation as the default and sends the outer selected conversation's sessionId only after the user selects Current conversation. cron_create keeps durable tasks unbound by default and adds sessionMode: 'current' for an explicitly requested durable current-conversation task.

Current-mode tool creation uses a daemon-only ACP control request. The ACP Session stamps its own session id and the executing prompt id, the bridge verifies that the connection owns that session and that the prompt matches its active prompt, and the daemon persists the task with sessionOwnedByTask: false. The public REST path retains its idle-session rule; only the prompt-matched private path may bind its active caller, and pending interactions remain ineligible.

The implementation reuses the existing #9361 lifecycle contract and durable schema. It adds conditional capability advertising, installs the callback on primary, startup-secondary, and dynamically attached workspace runtimes, keeps injected/partial bridges from advertising support, and relies on the existing keepalive, rehydration, deletion, and session scheduler behavior after persistence.

Why it's needed

#9361 added the daemon primitive for reusing an existing session, but users still could not request it from the Scheduled Tasks form or from an active chat. The form always minted a dedicated task conversation, while durable cron_create stayed unbound and could fire through a different shared per-project owner. These entrypoints let users explicitly keep future scheduled runs in the conversation where the task was requested without changing behavior for existing callers.

Reviewer Test Plan

How to verify

  1. Open Scheduled Tasks on a daemon that advertises scheduled_task_session_reuse. Create a task without changing the conversation selector and confirm a dedicated task conversation is created and the request omits sessionId.
  2. Select an idle top-level ordinary conversation, choose Current conversation, create the task, and confirm the request uses that session id and the persisted task has sessionOwnedByTask: false. Confirm busy, pending, parented, sourced, cross-workspace, and already-bound sessions cannot be selected.
  3. From an active daemon conversation, ask the agent to call cron_create with durable: true and sessionMode: 'current'. Confirm creation succeeds during that exact prompt, while missing prompt identity, a sibling session, an ineligible source, a pending interaction, or an older bridge fails without an unbound fallback.
  4. Switch to another conversation and confirm the scheduled turn remains in the bound conversation. Restart the daemon and confirm the bound conversation is rehydrated. Delete the task and confirm the caller-owned conversation remains open.
  5. Confirm a daemon with an injected bridge or without managed scheduled-task sessions does not advertise scheduled_task_session_reuse, and that primary, startup-secondary, and dynamically attached runtimes all receive the host callback when support is enabled.

Evidence (Before & After)

Before After
The Scheduled Tasks form had no conversation selector and never supplied the outer selected session id. The selector is capability-gated, defaults to Dedicated task conversation, validates the current conversation, and sends sessionId only after explicit selection. The component suite passes 51/51 tests.
cron_create had no current-session mode; durable tasks were always unbound. sessionMode: 'current' commits a caller-owned binding through an exact-prompt daemon path, while omitted/unbound behavior is unchanged. The core suite passes 16/16 tests.
The daemon had no authenticated tool path from the active prompt to #9361's existing-session creation contract. ACP, route, capability, Session, and runtime-wiring tests pass, including 124/124 bridge tests and 102/102 scheduled-task route tests.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

macOS, Node.js 22.22.3, npm 10.9.8. npm run build, npm run typecheck, and npm run lint passed after rebasing onto origin/main@3892ca32ca. Focused verification passed for Core (16), ACP bridge (124), scheduled-task routes (102), Web Shell (51), ACP Session (3 targeted), capability registry/server (1 targeted), primary/static-secondary/dynamic runtime wiring (2 targeted), and the formerly failing lazy content-generator test (27).

Risk & Scope

  • Main risk or tradeoff: the private control path crosses Core, ACP, daemon runtime ownership, and Web Shell capability boundaries; incorrect caller/prompt validation or partial capability advertising could bind the wrong session or offer unsupported behavior.
  • Not validated / out of scope: a manual wall-clock session-switch and daemon-restart E2E run, Windows/Linux local execution, PATCH rebinding, multiple tasks per session, task-history migration, non-ordinary sessions, and the legacy teardown-versus-reuse race tracked by fix(serve): serialize scheduled-task session teardown with binding via archive-coordinator lease #9415.
  • Breaking changes / migration notes: none. The new tool field and capability are optional, defaults are unchanged, public REST retains its idle rule, and the persisted task schema needs no migration.

Linked Issues

Follow-up to #8906 and #9361. Related to #9415.

中文说明

这个 PR 做了什么

这个 PR 为两个定时任务创建入口增加了显式绑定当前会话的能力,同时保留各自原有的默认行为。Scheduled Tasks 表单仍默认选择“独立任务会话”,只有用户选择“当前会话”后才发送外层选中会话的 sessionIdcron_create 的持久化任务默认仍保持未绑定,并新增 sessionMode: 'current',用于用户明确要求创建绑定当前会话的持久化任务。

工具的 current 模式通过 daemon-only ACP 控制请求创建任务。ACP Session 注入自身 session id 和当前执行中的 prompt id,bridge 校验该连接拥有这个会话且 prompt 与其 active prompt 匹配,daemon 随后以 sessionOwnedByTask: false 持久化任务。公开 REST 路径继续要求会话空闲;只有 prompt 精确匹配的私有路径可以绑定正在执行的调用方会话,存在待处理交互时仍然拒绝。

实现复用了 #9361 已有的生命周期约定和持久化 schema。它新增条件化 capability 广告,在 primary、启动时 secondary 和动态挂载的 workspace runtime 上安装 callback,禁止注入或仅部分支持的 bridge 广告能力,并在持久化之后继续复用现有 keepalive、rehydration、删除和 session scheduler 行为。

为什么需要

#9361 已经提供了复用已有会话的 daemon 基础能力,但用户仍无法从 Scheduled Tasks 表单或正在执行的聊天中请求该行为。表单始终创建独立任务会话,而持久化 cron_create 始终保持未绑定,可能由另一个共享的 per-project owner 执行。新增入口允许用户显式要求后续定时运行留在提出任务的会话中,同时不改变已有调用方的行为。

Reviewer 测试计划

如何验证

  1. 在广告 scheduled_task_session_reuse 的 daemon 上打开 Scheduled Tasks。不修改会话选择器创建任务,确认系统创建独立任务会话,并且请求省略 sessionId
  2. 选择一个空闲的顶层普通会话,选择“当前会话”并创建任务,确认请求使用该 session id,持久化任务包含 sessionOwnedByTask: false。确认忙碌、存在待处理交互、有 parent、有特殊来源、跨 workspace 或已绑定的会话无法选择。
  3. 在正在执行的 daemon 会话中要求 agent 使用 durable: truesessionMode: 'current' 调用 cron_create。确认任务可在该精确 prompt 执行期间创建;缺少 prompt 身份、指向 sibling 会话、来源不符合要求、存在待处理交互或使用旧 bridge 时都会失败,且不会回退为未绑定任务。
  4. 切换到另一个会话,确认定时执行仍出现在绑定会话中。重启 daemon,确认绑定会话被重新加载。删除任务,确认调用方所有的会话仍保持打开。
  5. 确认使用注入 bridge 或未管理定时任务会话的 daemon 不广告 scheduled_task_session_reuse;启用支持时,primary、启动时 secondary 和动态挂载 runtime 都收到 host callback。

证据(Before & After)

Before After
Scheduled Tasks 表单没有会话选择器,也不会传递外层选中的 session id。 选择器受 capability 控制,默认选择“独立任务会话”,校验当前会话,并且只在显式选择后发送 sessionId。组件测试 51/51 通过。
cron_create 没有当前会话模式;持久化任务始终未绑定。 sessionMode: 'current' 通过精确 prompt 的 daemon 路径提交调用方所有的绑定;省略/unbound 行为保持不变。Core 测试 16/16 通过。
daemon 没有从 active prompt 经过身份校验调用 #9361 已有会话创建约定的工具路径。 ACP、route、capability、Session 和 runtime 接线测试均通过,其中 bridge 测试 124/124、scheduled-task route 测试 102/102 通过。

测试平台

OS 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS,Node.js 22.22.3,npm 10.9.8。rebase 到 origin/main@3892ca32ca 后,npm run buildnpm run typechecknpm run lint 均通过。定向验证全部通过:Core(16)、ACP bridge(124)、scheduled-task routes(102)、Web Shell(51)、ACP Session(定向 3 个)、capability registry/server(定向 1 个)、primary/static-secondary/dynamic runtime 接线(定向 2 个),以及此前失败的 lazy content-generator 测试(27)。

风险与范围

  • 主要风险或取舍:私有控制路径跨越 Core、ACP、daemon runtime 所有权和 Web Shell capability 边界;调用方/prompt 校验错误或只广告部分能力可能绑定错误会话,或向用户提供当前环境不支持的行为。
  • 未验证 / 范围外:真实时钟下的会话切换与 daemon 重启手工 E2E、Windows/Linux 本地执行、PATCH 改绑、一个会话绑定多个任务、任务历史迁移、非普通会话,以及 fix(serve): serialize scheduled-task session teardown with binding via archive-coordinator lease #9415 记录的 legacy teardown-versus-reuse 竞态。
  • 破坏性变更 / 迁移说明:无。新增工具字段和 capability 都是可选的,默认行为不变,公开 REST 继续保留空闲规则,持久化任务 schema 不需要迁移。

关联问题

#8906#9361 的后续工作。与 #9415 相关。

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall direction is sound: keep the public REST idle rule, reuse the PR 9361 caller-owned sessionId contract, and add a daemon-only ACP path for cron_create because that tool necessarily runs in a busy turn.

Please tighten three design semantics before implementation. As written they can be implemented as the opposite of the intended behavior, especially the tool default vs REST “dedicated” meaning, and the standalone session filter vs ordinary Web Shell conversations.

Inline notes are on the specific paragraphs.

Comment thread docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md Outdated
Comment thread docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md Outdated
Comment thread docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md Outdated
Comment thread docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md Outdated
Comment thread docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md Outdated
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed the design review in 0627a51299.

Review item Action
Form dedicated vs. tool unbound defaults Fixed: added the three-outcome table and changed the tool option to unbound | current.
Ordinary-session source eligibility Fixed: added an exact top-level unset/default allow-list and explicit deny matrix. The document now uses standalone only for the real reserved source value.
ACP exact-turn attestation Fixed: public REST never skips idle; the trusted runtime stamps callerSessionId and forwards the executing promptId, which must match activePromptId.
Scheduled Tasks selected-session semantics Fixed: current means the outer selected session; no selection disables it, split panes do not win, and non-selected activity is ignored.
Capability/callback consistency Fixed: one condition gates capability advertising and callback installation across primary and dynamic workspace runtimes.

The PR description was updated to match the corrected defaults and trust model. Verification: Prettier check and git diff --check pass.

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed 0627a51 against the previous design notes.

The five earlier issues are addressed: distinct form-dedicated vs tool-unbound vs current outcomes, an exact ordinary-session allow-list, a correctly scoped ACP trust/prompt binding, selected-session semantics on the tasks page, and process-wide capability/callback wiring.

Remaining: identifier drift that will leak into the implementation PR if copied literally. See the inline note.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Reviewed the new identifier-drift suggestion on 0627a51299. No source change or new commit was needed: the comment contrasts identical spellings, while the design already matches the current bridge-entry, REST-summary, and persisted-task identifiers. The thread has been answered and resolved. Current substantive CI checks are green; the older cancelled route runs were superseded by the latest passing run.

@doudouOUC
doudouOUC force-pushed the docs/scheduled-task-current-session-entrypoints branch from 0627a51 to 5c65b19 Compare August 24, 2026 09:21
@doudouOUC doudouOUC changed the title docs(scheduled-tasks): Design current-session creation entrypoints feat(daemon): Support current-session scheduled tasks Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Implementation is now available in 5c65b19edb.

Verification after rebasing onto origin/main@b5aec6691e:

  • Core cron_create: 16/16 passed.
  • ACP bridge: 124/124 passed.
  • Scheduled-task routes: 102/102 passed, including the trusted active-prompt path, pending-interaction refusal, caller ownership, lifecycle, concurrency, capacity, and generation rollback coverage.
  • Web Shell Scheduled Tasks dialog: 51/51 passed.
  • ACP Session current-mode forwarding/older-bridge behavior: 2 targeted tests passed.
  • Capability registry/server: 3 targeted tests passed.
  • Primary, startup-secondary, and dynamically attached runtime callback wiring: 2 targeted tests passed.
  • npm run lint passed, and git diff origin/main --check is clean.

Manual wall-clock session-switch and daemon-restart E2E has not been run yet; the PR keeps the existing #9361 keepalive/rehydration and caller-owned deletion paths unchanged, and those lifecycle consumers were audited directly.

Known inherited base issue: after the latest rebase, full npm run build and npm run typecheck stop in unchanged packages/core/src/core/contentGenerator.test.ts:776. #9676 removed countTokens from the ContentGenerator interface, while the current origin/main test still calls it. The same full build/typecheck passed on the prior base before rebasing; this PR does not modify that area.

@doudouOUC
doudouOUC marked this pull request as ready for review August 24, 2026 09:52
@doudouOUC
doudouOUC enabled auto-merge August 24, 2026 09:52
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

doudouOUC and others added 3 commits August 24, 2026 17:57
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Re-run via @qwen-code /triage on 2026-08-26 06:45 UTC — still no new commits since the full gate pass at cf63c6e82e; the gate verdict is unchanged and the size numbers were independently recomputed again this pass (identical). The one new signal since the last update: the third sandboxed verification round at this head, in flight during the previous pass, settled at 2026-08-26 03:41 UTC — merge-ready again (details in the Stage 2 comment). No new commits or reviews from anyone since; the gate's position is unchanged.

Template: complete ✓

Problem: real, not theoretical. #9361 shipped the daemon primitive for binding a scheduled task to an existing session, but no user-facing entrypoint can request it — the Scheduled Tasks form always mints a dedicated conversation, and durable cron_create stays unbound. This PR wires the two entrypoints onto that merged contract, with linked follow-ups (#8906, #9361) and the live race tracker (#9415) explicitly deferred.

Direction: aligned — the established continuation of the scheduled-task session-reuse work in this repo, not a new direction.

Size: touches core paths (packages/core/src/config, packages/core/src/tools/cron-create.ts) plus cross-package changes in acp-bridge, cli/serve, and web-shell. Recomputed this pass: 1073 production-logic lines, 903 test, 348 docs. A feat is not hard-blocked on size, but 500+ production lines from a fork escalates to maintainer awareness, and the PR is also past the 1000-line large-PR advisory. The two entrypoints share one creation command, so splitting isn't natural — noted, not blocking.

Approach: scope still right. The delta since the last reviewed head (8ca008d370) remains exactly two feedback-address commits (140+/11− across 6 files): @wenshao's live-verified one-line blocker fix and the error-surfacing improvement his real-daemon run asked for. No drive-by growth.

Risk: Stage 1e still flags one high-risk path — packages/cli/src/acp-integration/session/Session.ts (acp-integration correlates with post-merge reverts in this repo's history) — so full review depth and real test evidence apply. Context carried from the previous pass: the maintainer's real-daemon verification found one genuine blocker on the pre-fix head (a prompt-id namespace mismatch); the current head carries his exact A/B-verified fix plus a seam-pinning regression test, and both were re-verified against the freshly fetched diff in this pass.

Moving on to code review. 🔍 Given the size and the daemon/ACP surface, this needs @yiliang114's sign-off regardless of the bot's verdict.

中文说明

@qwen-code /triage 于 2026-08-26 06:45 UTC 复跑——自 cf63c6e82e 的完整门禁审查后仍无新提交;门禁结论不变,规模数字本次再次独立重算(结果一致)。上次更新后唯一的新信号:该提交上的第 3 轮沙箱验证(上轮复跑时进行中)已于 2026-08-26 03:41 UTC 收敛——再次判定 merge-ready(详见 Stage 2 评论)。此后无任何新提交或审查,门禁立场不变。

模板:完整 ✓

问题:真实存在,不是理论问题。#9361 已合入"定时任务绑定已有会话"的 daemon 原语,但没有任何用户入口可以请求它——表单总是新建独立会话,持久化 cron_create 也始终不绑定。本 PR 把两个入口接到该已合入约定上,关联已合入的 #8906/#9361,并明确延后仍开放的 #9415 竞态跟踪。

方向:对齐——仓库内定时任务会话复用工作的既定延续,不是新方向。

规模:触及核心路径(packages/core/src/configpackages/core/src/tools/cron-create.ts)及 acp-bridgecli/serveweb-shell 的跨包改动。本次重新计算:1073 行生产逻辑、903 行测试、348 行文档。feat 不因规模硬阻断,但来自 fork 的 500+ 生产行提请维护者知悉,且已越过 1000 行大 PR 建议线。两个入口共用一个创建命令,拆分并不自然——仅提示,不阻断。

方案:范围依然合理。上次审查头(8ca008d370)之后的增量仍是两个反馈处理提交(6 个文件 140+/11−):@wenshao 实测验证过的一行阻断修复,以及其实测运行要求的错误呈现改进。无顺手扩张。

风险:Stage 1e 仍命中一条高风险路径——packages/cli/src/acp-integration/session/Session.tsacp-integration 与本仓库历史回滚相关)——适用完整审查深度与真实测试证据。沿用上一轮的背景:维护者的真实 daemon 验证在修复前的提交上发现一个真实阻断(prompt id 命名空间不一致);当前提交携带其 A/B 验证过的原样修复及钉住接缝的回归测试,本次已对照重新拉取的 diff 复核。

进入代码审查 🔍 鉴于规模和 daemon/ACP 影响面,无论机器人结论如何都需要 @yiliang114 签字。

Qwen Code · qwen3.8-max

Reviewed at cf63c6e82e1144d17252e0d3a606fd99f65d87a1 · re-run with @qwen-code /triage

@doudouOUC
doudouOUC force-pushed the docs/scheduled-task-current-session-entrypoints branch from 5c65b19 to 4693c7a Compare August 24, 2026 10:06
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Rebased onto origin/main@3892ca32ca and force-pushed 4693c7aa5b with an exact lease to pick up the upstream fix for the inherited contentGenerator.test.ts failure.

Item Result
PR patch integrity git range-diff reports all three commits patch-identical; no PR behavior changed during rebase.
Full verification npm run build, npm run typecheck, and npm run lint passed.
Focused verification Core 16/16, ACP bridge 124/124, scheduled-task routes 102/102, Web Shell 51/51, ACP Session 3 targeted, capability gate 1 targeted, runtime wiring 2 targeted, lazy content-generator 27/27.
Review feedback No new external feedback required action; all 6 review threads remain resolved.
PR description Updated the English and Chinese environment/risk sections to remove the now-stale inherited-failure note.

CI is rerunning on the new head. The remaining validation boundary is the manual wall-clock session-switch plus daemon-restart E2E already called out in the PR.

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Testing evidence carried: unattended CI run (issue_comment re-run) — PR code is never executed here. The testing section quotes the PR's own CI results for the reviewed head, fetched fresh via the API again on 2026-08-26 06:53 UTC, plus the three completed sponsored sandboxed verification reports for this exact head. The author's self-reported local numbers are quoted only as their claim.

No new commits since the previous full review at this SHA, so the findings below stand as established there. This pass re-checked the diff at cf63c6e82e for drift: the prompt-id precedence one-liner, the seam-pinning regression test, and the sessionOwnedByTask: false persistence are all still present — no drift.

Code review — the two feedback commits since 8ca008d370, the R3-1 dispute, and round 5

The previous pass's findings stand: the six round-1 Criticals, @yiliang114's CI-breaker, and the integration-test race fix were each verified in code at 8ca008d370 and are all still landed at this head. What is new since: 16a5786bfe and cf63c6e82e — 140+/11− across six files. I read the full delta.

@wenshao's live-verified blocker is fixed with his exact fix. His real-daemon run at 8ca008d370 proved cron_create sessionMode:'current' failed 4/4 because the tool sent the core turn-counter id (<session>########<turn>) while the bridge gate checks the daemon-minted prompt UUID. The current head applies his A/B-verified one-liner — getInvocationContext()?.promptId ?? req.promptId in the Session creator — so the daemon's own invocation-context id (the same value it stores as activePromptId) wins, with the tool-supplied id as fallback. The regression test was renamed to "uses the trusted daemon prompt id for a current-session task" and now runs the creator inside runWithInvocationContext with a daemon-shaped id while passing a turn-counter-shaped id as the request's promptId, asserting the daemon id is what reaches the wire — that pins the exact seam the defect lived in, which is what the maintainer asked for.

The [object Object] error-surfacing issue is fixed at all three layers. The bridge now maps only a shape-validated ExistingSessionScheduledTaskCreateError (name + finite numeric status + non-empty string code) to a structured ACP error carrying errorKind, status, and a bounded hint — mirroring the existing preserveFsErrorOverAcp pattern — while unknown host errors stay opaque by design. The daemon Session restores a readable errorKind: hint message before cron_create reports it, and the tool uses the shared getErrorMessage helper so a plain JSON-RPC object can no longer stringify to [object Object]. Tests cover all three layers (bridge mapping, Session surfacing, tool display).

The R3-1 dispute — adjudicated against the code, not the reply threads. Round 3's sole new Critical claimed a parented/channel-sourced session can become eligible and bind a task after a daemon restart, because lineage persists only in the child transcript and the spawn-time persist can fail. I verified both sides hop by hop at this commit:

  • The finding's first two hops are real: both gates (the bridge's handleCreateCurrentSessionScheduledTask and the route's assertReusableScheduledTaskSession via getSessionSummary) read lineage from live bridge state, and the restore path re-seeds parentSessionId/sourceType/sourceId only from transcript records.
  • But the decisive hop — "persist fails, yet a restorable transcript exists later" — is unreachable with the real recorder. recordParentSession/recordSessionSource await appendRecordStrict; every write serializes on one operationTail; and a failed append calls enterWriteFailure, which permanently sets acceptingWrites=false and latches writeFailure — every later strict append throws and every fire-and-forget append becomes a no-op on that recorder. No recording service at all means persisted:false and no writer that could ever produce a restorable transcript. A bridge-side timeout cannot reorder this either: the uncancelled metadata write is already queued ahead of any later prompt record on the same tail.
  • The bridge tests the finding leaned on fake the child ext-method to return persisted:false while continuing to behave as a healthy writer — a combination the real recorder state machine does not permit. The finding itself notes no unmodified-code witness reaches the trigger.

So I agree with the author's resolution: R3-1 does not stand as a blocker at this commit. One honest caveat, narrower than the finding: a fresh recorder instance after a daemon restart could in principle write new turns into an existing-but-lineage-less transcript left behind by a rare creation-time append failure. That state is indistinguishable from a legitimate top-level session (so the finding's minimal fix — "missing lineage means ineligible" — would break legacy transcripts), and the proper fix (a daemon-side lineage catalog or durable ineligibility marker) is defense-in-depth without a witness in unmodified code. Follow-up territory, which the author also conceded; I'd suggest filing a small tracking issue for it if the maintainer wants that hardening. Note also that the live-only-until-restart persist semantics predate this PR — the gate is a new consumer of them, not their origin.

No new critical defects at this head. The deferred /review round-3 items (D3-* probes, R2-2/R2-3/R2-4) remain recorded and non-blocking under that round's own convergence posture; of those, D3-6/D3-9/D3-10 are "mutant survived" test-strength probes, not observed defects. Round 4 re-posted them as COMMENTED (no new Critical, no change request).

Round 5 of /review landed since the last update (2026-08-25 20:15 UTC) — COMMENTED, no change request, no new Critical. It recorded thirteen Suggestion-level deferrals under its convergence posture. I independently spot-checked the ones with teeth:

  • The three design-doc accuracy claims hold. The new 347-line design doc cites a session_not_live error code that exists nowhere in this diff or in the base codebase (the codes the route actually throws are session_busy, session_already_bound, session_workspace_mismatch, session_source_ineligible, session_binding_unavailable, session_not_found, max_tasks_reached, and the invalid-input ones); it names a currentSessionSchedulingEnabled construction-time condition where the code's actual toggle is currentSessionSchedulingAvailable; and it presents an ExistingSessionCreateOptions type the implementation does not define. None of this affects runtime behavior — the code itself was verified correct — but a committed design doc that cites identifiers the code doesn't have undermines its own value, so these are worth the author fixing before merge.
  • The "dead switch" claim on the bootstrap's currentSessionSchedulingAvailable parameter is hygiene, not a defect: bootstrap deliberately hides the feature (its sole call site hardcodes false, which the tests pin), and the mounted runtime is the one that passes deps.bridge === undefined. Threading an always-false parameter through the bootstrap capability envelope instead of folding the constant away is a simplification opportunity, already recorded in the round-5 thread.

None of these block; they join the standing recorded deferrals as pre-merge cleanup or follow-ups.

sequenceDiagram
    participant P1 as User agent turn
    participant P2 as cron_create tool (core)
    participant P3 as ACP Session (cli)
    participant P4 as BridgeClient (acp-bridge)
    participant P5 as daemon handler (run-qwen-serve)
    participant P6 as shared create command (routes)
    participant P7 as cron task file
    P1->>P2: cron_create with sessionMode current and durable true
    P2->>P3: creator(cron, prompt, recurring, turn-scoped promptId)
    P3->>P3: prefer the daemon invocation-context prompt id
    P3->>P4: extMethod create-current with its own session id and that prompt id
    P4->>P4: verify ownership, exact active prompt, source eligibility
    P4->>P5: handler call carrying assertCallerPromptActive closure
    P5->>P6: createScheduledTaskWithExistingSession, source cron-tool
    P6->>P6: assert exact prompt before the lock and inside the lock
    P6->>P7: persist task with sessionOwnedByTask false
    P7-->>P1: task id and cron, no unbound fallback on failure
Loading
Files changed (27 of 27 shown)
File What changed
docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md Design doc; names four rejected alternatives and the chosen contract (round-5 flagged three identifiers in it that don't match the code — pre-merge cleanup)
docs/developers/qwen-serve-protocol.md Documents the new capability in the feature table
integration-tests/cli/qwen-serve-routes.test.ts Adds the new feature to the baseline capabilities expectation (bootstrap-tag filtering documented)
packages/acp-bridge/src/bridge.ts Passes the new handler option into BridgeClient
packages/acp-bridge/src/bridgeClient.test.ts Dispatch tests: forged identity, ineligible sources, revalidation before commit, structured business-error mapping
packages/acp-bridge/src/bridgeClient.ts Validates the create-current control method, stamps the assertion closure, maps recognized domain rejections to structured ACP errors
packages/acp-bridge/src/bridgeOptions.ts Handler, info, and result types for the new callback
packages/acp-bridge/src/status.ts Registers the create-current serve-control ext method
packages/cli/src/acp-integration/session/Session.test.ts Daemon-prompt-id precedence pin, stamping, non-daemon no-op, structured-rejection surfacing tests
packages/cli/src/acp-integration/session/Session.ts Registers the creator under QWEN_CODE_SERVE, prefers the daemon invocation-context prompt id, restores readable rejections
packages/cli/src/serve/capabilities.ts Capability registry entry and conditional toggle
packages/cli/src/serve/routes/scheduled-tasks.test.ts Eligibility, cron-tool binding, retry hint, and owner-lookup mapping tests
packages/cli/src/serve/routes/scheduled-tasks.ts Shared creation command extracted; REST route moved onto it
packages/cli/src/serve/run-qwen-serve.test.ts Bootstrap must not advertise; mounted runtime must; callback wiring checks
packages/cli/src/serve/run-qwen-serve.ts Installs the handler on primary, startup-secondary, and dynamic runtimes; bootstrap hides the feature
packages/cli/src/serve/server.test.ts Capability predicate tests; managed-only advertising
packages/cli/src/serve/server.ts Gates advertising on managed task sessions plus the toggle
packages/cli/src/serve/server/serve-features.ts Threads the toggle into the feature serializer
packages/core/src/config/config.ts Creator slot on Config (set and get)
packages/core/src/index.ts Exports the new creator types
packages/core/src/tools/cron-create.test.ts sessionMode behavior, no-fallback, plain-object error display tests
packages/core/src/tools/cron-create.ts sessionMode parameter; current mode requires durable plus an active prompt id; readable error messages
packages/web-shell/client/App.test.tsx App-boundary tests: stale-response ordering and prompt-boundary refresh
packages/web-shell/client/App.tsx Session summary refresh per prompt boundary; capability flag with full optional chain
packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx Selector behavior matrix including capability-loss reset
packages/web-shell/client/components/dialogs/ScheduledTasksDialog.tsx Conversation selector, sessionMode reset on capability loss, client-side eligibility
packages/web-shell/client/i18n.tsx Bilingual strings for the selector and hints

CI test evidence (reviewed head cf63c6e82e, fetched once — no polling)

CI on this head is fully green and settled — re-fetched again on 2026-08-26 06:53 UTC for this pass; no drift. Every check-run on the head has completed: 34 success, 212 skipped by design, and 3 cancelled entries that are the superseded orchestration attempts from the 2026-08-24 22:04 force-push (they predate the final head and are not PR CI), and every pull_request-event workflow on this head shows completed/success. The one red this head ever carried — 3 timing-race failures in coordination-harness.test.ts inside Test (ubuntu-latest, Node 22.x), a team-coordination utility this PR does not touch — did not repeat on its re-run: the job landed green at 2026-08-25 04:16 UTC. The earlier classification — pre-existing flake under runner SIGKILL noise, no causal path from this diff — stands, and the settled outcome is consistent with it.

Everything this PR actually touches is green at this head: acp-bridge, cli (including the route and Session suites with the new tests), core, Desktop Shell (both), web-shell E2E Smoke, Serve A/B, web-shell visuals capture, Security Checks, and the SDK Java matrix. Test (macos/windows) and Integration Tests (CLI, No Sandbox) show skipped on every PR by design — they run only in the merge queue.

Final CI results for cf63c6e (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Capture web-shell visuals (ubuntu-latest, Node 22.x) ✅ success
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
macos-latest / Java 21 ✅ success
Real daemon E2E / Java 11 ✅ success
Secret scan (TruffleHog) ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
windows-latest / Java 21 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Sandboxed verification at exactly this head has already landed, closing the gap the previous pass flagged. The round-2 sponsored run (the author lacks write access — pre-execution risk screen and full workspace wipe applied; read the report with the same skepticism as the fork's own CI logs) verified cf63c6e82e and returned merge-ready (agent verdict): 130/130 scripted assertions passed, 12/12 guard mutants killed, A/B against the base build on the real built dist/ of both trees. The load-bearing claim — cron_create sessionMode:'current' binds the active caller session with an exact prompt match and persists sessionOwnedByTask: false, failing closed on every identity mismatch — passed 34/34 harness cells on head while the method does not exist at all on base, which is precisely the live seam @wenshao's unit-green/live-red run exposed. Round 1's sole informational finding (the description's "retains its idle-session rule" understates the REST eligibility tightening) was re-measured and stands, non-blocking. The third round at this head — dispatched by the 2026-08-26 02:14 UTC trigger — settled at 2026-08-26 03:41 UTC, shortly after the previous pass posted: merge-ready again — 127/127 scripted assertions passed, 12/12 guard mutants killed (the flakiness gate timed out at 2 of 5 rounds; the completed rounds agreed). Three sponsored verification rounds have now landed at exactly this head, all merge-ready.

Not verified: live end-to-end behaviour at this head beyond the hermetic sandboxed harness (this run never executes PR code); Windows/macOS behaviour (author's table marks both untested; those matrix jobs run only in the merge queue). The author's local suite numbers are their claim — though CI on this head independently passed the same suites.

中文说明

无人值守 CI 复跑,不执行 PR 代码;测试证据来自 2026-08-26 06:53 UTC 再次经 API 重新拉取的被审查提交自身 CI 结果,以及该提交上已完成的三轮受助沙箱验证报告。作者自报数字仅作转述。

自上次在该 SHA 的完整审查后无新提交,下列结论维持该轮建立时的判定。本次重新比对 cf63c6e82e 的 diff 以排查漂移:prompt id 优先级一行修复、接缝钉扎回归测试、sessionOwnedByTask: false 持久化均仍在位——无漂移。

代码审查要点(与上轮一致):@wenshao 实测发现的阻断已按其 A/B 验证过的原样修复——Session 创建器改为 getInvocationContext()?.promptId ?? req.promptId,daemon 调用上下文中的 prompt id(与其 activePromptId 同源)优先,工具侧 id 兜底;回归测试在 runWithInvocationContext 内以 daemon 形态 id 运行创建器、以回合计数器形态 id 作为请求参数,断言到达 wire 的是 daemon id——恰好钉住缺陷所在接缝。[object Object] 错误呈现问题在三层全部修复:bridge 仅把通过形状校验的 ExistingSessionScheduledTaskCreateError 映射为携带 errorKind/status/有界 hint 的结构化 ACP 错误(沿用 preserveFsErrorOverAcp 模式);Session 还原可读的 errorKind: hint 消息;工具改用共享 getErrorMessage。三层均有测试。

R3-1 争议按代码裁定(与上轮一致):前两跳属实,但决定性的"持久化失败却仍存在可恢复 transcript"一跳在真实录制器下不可达——严格追加失败即永久锁死写入(enterWriteFailure),无录制服务则既 persisted:false 也不存在可产生可恢复 transcript 的写入方。**R3-1 在本提交不构成阻断。**保留一个比该发现更窄的诚实项:重启后新录制器实例理论上可写入创建期追加失败遗留的无谱系 transcript——属无见证的纵深防御,适合后续跟进。本提交未发现新的关键缺陷;第 3/4 轮延后项保持记录、不构成阻断(第 4 轮以 COMMENTED 发布,无新 Critical)。

/review 第 5 轮已于上次更新后落地(2026-08-25 20:15 UTC)——COMMENTED、无变更请求、无新 Critical,仅按收敛姿态记录 13 条建议级延后项。本次独立抽查了其中要害项:三条设计文档准确性指控成立——新增的 347 行设计文档引用了 session_not_live 这一在 diff 与基线代码中均不存在的错误码(路由实际抛出的是 session_busysession_already_boundsession_workspace_mismatchsession_source_ineligiblesession_binding_unavailablesession_not_foundmax_tasks_reached 及非法输入类);将代码中实际名为 currentSessionSchedulingAvailable 的开关写作 currentSessionSchedulingEnabled;并给出了实现中并不存在的 ExistingSessionCreateOptions 类型。均不影响运行时行为——代码本身已验证正确——但已提交的设计文档引用代码中不存在的标识符会损害其自身价值,建议作者合并前修正。"死开关"指控(bootstrap 的 currentSessionSchedulingAvailable 参数)属卫生问题而非缺陷:bootstrap 本就刻意不广告该特性(唯一调用点硬编码 false 且有测试钉住),挂载运行时才传 deps.bridge === undefined;把恒为 false 的参数穿过 bootstrap 能力封装而非直接内联常量只是简化空间,已记录于第 5 轮线程。以上均不阻断,与既有延后项一并作为合并前清理或后续跟进。

CI 证据:本提交 CI 已全绿且已收敛——本次于 2026-08-26 06:53 UTC 再次重新拉取,无漂移。该提交所有 check-run 均已完成:34 成功、212 按设计跳过、3 个 cancelled 为 2026-08-24 22:04 强制推送被取代的编排尝试(早于最终提交、非 PR CI);本提交所有 pull_request 事件工作流均已完成且成功。唯一红过的仍是 Test (ubuntu-latest, Node 22.x)coordination-harness.test.ts 的三个时序竞态失败(本 PR 未触及),重跑未复现:该作业于 2026-08-25 04:16 UTC 转绿。此前"既有抖动(runner SIGKILL 噪声、无因果路径)"的分类维持不变,收敛结果与之一致。本 PR 触及的套件全部为绿。macos/windows 与 CLI 集成测试按设计仅在合并队列运行。

沙箱验证已在该提交上完成,补上了上轮标记的缺口:第 2 轮受助运行(作者无写权限——运行前风险筛查与工作区清除;报告应如 fork CI 日志般保持怀疑阅读)验证 cf63c6e82e,判定 merge-ready(agent 判定):130/130 脚本断言通过、12/12 守卫变异全部被杀死、与 base 构建成对 A/B。承重结论——cron_create sessionMode:'current' 以精确 prompt 匹配绑定活跃调用方会话、持久化 sessionOwnedByTask: false、一切身份不匹配均失败关闭——在 head 上 34/34 通过,而 base 上该方法根本不存在;这正是 @wenshao"单测绿、真实链路红"所暴露的接缝。第 1 轮唯一信息级发现(描述中"保留空闲规则"的措辞低估了 REST 资格收紧)复测后仍成立,非阻塞。该提交上的第 3 轮由 2026-08-26 02:14 UTC 触发派发,已于 2026-08-26 03:41 UTC(上次复跑发布后不久)收敛:再次判定 merge-ready——127/127 脚本断言通过、12/12 守卫变异全部被杀死(抖动门 5 轮仅完成 2 轮,完成的轮次结论一致)。该提交至此已有 3 轮受助沙箱验证落地,全部 merge-ready。

未验证:除密封沙箱 harness 之外的本提交真实端到端行为(本运行不执行 PR 代码);Windows/macOS 行为(作者未测,矩阵作业仅在合并队列运行)。作者本地套件数字仅为其声明——不过本提交 CI 已独立跑通相同套件。

Qwen Code · qwen3.8-max

Reviewed at cf63c6e82e1144d17252e0d3a606fd99f65d87a1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — unchanged: the evidence pile grew stronger and the hold stayed exactly where it was. The head is the same (cf63c6e82e); CI re-fetched again at 2026-08-26 06:53 UTC is still fully green and settled; and the verification round that was in flight during the last pass settled at 2026-08-26 03:41 UTC — merge-ready again (127/127 assertions, 12/12 guard mutants killed), the third settled sponsored round at exactly this head. The remaining hold is policy, not doubt — a 1000+-line fork feature needs maintainer sign-off, and two standing CHANGES_REQUESTED reviews need human disposition.

⏸️ Deferring to @yiliang114 (and @wenshao, whose real-daemon verification drove the last round) — the same two things only humans can close:

  1. Policy cap, not doubt. 1073 production-logic lines across core, acp-bridge, cli/serve, and web-shell from a fork is the class of change this gate refuses to approve automatically, however clean it reads. That is the cap working as designed, not a reflection on the author.
  2. Standing reviews need disposition. @wenshao's CHANGES_REQUESTED predates the fix for his own blocker — the re-pass is his to make. The /review round-3 CHANGES_REQUESTED carries the one Critical (R3-1) my Stage 2 review adjudicated as non-blocking at this commit, and rounds 4 and 5 then posted as COMMENTED with recorded deferrals only — no new Critical, no change request. Dismissal or re-review is the maintainer's call.

New signal since the 2026-08-25 14:24 UTC pass (cumulative through this 2026-08-26 06:45 UTC re-run):

  • /review round 5 posted as COMMENTED with thirteen recorded Suggestion-level deferrals — no new Critical, no change request. The three design-doc accuracy items in it I verified hold on spot-check (a cited session_not_live error code that exists nowhere, a currentSessionSchedulingEnabled condition name the code doesn't use, and an ExistingSessionCreateOptions type only the doc defines): runtime-unaffected, but worth the author fixing before merge — a committed design doc should cite identifiers the code actually has. The "dead switch" item is hygiene: bootstrap hardcodes the feature off by design.
  • CI re-fetched and still fully green — every check-run on this head completed (34 success, 212 skipped by design, 3 cancelled superseded orchestration attempts from the force-push), every pull_request-event workflow successful. No drift since the settle at 2026-08-25 04:16 UTC.
  • Sandboxed verification round 3 at this head settled — dispatched by the 2026-08-26 02:14 UTC trigger, landed at 2026-08-26 03:41 UTC (after the previous pass posted): merge-ready again — 127/127 scripted assertions, 12/12 guard mutants killed, flakiness gate timed out at 2 of 5 rounds with the completed rounds agreeing. Three sponsored rounds have now landed at exactly this head, all merge-ready.

Resolved before that, and still standing: the problem is real, the direction is aligned, the blocker got the structural fix its reporter A/B-verified (plus a seam-pinning regression test), the error-surfacing follow-up landed at all three layers, CI is green, and a hermetic live run at this head passes. What remains is human by design: the policy call on a fork feature of this size, and the disposition of two standing reviews. The standing /review deferrals (D3-* probes, R2-2/R2-3/R2-4, and now round-5's thirteen) are recorded in the threads — worth a maintainer's glance before merge or a follow-up list after.

Once @wenshao re-passes the path he broke, a maintainer signs off on the size, and the design-doc identifiers are corrected, there is nothing further from the gate's side.

中文说明

结论不变:证据继续增强,而保留仍在原地。提交未变(cf63c6e82e);CI 于 2026-08-26 06:53 UTC 再次重拉,仍全绿且已收敛;上次复跑时进行中的沙箱验证轮已于 2026-08-26 03:41 UTC 收敛——再次判定 merge-ready(127/127 断言、12/12 守卫变异被杀死),这是该提交上第 3 轮收敛的受助验证。剩余的保留纯属政策而非质疑:来自 fork 的 1000+ 行生产逻辑需要维护者签字,两条现存 CHANGES_REQUESTED 审查需要人类处置。

⏸️ 转交 @yiliang114(以及驱动上一轮的 @wenshao)——仍是两件只有人类能定夺的事:

  1. 政策性上限,不是质疑。 来自 fork 的 1073 行生产逻辑横跨四个包,属于门禁无论如何干净都不自动批准的变更类别。这是上限机制的正常运作,不是对作者的否定。
  2. 现存审查需要处置。 @wenshaoCHANGES_REQUESTED 早于对其阻断的修复——复审由其完成。/review 第 3 轮的 CHANGES_REQUESTED 仅有一条 Critical(R3-1),Stage 2 已裁定其在本提交不构成阻断;第 4、5 轮随后均以 COMMENTED 发布,仅记录延后项,无新 Critical、无变更请求。解除或重跑由维护者决定。

2026-08-25 14:24 UTC 之后的新信号(累计至本次 2026-08-26 06:45 UTC 复跑):

  • /review 第 5 轮以 COMMENTED 发布,记录 13 条建议级延后项——无新 Critical、无变更请求。其中三条设计文档准确性指控经抽查成立(引用了不存在的 session_not_live 错误码、使用了代码中并不存在的 currentSessionSchedulingEnabled 条件名、给出了仅文档中定义的 ExistingSessionCreateOptions 类型):不影响运行时行为,但建议作者合并前修正——已提交的设计文档应当引用代码中真实存在的标识符。"死开关"一项属卫生问题:bootstrap 本就按设计硬编码关闭该特性。
  • CI 重新拉取,仍全绿——该提交所有 check-run 均已完成(34 成功、212 按设计跳过、3 个取消项为强制推送被取代的编排尝试),所有 pull_request 事件工作流成功。相对 2026-08-25 04:16 UTC 收敛后无漂移。
  • 该提交上的第 3 轮沙箱验证已收敛——由 2026-08-26 02:14 UTC 触发派发,2026-08-26 03:41 UTC(上次复跑发布后)落地:再次 merge-ready——127/127 脚本断言、12/12 守卫变异被杀死,抖动门 5 轮仅完成 2 轮且结论一致。该提交至此已有 3 轮受助验证落地,全部 merge-ready。

更早已落定且维持:问题真实、方向对齐、阻断得到了报告者 A/B 验证过的结构性修复(并附接缝钉扎回归测试)、错误呈现跟进在三层落地、CI 绿、该提交的密封真实运行通过。剩下的按设计属于人类:对该规模 fork 特性的政策判断,以及两条现存审查的处置。/review 各轮延后项(D3-* 探针、R2-2/R2-3/R2-4,以及第 5 轮的 13 条)均记录在回复线程中——合并前值得维护者一瞥,或合并后列入跟进清单。

一旦 @wenshao 复审通过他当时发现问题的路径、维护者对规模签字、设计文档标识符得到修正,门禁一侧再无事项。

Qwen Code · qwen3.8-max

Reviewed at cf63c6e82e1144d17252e0d3a606fd99f65d87a1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head cf63c6e. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

⚠️ No preview: one or more scenarios failed to render on this head — see the workflow run. This is not "no visual change" — a scenario that times out or throws produces no image. Fix the failing scenario (or a genuine regression it caught) and the preview returns on the next push.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head cf63c6e, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 12 scenario(s).

Qwen Code · serve A/B

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

Copy link
Copy Markdown
Collaborator Author

CI fix pushed in 1180fcd.

  • Documented the conditional scheduled_task_session_reuse capability so the protocol registry contract is complete.
  • Verified the focused contract test, repository build, typecheck, lint, formatting, diff checks, and two clean audit passes. The independent test-engineer verification also passed.
  • The Web Shell visual-preview workspace-sidebar failure is identical on the PR base and head and comes from an existing mock workspace-filtering bug; PR feat(daemon): Support current-session scheduled tasks #9838 does not modify that fixture or sidebar path.

Handled review threads in this push: 0. Existing review threads remain 6/6 resolved. Fresh CI was triggered by the push.

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

Copy link
Copy Markdown
Collaborator Author

CI fix pushed in 98d15a538f.

  • Made the Web Shell capability lookup fail closed when a partial or malformed workspace snapshot omits features, instead of crashing the Scheduled Tasks page.
  • Verified the formerly failing test and the complete App.test.tsx suite (540/540), repository build, typecheck, lint, changed-file Prettier check, diff checks, and two clean audit passes. Independent test-engineer reproduction and post-fix verification also passed.
  • The repository-wide Web Shell format check still reports five unchanged pre-existing files; the changed file itself is correctly formatted.

Handled review threads in this push: 0. Existing review threads remain 6/6 resolved. Fresh CI was triggered by the push.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at head 1180fcd — the security surface checks out, but there is one CI-breaking regression to fix first, so not approving yet.

Verified solid: the authorization chain for current-session binding — the ACP Session stamps its own sessionId server-side (not caller-supplied) plus the executing prompt id; the bridge rejects forged session/prompt identity and ineligible sources (tests pin forged-identity and ineligible-source rejections); the daemon route rejects cross-workspace (session_workspace_mismatch), ambiguous ownership (fail-closed), busy/pending/parented/sourced/already-bound sessions; allowActivePrompt is true only for the prompt-matched cron-tool source while the public REST path keeps the idle rule.

Blocking: the incomplete optional chain at App.tsx:12528 (workspace.capabilities?.features.includes(...)) — see the inline comment.

Not approving until the CI break is fixed; everything else in the PR reads as designed.

Comment thread packages/web-shell/client/App.tsx Outdated
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

The bot already has a review of its own on cf63c6e82e1144d17252e0d3a606fd99f65d87a1, which still stands.

机器人在 cf63c6e82e1144d17252e0d3a606fd99f65d87a1 上已有自己的评审,且仍然有效。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R2-4 capabilities-envelope assertion unreachable from any workspace test command — already reported (comment 3847526093)

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not reviewed: reverse audit — stopped at the 5-round cap without two consecutive dry rounds (round 5 reported new findings).

Not explored to full depth (tool budget reached): chunk 9: executing ScheduledTasksDialog.test.tsx under vitest (no node_modules installed in the review worktree; static verification only).

Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:

  • docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md:102 — [review] design doc cites fabricated session_not_live error code
  • packages/web-shell/client/components/dialogs/ScheduledTasksDialog.tsx:750 — [probe] formWorkspace non-locked branch untested (probe mutant survives)
  • packages/acp-bridge/src/bridge.ts:4061 — [probe] BridgeClient wiring line for onCreateCurrentSessionScheduledTask untested (probe mutant survives)
  • docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md:249 — [review] design doc names fabricated currentSessionSchedulingEnabled condition
  • packages/cli/src/serve/routes/scheduled-tasks.test.ts:796 — [review] eligible sourceType 'default' never pinned in the route source matrix
  • packages/web-shell/client/App.tsx:12534 — [review] hasActivePrompt live merge is untested
  • docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md:36 (+2 locations) — [review] unbound-stays-unbound claim falsified by daemon keepalive
  • packages/web-shell/client/components/dialogs/ScheduledTasksDialog.tsx:1002 — [review] submit-time fail-closed re-check for 'current' mode never exercised by tests
  • docs/design/2026-08-24-scheduled-task-current-session-entrypoints.md:199 — [review] design doc presents fabricated ExistingSessionCreateOptions type
  • packages/core/src/tools/cron-create.ts:102 — [probe] !creator half of fail-closed guard untested (probe mutant survives)
  • packages/cli/src/serve/run-qwen-serve.ts:1513 — [review] currentSessionSchedulingAvailable bootstrap parameter is a dead switch
  • packages/core/src/tools/cron-create.test.ts:14 — [probe] explicit sessionMode 'unbound' never pinned (probe flips, suite stays green)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had none either, so the next review re-reads the whole diff and will keep doing so until a round's marker carries an anchor again. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:reverse audit — stopped at the 5-round cap without two consecutive dry rounds (round 5 reported new findings)。

未探索到全部深度(达到工具调用预算):chunk 9:executing ScheduledTasksDialog.test.tsx under vitest (no node_modules installed in the review worktree; static verification only)

收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 12 条(原文未翻译,列表见上方英文部分)。

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有锚点,因此下一次评审将重读整个 diff——并会一直如此,直到某一轮的标记重新带上锚点。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

— qwen3.8-max via Qwen Code /review (v0.22.0)

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 127 passed · 0 failed · 127 total

Flakiness gate: ⚠️ timeout — only 2 of 5 rounds fit the 15-minute budget; the completed rounds agreed

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:127 通过 · 0 失败 · 127 总计

抖动门:⚠️ timeout — only 2 of 5 rounds fit the 15-minute budget; the completed rounds agreed

Verification report

PR #9838 — feat(daemon): Support current-session scheduled tasks (round 4)

Verdict: merge-ready — 127/127 scripted assertions passed (0 unexpected failures), 12/12 guard mutants killed, verified head cf63c6e82e1144d17252e0d3a606fd99f65d87a1 (merge-ref HEAD^2; base tip 48645c56f8ed3b7e0e2838d68685de1f753ad1a9).

This is a follow-up round. Round 3 verified the same PR head cf63c6e (merge-ready, 114/114) against base ef18a738. Since then the PR head did not move, but the base tip advanced to 48645c56 (#9993 "feat(web-shell): make compact view the only mode"). The old base ef18a738 is not reachable in this container's shallow object store (git cat-file -t fails for it and for the snapshot's baseRefOid 3892ca32), so the between-rounds base delta cannot be isolated per-commit; the aggregate base-side motion visible from here is HEAD^2..HEAD = 2,204 files (+71,987/−387,150). Of the PR's 27 files, 17 overlap base-side changes (including run-qwen-serve.ts, Session.ts, App.tsx, config.ts, bridge.ts, i18n.tsx) and the root package-lock.json moved base-side (+30/−3: a new @qwen-code/channel-dws workspace link at 0.22.0 and a node-repl-mcp version reset 0.21.11→0.1.0 — the PR itself touches no lockfile). No input closure therefore qualified for the carry-forward shortcut: every round-3 measurement was rebuilt and re-run at the new base, and both harnesses were rewritten from the published contract.

中文摘要
  • 结论merge-ready。127/127 脚本断言全部通过,0 个意外失败;12/12 守卫变异全部被杀死,无幸存者。
  • 上轮发现复核:唯一发现(信息级:REST 资格收紧范围超出描述中"保留空闲规则"的措辞)在新 base 48645c56复测后仍然成立——翻转单元 R3/R4/R5 在 base 上实测仍可绑定(201 + 持久化)、head 上拒绝(409);快照中的 PR 描述措辞未变。非阻塞。见 "Previous-finding status"。
  • A/B 结论(见证图 01–04):私有路径 head 35/35、base 20/20(机制不存在:-32601,无持久化);REST 路径 head 28/28、base 25/25,其中三个翻转单元(pending-interaction / parented / sourced:base 201 绑定 → head 409 拒绝)与七个 A/A 等价单元行为与上轮一致。
  • 定向门禁:7 个套件全绿;与上轮相比,run-qwen-serve 266→327、server 的 1 个 skip 变为活测试、web-shell 595→594,均为 base 侧测试增减(feat(web-shell): make compact view the only mode #9993 等),非 PR 回归。
  • 增量分析:PR head 未变;base 前移至 48645c56(2,204 文件的聚合增量;旧 base ef18a738 在本容器浅克隆中不可达,无法逐提交隔离)。合并完整性已验证:10 个无重叠 PR 文件在 HEAD^2..HEAD 上逐字节相同(0 行 diff);聚合有效 diff 与上轮一致(27 文件 +2132/−192,sha256 17d47434…);17 个重叠文件中的 PR 符号均存在。
  • 未覆盖:见下 "Not covered"。

Previous-finding status (round 3 → round 4)

# Finding (round 3) Severity Status at new base 48645c56
1 REST eligibility tightening is broader than the description's "the public REST path retains its idle-session rule": on base, POST /scheduled-tasks with a sessionId bound sessions carrying a pending interaction, a parentSessionId, or a sourceId (201); head rejects them 409 Informational Stands — re-measured, not diffed: cells R3/R4/R5 still flip on the new base (201 bound + persisted → head 409 session_busy / session_source_ineligible, nothing persisted; witness 02-rest-ab-base-flip-cells.png), and the snapshot's PR body still says "retains its idle-session rule" / "Breaking changes … none". The tightening matches the Reviewer Test Plan's own eligibility list, so it is evidently intended; the wording remains the only gap. Non-blocking.

All other round-3 measurements were re-run rather than carried: the base tip moved 2,204 files, 17 PR files overlap base-side changes, and the lockfile moved, so nothing had an unchanged input closure. Every re-run reproduced the round-3 observation; nothing worsened or moved.

Scope selection

  • Central claim (unchanged): cron_create with sessionMode: 'current' + durable: true binds the durable task to the session whose active turn executes the tool, via a daemon-only ACP control request (qwen/control/scheduled-task/create-current) that verifies connection-ownership and exact-prompt match, persisting sessionOwnedByTask: false; every identity mismatch fails closed with no unbound fallback.
  • Secondary 1: the shared creator (createScheduledTaskWithExistingSession) keeps the REST idle rule, tightens eligibility (pending interaction / parented / sourced sessions), and re-checks identity inside the task-file write lock.
  • Secondary 2: scheduled_task_session_reuse is advertised only when the daemon manages scheduled-task sessions with a daemon-created (non-injected) bridge; the Web Shell form defaults to Dedicated and sends sessionId only after explicit selection.

Delta analysis (round 3 → round 4)

  • Head unchanged: HEAD^2 is cf63c6e in rounds 2–4. The snapshot's 10-commit array cannot be checked against local history: git rev-list HEAD^1..HEAD^2 returns 1 commit (cf63c6e alone) while git rev-parse --is-shallow-repository is true — the shallow-boundary false positive the method warns about — so per-commit attribution stays out of reach and all evidence is for the aggregate HEAD^1..HEAD diff.
  • Base moved: old base ef18a738 and the snapshot's baseRefOid 3892ca32 are both absent from this container's object store (git cat-file -t fails for each), so the between-rounds delta cannot be isolated; the measured aggregate base-side motion is HEAD^2..HEAD = 2,204 files (+71,987/−387,150), overlapping 17 of the PR's 27 files plus the root lockfile. qwen-triage.yml shows no motion in that view.
  • Merge integrity: the 10 PR files with no base-side overlap are byte-identical between HEAD^2 and HEAD (0 lines of diff); the aggregate effective diff HEAD^1..HEAD is stat-identical to rounds 2–3 (27 files, +2132/−192; sha256 17d474343819803fd59a910b9b81eaa7433b7b4f52cf61065124dae452e6782d); and the PR's symbols are present in the merged overlap files (onCreateCurrentSessionScheduledTask ×3 in bridgeClient.ts, the wire constant in status.ts, #registerCurrentSessionScheduledTaskCreator in Session.ts, setCurrentSessionScheduledTaskCreator in config.ts, scheduled_task_session_reuse in capabilities.ts/App.tsx/qwen-serve-protocol.md, sessionMode ×6 in cron-create.ts, the scheduledTasks.session.* keys in i18n.tsx, CurrentSessionScheduledTask* types in core index.ts, and createScheduledTaskWithExistingSession/ExistingSessionScheduledTaskCreateError in routes/scheduled-tasks.ts).
  • Lockfile: the effective diff touches no lockfile, so both A/B arms share one dependency tree and the mirrored node_modules is a clean control. The base-side motion relative to the PR head (+30/−3) is workspace churn: the new @qwen-code/channel-dws link (0.22.0) and a node-repl-mcp version reset (0.21.11→0.1.0) — pre-existing base history, not a PR change.

A/B tables

Harnesses drive the real built dist/ code of each tree: h1-rest.mjs boots the real registerScheduledTasksRoutes on loopback HTTP with the real cron-task file store (the session bridge is the route module's documented structural seam, mirrored from the package's own test rig; SessionNotFoundError is the real class from the tree's bridgeErrors dist). h2-private.mjs constructs the real BridgeClient (24-slot positional constructor mirrored verbatim from bridgeClient.test.ts's makeClient) and wires its handler to the real createScheduledTaskWithExistingSession, mirroring run-qwen-serve's createCurrentSessionScheduledTaskHandler; dispatch goes through the real client.extMethod. Base control = scratch worktree at HEAD^1 (48645c56) with the root node_modules mirrored entry-by-entry preserving relative symlink targets (which is what re-points every @qwen-code/* link into the base tree; third-party entries shared — both arms carry the identical lockfile), nested per-package node_modules mirrored (verified third-party-only: no @qwen-code links inside any nested dir), scripts/generate-git-commit-info.js run, then coreacp-bridgecli rebuilt there (logs logs/base-build-*.log). Control validity asserted from inside the base tree: import.meta.resolve('@qwen-code/qwen-code-core')tmp/base-tree/packages/core/dist/index.js and @qwen-code/acp-bridgetmp/base-tree/packages/acp-bridge/dist/index.js (from both the root and the packages/cli positions), plus zero occurrences of createScheduledTaskWithExistingSession / qwen/control/scheduled-task/create-current / session_source_ineligible / ExistingSessionScheduledTaskCreateError / onCreateCurrentSessionScheduledTask / scheduled_task_session_reuse in the base dists, and zero sessionMode in the base cron-create tool and cli serve surfaces (the string appears elsewhere in base for the unrelated session-model feature — 7 files — so the check is scoped to the PR's surface; sessionOwnedByTask pre-exists from #9361 — the field is reused; only the PR's new writers are absent). These dist checks were corroborated at base source level via git grep HEAD^1 after the worktree was removed. Raw logs in logs/.

Public REST path — witness 01-rest-ab-head-all-guards.png / 02-rest-ab-base-flip-cells.png:

cell base 48645c5 head cf63c6e
R1 idle session binds (sessionOwnedByTask:false on disk) 201 ✓ 201 ✓ (A/A equal)
R2 busy session 409 session_busy 409 (A/A equal)
R3 idle + pending interaction 201 — binds (pre-existing gap) 409 session_busy ← flip
R4 parented session 201 — binds 409 session_source_ineligible ← flip
R5 sourced session (sourceId) 201 — binds 409 session_source_ineligible ← flip
R6 sourceType:'scheduled_task' 409 session_already_bound 409 (A/A equal)
R7 no sessionId → dedicated session minted, task-owned 201 ✓ (spawnOrAttach called; sessionOwnedByTask absent = owned) 201 ✓ (A/A equal)
R8 unknown UUID 404 session_not_found 404 (A/A equal)
R9 cross-workspace 400 session_workspace_mismatch 400 (A/A equal)
R10 second bind of same session 409 session_already_bound, store keeps 1 409, store keeps 1 (A/A equal)

Head 28/28, base 25/25 (base asserts the binding cells R3–R5 as the expected pre-existing gap). The seven A/A cells validate the control on the new base; the three flips are the behavioral delta and re-prove Finding 1.

Private control path (central claim) — witness 03-private-path-head-35-assertions.png / 04-private-path-base-absent.png:

cell head base
P0 wire method constant qwen/control/scheduled-task/create-current constant absent
P0b shared creator export present absent
P1 exact prompt, active caller binds; persisted sessionId=caller, sessionOwnedByTask:false n/a — method absent
P2 forged/sibling sessionId -32602 callerSessionId … owned by this connection, nothing persisted -32601, nothing persisted
P3 wrong promptId -32602 does not own the active prompt, nothing persisted -32601
P4 Goal-turn shape (promptActive:false) -32602 fail-closed, nothing persisted -32601
P5 prompt ends between guard and write-lock commit -32602 no longer owns the active prompt, no file written -32601
P14 session-entry object replaced mid-create -32602 no longer owns the active prompt (identity, not value, comparison), no file written n/a
P6 pending interaction on caller -32602, data.errorKind: session_busy, nothing persisted -32601
P7 parented caller session -32602 source cannot own a scheduled task -32601
P8 sourced caller session -32602 source cannot own a scheduled task -32601
P9 cron >200 chars -32602 length guard -32601
P10 recurring:'yes' -32602 type guard -32601
P11 handler absent (injected-bridge model) -32601 method not found -32601
P12 host returns {id:''} -32603 invalid result, nothing persisted n/a
P13 second bind of same session -32602 errorKind: session_already_bound, store keeps 1 task n/a

Head 35/35, base 20/20 (base runs the same 9 parameter shapes through the same real dispatch and asserts method-not-found plus an empty store for each). P5 is driven through the real production closure: a natural bind makes exactly 3 resolveEntry calls — measured with dbg-resolve.mjs: bridge pre-guard, creator pre-lock recheck, creator in-lock recheck — and the harness flips promptActive on the third, so the pre-lock guard sees a valid entry while the in-lock guard sees the ended prompt; the failure message and the empty store are the witness. P14 flips object identity instead of value on the same call.

Mutation matrix (head cf63c6e on merged tree, restored after each run; git status clean at the end)

Witness 05-mutation-matrix-all-killed.png; per-mutant vitest logs in logs/M*.log, driver summary in logs/mutation-matrix.txt (the driver's live classifier missed ANSI-coded FAIL lines; every row below is the ANSI-stripped log content, which is what the witness prints).

mutant guard removed suite result killed by
M1 bridge exact-prompt-match bridgeClient 1 failed rejects a forged session or prompt identity
M2 bridge connection-ownership bridgeClient 1 failed rejects a forged session or prompt identity
M3 bridge source-eligibility bridgeClient 7 failed it.each ineligible-source matrix (all 7 shapes)
M4 route pending-interaction routes 2 failed rejects a pending interaction…; keeps pending interactions ineligible on the trusted cron-tool path
M5 route in-lock identity recheck routes 2 failed rechecks the exact caller prompt inside the task-file lock; fails cleanly when the session disappears before commit
M6 route source-eligibility routes 1 failed rejects a pending interaction and ineligible session sources
M8 tool fail-closed w/o creator+promptId cron-create 1 failed rejects current-session mode without an active daemon prompt
M9 tool durable-required (positive control) cron-create 1 failed rejects current-session mode for a session-only job
M10 capability toggle forced on server 3 failed advertises current-protocol features separately from the registry; honors every entry in CONDITIONAL_SERVE_FEATURES (drift insurance); GET /capabilities returns the v1 envelope
M11 Session errorKind: hint message shape Session 1 failed surfaces structured daemon rejections to the current-session tool
M12 Session -32601 → unavailable mapping Session 1 failed reports an explicit unavailable error for an older daemon bridge
M13 Session trusted daemon promptId precedence Session 1 failed uses the trusted daemon prompt id for a current-session task

No survivors, no combination row needed (M3/M6 pin the source-eligibility predicate independently at both layers; M11–M13 are single-clause guards with no sibling hunk). Every red is the intended behavioral assertion — e.g. M1 fails with "promise resolved { id: 'cron-1', cron: '5 9 * * *' } instead of rejecting", i.e. the forged prompt bound, exactly the hazard the guard closes; positive control M9 proves the cron-create runner can go red. One delta versus round 3: M10's kill set moved from the two capability tests round 3 named to the registry drift-insurance trio — the base-side server.test.ts motion (+130 lines) restructured that coverage; the test round 3 quoted (advertises current-session scheduling only with managed task sessions) now passes under M10 because it drives explicit boolean toggles that !== false does not distort, while the drift-insurance tests exercise the undefined toggle the mutant actually changes. The mutant is still dead; only its killers moved.

Targeted gates (clean merged tree, unmutated)

Witness 06-gates-and-totals.png; logs logs/gate-*.log. Gate liveness is proven by the matrix itself: the same suites went red on 12/12 point mutants (M9 positive control included), so each green below is a measurement, not an empty match.

suite round 4 round 3 note
core cron-create.test.ts 17/17 17/17 unchanged
acp-bridge bridgeClient.test.ts 126/126 126/126 unchanged
cli routes/scheduled-tasks.test.ts 105/105 105/105 unchanged
cli Session.test.ts 696/696 696/696 unchanged
cli server.test.ts 1080/1080 1079 passed + 1 skipped the skip is now a live test (base-side server.test.ts motion)
cli run-qwen-serve.test.ts 327/327 266/266 +61 base-side tests (2,825-line overlap file)
web-shell ScheduledTasksDialog.test.tsx + App.test.tsx 594/594 595/595 −1: base-side #9993 rewrite of App.test.tsx removed a test; the PR's ScheduledTasksDialog.test.tsx is byte-identical to the PR head

The count movements are all base-side test churn on the overlap test files: the suites with no base overlap (cron-create, bridgeClient, routes) held their round-3 counts exactly, Session.test.ts held its count despite overlap, and the three movers (server, run-qwen-serve, web-shell App) are exactly the overlap files whose base-side content changed (+130 / +2,825 / +165 lines).

Corrections

None required to the PR description beyond the standing Finding 1 wording note.

Findings

  1. (Informational, carried over — stands) The REST eligibility tightening is broader than the description states ("the public REST path retains its idle-session rule"; "Breaking changes … none"). Repro (after rebuilding the base worktree per Methodology): TREE=tmp/base-tree ARM=base node tmp/pr9838-verify-20260826-024608/h1-rest.mjs vs TREE=. ARM=head … (cells R3–R5; harness + captured logs in this artifact dir). Re-measured at the new base: on 48645c56, POST /scheduled-tasks with a sessionId accepted sessions carrying a pending interaction, a parentSessionId, or a sourceId (cells R3–R5: 201, task persisted); head rejects them 409. This matches the eligibility set the Reviewer Test Plan step 2 names for the form, so it is evidently intended and closes a real gap — but it is a behavior change for direct REST consumers beyond the idle rule, and the description should say so. Non-blocking; unchanged since round 1.

No new findings this round.

Not covered

  • Per-commit attribution of the PR (10 commits in the snapshot; shallow checkout — git rev-list HEAD^1..HEAD^2 sees only cf63c6e) and per-commit attribution of the base delta (old base ef18a738 unreachable from this container's object store; the 2,204-file HEAD^2..HEAD tree diff is the measured aggregate). All evidence is for the aggregate HEAD^1..HEAD diff.
  • Wall-clock E2E (Test Plan step 4): session-switch persistence, daemon-restart rehydration, delete-keeps-caller-conversation — not driven; pre-existing feat(scheduled-tasks): allow creating a task with an existing session #9361 lifecycle behavior the PR reuses. The harnesses reproduce the wire shape of the feature end to end (real routes, real BridgeClient dispatch, real file store), not the model-driven trigger (no API credentials in this container).
  • PATCH rebinding, multiple tasks per session, task-history migration, non-ordinary sessions, the fix(serve): serialize scheduled-task session teardown with binding via archive-coordinator lease #9415 teardown-vs-reuse race, Windows/Linux runs — author-declared out of scope; not probed.
  • Web Shell rendered UI — component tests only (594/594); no browser run.
  • Repo-wide gates (npm run lint, full typecheck, full suites, integration lane including the PR's qwen-serve-routes.test.ts adjustment) — covered by the PR's own CI; this round ran the 7 targeted suites only.
  • The bridge.ts factory pass-through (opts.onCreateCurrentSessionScheduledTask into the BridgeClient's 24th constructor slot) was not separately mutated; it is exercised by the run-qwen-serve wiring assertions (327/327) and mirrored position-by-position in the private-path harness.

Methodology

Environment: CI verify container (node:22-bookworm, node v22.23.2), merge-ref checkout at depth 2 (HEAD merge 3efe821b, HEAD^1 base tip 48645c56, HEAD^2 PR head cf63c6e). Nothing was carried forward from round 3 by shortcut: the base tip moved 2,204 files, 17 PR files overlap base-side changes, and the lockfile moved, so harnesses h1-rest.mjs / h2-private.mjs were rewritten from the published contract and re-run on both arms, and the matrix and gates were re-run fresh on the merged tree. Harnesses import the compiled dist/ of the tree under test and use only the project's own documented seams (structural session-bridge fake mirrored from the route test rig, entry-map rig, positional BridgeClient constructor mirrored verbatim from the package's own makeClient helper); everything else — express loopback HTTP, BridgeClient dispatch, cron-task file store with its cross-process write lock — is production code. Base control: scratch worktree at HEAD^1 with the root node_modules mirrored entry-by-entry preserving relative symlink targets (which re-points every @qwen-code/* link into the base tree — realpath-asserted from inside the base tree), nested per-package node_modules mirrored, scripts/generate-git-commit-info.js run for the generated files, then coreacp-bridgecli rebuilt there (logs logs/base-build-*.log); symbol absence asserted in the base dists; worktree removed after the A/B cells were captured (reproduce by re-running the worktree + mirror + build steps above with TREE=<base-tree>). Two harness-oracle fixes were made during bring-up and affected both arms identically: R8 must throw the real SessionNotFoundError class (a name-only fake yields 500 on both arms), and R7's task-owned oracle is sessionOwnedByTask !== false because the mint path leaves the field absent (the delete path reads !== false). dbg-resolve.mjs measured the 3-call resolveEntry sequence a natural bind makes, which is what P5/P14's call-indexed flips are gated on. Mutation matrix applied in-place by mutant-runner.mjs (apply requires the anchor to occur exactly once — pre-verified for all 12; each file restored byte-for-byte after its run and verified, git status --porcelain empty at the end); the runner's live classifier failed to parse ANSI-coded vitest output, so the matrix rows were reclassified from the raw per-mutant logs with ANSI stripped — the substance (which tests went red) is identical either way. Assertion counts: 108 (A/B: 28 REST head + 25 REST base + 35 private head + 20 private base) + 12 (mutants) + 7 (gate suites) = 127 pass, 0 fail.

Flakiness gate log

integration test, out of gate scope: integration-tests/cli/qwen-serve-routes.test.ts
rounds=5 files=8 skipped=1
file packages/acp-bridge/src/bridgeClient.test.ts: (cd packages/acp-bridge) npx --no-install vitest run ./src/bridgeClient.test.ts
file packages/cli/src/acp-integration/session/Session.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/session/Session.test.ts
file packages/cli/src/serve/routes/scheduled-tasks.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/scheduled-tasks.test.ts
file packages/cli/src/serve/run-qwen-serve.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/run-qwen-serve.test.ts
file packages/cli/src/serve/server.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server.test.ts
file packages/core/src/tools/cron-create.test.ts: (cd packages/core) npx --no-install vitest run ./src/tools/cron-create.test.ts
file packages/web-shell/client/App.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/App.test.tsx
file packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/dialogs/ScheduledTasksDialog.test.tsx


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/bridgeClient.test.ts: PPP
  packages/cli/src/acp-integration/session/Session.test.ts: PPP
  packages/cli/src/serve/routes/scheduled-tasks.test.ts: PPP
  packages/cli/src/serve/run-qwen-serve.test.ts: PPP
  packages/cli/src/serve/server.test.ts: PPP
  packages/core/src/tools/cron-create.test.ts: PPP
  packages/web-shell/client/App.test.tsx: PPP
  packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: PP

verdict: timeout
summary: only 2 of 5 rounds fit the 15-minute budget; the completed rounds agreed

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/acp-bridge/src/bridgeClient.test.ts: P (exit 0)
round 1 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/scheduled-tasks.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/run-qwen-serve.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/core/src/tools/cron-create.test.ts: P (exit 0)
round 1 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: P (exit 0)
round 2 · packages/acp-bridge/src/bridgeClient.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/routes/scheduled-tasks.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/run-qwen-serve.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 2 · packages/core/src/tools/cron-create.test.ts: P (exit 0)
round 2 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: P (exit 0)
round 3 · packages/acp-bridge/src/bridgeClient.test.ts: P (exit 0)
round 3 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/routes/scheduled-tasks.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/run-qwen-serve.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 3 · packages/core/src/tools/cron-create.test.ts: P (exit 0)
round 3 · packages/web-shell/client/App.test.tsx: P (exit 0)

Evidence images

01-rest-ab-head-all-guards

02-rest-ab-base-flip-cells

03-private-path-head-35-assertions

04-private-path-base-absent

05-mutation-matrix-all-killed

06-gates-and-totals

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

The bot already has a review of its own on cf63c6e82e1144d17252e0d3a606fd99f65d87a1, which still stands.

机器人在 cf63c6e82e1144d17252e0d3a606fd99f65d87a1 上已有自己的评审,且仍然有效。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@doudouOUC doudouOUC self-assigned this Aug 26, 2026
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 56 passed · 0 failed · 56 total

Flakiness gate: ⚠️ timeout — only 2 of 5 rounds fit the 15-minute budget; the completed rounds agreed

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:56 通过 · 0 失败 · 56 总计

抖动门:⚠️ timeout — only 2 of 5 rounds fit the 15-minute budget; the completed rounds agreed

Verification report

PR #9838 — feat(daemon): Support current-session scheduled tasks (round 5)

Verdict: merge-ready — 56/56 scripted assertions passed this round (0 unexpected failures), verified head cf63c6e82e1144d17252e0d3a606fd99f65d87a1 (merge-ref HEAD^2; base tip 48645c56f8ed3b7e0e2838d68685de1f753ad1a9; merge commit 3efe821bf1bd6e709eb15c957af4ee676ab02f83).

This is a follow-up round in which nothing moved: the checkout is the same merge commit round 4 verified (3efe821b…), with the same base tip (48645c56) and the same PR head (cf63c6e). Round 4 re-measured everything at exactly this head/base pair after the base moved under an unchanged head; this round therefore applied the proven-identical-input-closure shortcut to carry round 4's measurements, and re-executed a live verification subset on this round's own CI build as fresh evidence (see What this round re-executed). No new findings.

中文 — 判定:✅ 通过 · 可合入(agent 判定)
  • 结论merge-ready。本轮 56/56 脚本断言全部通过,0 个意外失败。
  • 本轮与上轮输入完全相同:合并提交 3efe821b、base 48645c56、PR head cf63c6e 均与第 4 轮逐字节一致(提交 OID 相同 ⇒ 树内容相同)。第 4 轮在该 head/base 组合上的全部测量按"输入闭包相同"规则继承;本轮另在本容器 CI 构建上重跑了一个活的验证子集作为新证据(见表 What this round re-executed 与见证图 01–04)。
  • 上轮发现复核:唯一发现(信息级:REST 资格收紧范围超出描述中"retains its idle-session rule"的措辞)状态不变(stands)——本轮 head 侧实测翻转单元 R3/R4/R5 仍为 409 拒绝且不落盘(见证图 02-rest-path-head-flip-cells.png);base 侧 201 绑定行为由第 4 轮在同一 base 48645c56 上的测量继承。快照中的 PR 描述措辞本轮复核仍未变。非阻塞。见 "Previous-finding status"。
  • 未覆盖:见下 "Not covered"。

Previous-finding status (round 4 → round 5)

# Finding (round 4, carried from round 1) Severity Status at round 5
1 REST eligibility tightening is broader than the description's "the public REST path retains its idle-session rule": on base, POST /scheduled-tasks with a sessionId bound sessions carrying a pending interaction, a parentSessionId, or a sourceId (201); head rejects them 409 Informational Stands — inputs are bit-identical to the base 48645c56 round 4 measured this against, and the head side was re-driven live this round: cells R3/R4/R5 reject 409 (session_busy / session_source_ineligible / session_source_ineligible) with nothing persisted (witness 02-rest-path-head-flip-cells.png, harness h1-rest.mjs). The snapshot's PR body still says "retains its idle-session rule" and "Breaking changes / migration notes: none" (re-read from $QWEN_VERIFY_CONTEXT this round). The tightening matches the Reviewer Test Plan's own eligibility list, so it is evidently intended; the wording remains the only gap. Non-blocking.

Closure identity proof (why round 4's measurements carry)

Everything round 4 consumed is shown unchanged — not one file hash, the whole closure:

input round 4 round 5 (measured)
merge commit 3efe821b… 3efe821bf1bd6e709eb15c957af4ee676ab02f83same OID (a commit OID pins tree + parents, so the full tree content is identical)
base tip (HEAD^1) 48645c56… 48645c56f8ed3b7e0e2838d68685de1f753ad1a9 — same
PR head (HEAD^2) cf63c6e… cf63c6e82e1144d17252e0d3a606fd99f65d87a1 — same
working tree clean git status --porcelain = 0 lines
effective diff 27 files, +2132/−192 27 files, +2132/−192 — same stat
merge integrity 17 PR files overlap base-side motion; the other 10 are byte-identical between HEAD^2 and HEAD re-derived here: 17 overlap / 10 identical — same sets
lockfile untouched by the effective diff; base-side churn only git diff HEAD^1..HEAD -- package.json package-lock.json = 0 lines — same
PR symbols at HEAD all present, named multiplicities re-grepped: onCreateCurrentSessionScheduledTask ×3 in bridgeClient.ts, wire constant in status.ts, sessionMode ×6 in cron-create.ts, ExistingSessionScheduledTaskCreateError ×18 in routes, etc. — all present
base-arm control validity base dists rebuilt at 48645c56, zero PR symbols base source census at HEAD^1 re-run here: 0 matches for all 6 PR symbols; no sessionMode in the base cron-create tool

One discrepancy, accounted for: the sha256 of the serialized git diff HEAD^1..HEAD output in this container is 9673da00… where round 4 quoted 17d47434…. The serialization is byte-stable within this container (two runs, same hash), and every content-level census above matches round 4's record exactly, so this is a diff-output serialization artifact of the other container, not content drift — and the identical merge-commit OID makes the content question moot anyway.

Because the base commit, the lockfile, and the tree are all unchanged, round 4's base-arm rebuild and its base cells (private path: method absent -32601, nothing persisted; REST: R3–R5 bind 201) carry under the same closure rule. Rebuilding the base worktree again this round would have repeated round 4's work byte-for-byte; per the skill's budget rule ("once the central claim is proven and the report is written, ship — there is no credit for using the clock"), it was carried instead. Reproduce by following round 4's Methodology worktree + mirror + build steps.

What this round re-executed (fresh measurements, all on this round's CI build)

measurement result witness
h2-private.mjs — central claim, compact dist-driven probe (real BridgeClient dispatch → real createScheduledTaskWithExistingSession → real durable task file store): exact-prompt bind (sessionOwnedByTask:false, exactly 3 resolveEntry calls), forged sessionId, wrong promptId, pending interaction, parented caller, prompt-ended-mid-create (in-lock recheck), double bind 23/23 (run twice: plain + under capture) 01-private-path-head-compact-probe.png
h1-rest.mjs — public REST path, real routes on loopback HTTP: idle binds 201; busy / pending / parented / sourced reject 409 with the right codes and nothing persisted; unknown → 404 17/17 (run twice) 02-rest-path-head-flip-cells.png
Gate liveness mutant M1 (bridge exact-prompt-match clause removed): suite must go red on the intended behavioral assertion killed — 1 failed | 125 passed, exit 1; failure: rejects a forged session or prompt identitypromise resolved "{ id: 'cron-1', cron: '5 9 * * *' }" instead of rejecting (a forged prompt bound — the exact hazard the guard closes). Restored byte-for-byte afterwards (sha256 2f09e75d… identical; git status clean); post-restore suite green 126/126 03-mutant-m1-bridge-prompt-guard-red.png
7 targeted gate suites (unmutated merged tree) cron-create 17/17 · bridgeClient 126/126 · routes 105/105 · Session 696/696 · run-qwen-serve 327/327 · server 1080/1080 · web-shell 594/594 — all exit 0, counts identical to round 4 04-gates-and-mutant-summary.png, logs logs/gate-*.log
Base-symbol absence census at HEAD^1 (control validity) 7/7 zero-match checks as expected logs/ + Methodology

Scope selection

  • Central claim (unchanged since round 1): cron_create with sessionMode: 'current' + durable: true binds the durable task to the session whose active turn executes the tool, via a daemon-only ACP control request (qwen/control/scheduled-task/create-current) that verifies connection-ownership and exact-prompt match, persisting sessionOwnedByTask: false; every identity mismatch fails closed with no unbound fallback.
  • Secondary 1: the shared creator (createScheduledTaskWithExistingSession) keeps the REST idle rule, tightens eligibility (pending interaction / parented / sourced sessions), and re-checks identity inside the task-file write lock.
  • Secondary 2: scheduled_task_session_reuse is advertised only when the daemon manages scheduled-task sessions with a daemon-created (non-injected) bridge; the Web Shell form defaults to Dedicated and sends sessionId only after explicit selection. (Component-level only this round, via the web-shell gate suite.)

Findings

  1. (Informational, carried over — stands) As in the status table above: the REST eligibility tightening is broader than the description states. Repro (head side, this round): node tmp/pr9838-verify-20260826-074531/h1-rest.mjs — cells R3–R5 reject 409 with nothing persisted; base-side 201-bind cells carry from round 4's measurement at this same base. Non-blocking; unchanged since round 1.

No new findings this round.

Not covered

  • Base-arm re-execution this round — carried from round 4 under the closure identity proven above (same base commit, same lockfile, symbol absence re-verified at source level). Not re-driven live.
  • Per-commit attribution of the PR (10 commits in the snapshot) and of any base delta — shallow checkout (git rev-parse --is-shallow-repository = true; git rev-list HEAD^1..HEAD^2 sees only cf63c6e); unchanged since round 4 and moot this round because nothing moved. All evidence is for the aggregate HEAD^1..HEAD diff.
  • Wall-clock E2E (Test Plan step 4): session-switch persistence, daemon-restart rehydration, delete-keeps-caller-conversation — not driven; pre-existing feat(scheduled-tasks): allow creating a task with an existing session #9361 lifecycle behavior the PR reuses. The harnesses reproduce the wire shape of the feature end to end (real routes, real BridgeClient dispatch, real file store), not the model-driven trigger (no API credentials in this container).
  • PATCH rebinding, multiple tasks per session, task-history migration, non-ordinary sessions, the fix(serve): serialize scheduled-task session teardown with binding via archive-coordinator lease #9415 teardown-vs-reuse race, Windows/Linux runs — author-declared out of scope; not probed.
  • Web Shell rendered UI — component tests only (594/594); no browser run.
  • Repo-wide gates (npm run lint, full typecheck, full suites, integration lane including the PR's qwen-serve-routes.test.ts adjustment) — covered by the PR's own CI; this round ran the 7 targeted suites only.
  • The bridge.ts factory pass-through (opts.onCreateCurrentSessionScheduledTask into the BridgeClient's 24th constructor slot) was not separately mutated; it is exercised by the run-qwen-serve wiring assertions (327/327) and mirrored position-by-position in h2-private.mjs. The remaining 11 mutants of round 4's 12/12-killed matrix carry under the closure proof.
  • The mutation matrix beyond M1 — round 4's full 12-mutant matrix carries; this round re-killed M1 live as the gate-liveness proof (the rule requires a live liveness demonstration for the gates cited this round; M1 is the most load-bearing guard of the central claim).
  • Flakiness gate — owned by the workflow, not this agent.

Methodology

Environment: CI verify container (node:22-bookworm, node v22.23.2), merge-ref checkout at depth 2 (HEAD merge 3efe821b, HEAD^1 base tip 48645c56, HEAD^2 PR head cf63c6e), npm ci + npm run build pre-run by the workflow. Round 4's measurements were carried under the proven-identical input closure (table above); this round then re-executed a live subset on the fresh CI build. Harnesses h1-rest.mjs / h2-private.mjs (in this artifact dir) were rewritten from the published round-4 contract and import the compiled dist/ of the current tree: h2 constructs the real BridgeClient via the 24-slot positional constructor mirrored verbatim from bridgeClient.test.ts's makeClient, wires its handler to the real createScheduledTaskWithExistingSession mirroring run-qwen-serve's createCurrentSessionScheduledTaskHandler, and dispatches through the real client.extMethod; h1 boots the real registerScheduledTasksRoutes on loopback HTTP behind app.listen(0) and drives it with fetch over a real socket. The session bridge is the route module's documented structural seam (mirrored from the package's own test rig); everything else — dispatch, eligibility guards, write-lock recheck, durable task file store with its cross-process lock — is production code; the P5 cell flips promptActive on the third resolveEntry call (the in-lock recheck), matching the 3-call sequence asserted by P1. The mutant applied in-place to packages/acp-bridge/src/bridgeClient.ts (anchor entry.activePromptId !== promptId clause of the pre-guard), ran the suite red, and was restored byte-for-byte (sha256-verified before and after; git status --porcelain empty; post-restore suite green). Base-arm validity re-proven by source census at HEAD^1 (0 matches for all 6 PR symbols, no sessionMode in the base cron-create tool). Gates ran via (cd packages/<ws> && npx --no-install vitest run <files>); raw per-suite logs in logs/gate-*.log, probe logs in logs/h*-run1.log and logs/M1-mutant.log. Assertion counts: 23 (h2) + 17 (h1) + 7 (gates) + 1 (M1 expected-red kill) + 1 (mutant restore byte-identity) + 7 (base-symbol census) = 56 pass, 0 fail.

Flakiness gate log

integration test, out of gate scope: integration-tests/cli/qwen-serve-routes.test.ts
rounds=5 files=8 skipped=1
file packages/acp-bridge/src/bridgeClient.test.ts: (cd packages/acp-bridge) npx --no-install vitest run ./src/bridgeClient.test.ts
file packages/cli/src/acp-integration/session/Session.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/session/Session.test.ts
file packages/cli/src/serve/routes/scheduled-tasks.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/scheduled-tasks.test.ts
file packages/cli/src/serve/run-qwen-serve.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/run-qwen-serve.test.ts
file packages/cli/src/serve/server.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server.test.ts
file packages/core/src/tools/cron-create.test.ts: (cd packages/core) npx --no-install vitest run ./src/tools/cron-create.test.ts
file packages/web-shell/client/App.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/App.test.tsx
file packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/dialogs/ScheduledTasksDialog.test.tsx


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/bridgeClient.test.ts: PPP
  packages/cli/src/acp-integration/session/Session.test.ts: PPP
  packages/cli/src/serve/routes/scheduled-tasks.test.ts: PPP
  packages/cli/src/serve/run-qwen-serve.test.ts: PPP
  packages/cli/src/serve/server.test.ts: PPP
  packages/core/src/tools/cron-create.test.ts: PPP
  packages/web-shell/client/App.test.tsx: PP
  packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: PP

verdict: timeout
summary: only 2 of 5 rounds fit the 15-minute budget; the completed rounds agreed

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/acp-bridge/src/bridgeClient.test.ts: P (exit 0)
round 1 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/scheduled-tasks.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/run-qwen-serve.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/core/src/tools/cron-create.test.ts: P (exit 0)
round 1 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: P (exit 0)
round 2 · packages/acp-bridge/src/bridgeClient.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/routes/scheduled-tasks.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/run-qwen-serve.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 2 · packages/core/src/tools/cron-create.test.ts: P (exit 0)
round 2 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/dialogs/ScheduledTasksDialog.test.tsx: P (exit 0)
round 3 · packages/acp-bridge/src/bridgeClient.test.ts: P (exit 0)
round 3 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/routes/scheduled-tasks.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/run-qwen-serve.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 3 · packages/core/src/tools/cron-create.test.ts: P (exit 0)

Evidence images

01-private-path-head-compact-probe

02-rest-path-head-flip-cells

03-mutant-m1-bridge-prompt-guard-red

04-gates-and-mutant-summary

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

The bot already has a review of its own on cf63c6e82e1144d17252e0d3a606fd99f65d87a1, which still stands.

机器人在 cf63c6e82e1144d17252e0d3a606fd99f65d87a1 上已有自己的评审,且仍然有效。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@wenshao

wenshao commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Round-2 local real-daemon verification of #9838 — blocker fixed, one diagnosability gap left

I rebuilt the verification environment against the current head cf63c6e82e1144d17252e0d3a606fd99f65d87a1 and re-ran the whole thing end to end on a live qwen serve daemon (real HTTP, real spawned ACP children, real wall-clock cron firing, real Web Shell in Chromium, real daemon restart, real multi-workspace daemon).

Verdict: the round-1 blocker is genuinely fixed — LGTM to merge. cron_create(sessionMode: 'current') now succeeds on a real daemon (4/4), the full lifecycle of a tool-created binding works (9/9), and all three runtime install sites — primary, startup-secondary and dynamically attached — bind for real (6/6). One non-blocking follow-up remains: half of the rejection paths still reach the user as a bare Invalid params, and it is the same seam that hid the round-1 blocker.

Environment

Worktree /root/git/pr9838 at cf63c6e82e, full npm run build, no source modifications during the scenarios
Daemon node packages/cli/dist/index.js serve --port 4371 --workspace <ws> [--workspace <ws2>] [--token …]
Model local OpenAI-compatible mock (marker-driven: tool-call / text / delayed reply / two parallel tool calls)
UI the daemon's own Web Shell build in headless Chromium (Playwright), deviceScaleFactor: 2
Wire QWEN_CLI_ENTRY stdio tap that tees every JSON-RPC frame between the daemon and its ACP children
A/B the previously verified head 8ca008d370 kept as a second built dist and swapped in

1. The blocker is fixed — real two-build A/B

Arm Wire promptId sent by cron_create Result
8ca008d370 (round-1 head, dist swapped back in) d74fb300-…-3235373a659e########1 — the core turn id TOOL failed, Error creating cron job: [object Object], 0 persisted (0/2)
cf63c6e82e (this head) e7d91d82-6a34-47a4-9ec5-dc4e943b8486 — the daemon prompt id, identical to _meta["qwen-code/invocation"].promptId on session/prompt TOOL completed, persisted sessionId = caller conversation, sessionOwnedByTask: false (4/4)

tool path fixed

The identity check was not weakened to get there. I patched the built child to send a forged promptId and left everything else at head: the daemon rejected it and nothing was persisted. The adapter changed which id is sent, not whether it is checked.

2. Lifecycle of a tool-created binding — 9/9

This is coverage that was impossible before this head, because the tool entrypoint never produced a binding. Real clock, * * * * *, observed over ~7 minutes across a restart.

# Check Result
L1 The tool binds the caller conversation sessionOwnedByTask: false, sessionId = caller
L2/L3 The user switches to another conversation; runs still land in the bound one ✅ 2/2 runs, every runs[].sessionId = caller
L4 The bound conversation carries the scheduled turns ✅ 3 occurrences of the scheduled prompt in its transcript
L5 The conversation the user switched to is untouched ✅ no scheduled prompt in it
L6 The binding survives a daemon restart ✅ rehydrated with the same sessionId
L7 It fires again into the same conversation after the restart ✅ runs 2 → 3, last run = caller
L8 The caller conversation is still alive after the restart 200
L9 Deleting the task keeps the caller conversation open and usable 200 + a new turn completes

bound task run history

3. Runtime wiring — all three install sites bind for real (6/6)

Reviewer test-plan step 5 was the largest untested claim; I drove it on a two-workspace daemon and then attached a third at runtime through POST /workspaces.

# Check Result
W0 Capability advertised on a multi-workspace daemon scheduled_task_session_reuse
W1 Primary workspace runtime binds ✅ caller-owned
W2 Startup-secondary workspace runtime binds ✅ caller-owned
W3 Dynamically attached workspace runtime binds (attached at runtime with POST /workspaces, 201) ✅ caller-owned
W4 Cross-workspace REST bind is refused 400 session_workspace_mismatch
W5 Each binding is persisted only in its own workspace's cron file ✅ ws/ws2/ws3 isolated

4. Web Shell entrypoint — no regression

# Check Result
U1 Capability advertised
U2 No conversation open → option disabled "Open an existing conversation before selecting the current conversation."
U3 Defaults to the dedicated task conversation
U4 Idle conversation → option selectable "Future runs continue in the conversation that is open now."
U5 Already-bound conversation → option disabled "The current conversation is already bound to a scheduled task."
U6 Default arm omits sessionId {"cron":"0 9 * * *","prompt":"PING_UI_DEDICATED",…}
U7 Explicit arm sends the open conversation id …,"sessionId":"7adeb182-14a3-4f67-b99b-748de845c260"
U8 Ownership semantics, proved by deletion ✅ caller-owned conversation survives (200, still usable); the dedicated one is torn down (404)

form with current conversation

5. Local fail-closed rules

Check Result
sessionMode: 'current' without durable: true "Current-session scheduling requires durable: true because session-only jobs cannot survive a daemon session switch."
The same tool call outside a daemon (qwen -p …) "current_session_scheduling_unavailable: Current-session scheduling requires an active daemon prompt." — no unbound fallback
Caller already bound to a task "session_already_bound: The requested session is already bound to another scheduled task", nothing persisted

Follow-up (non-blocking) — half of the rejections still say only Invalid params

The [object Object] fix works for the daemon route's domain errors: those now carry data.errorKind/status/hint, and session_already_bound reaches the model as a full sentence. But the bridge's own guards — the ones that fire on an identity or eligibility mismatch — still reach the user with their message stripped.

Live repro, no instrumentation, a scenario a user can actually hit: a scheduled run asks to keep its own future runs in the conversation it is running in. The caller is a task-owned conversation, so the source guard correctly refuses — and this is all the user and the model get:

Error creating cron job: Invalid params

opaque rejection

The wire tap for the same guard — driven from a scheduled_task-sourced conversation so the frames are easy to isolate — shows the daemon really does send nothing more:

OUT {"jsonrpc":"2.0","id":0,"method":"qwen/control/scheduled-task/create-current",
     "params":{"callerSessionId":"a23482c7-…","promptId":"e7d91d82-…","cron":"*/13 * * * *","prompt":"R4_SOURCED","recurring":true}}
IN  {"jsonrpc":"2.0","id":0,"error":{"code":-32602,"message":"Invalid params"}}

Root cause. withLogSafeAcpError (packages/acp-bridge/src/bridge.ts:531) replaces every outbound RequestError message with logSafeRequestErrorMessage(code) and drops any data that has no errorKind. preserveScheduledTaskCreateErrorOverAcp was added precisely to survive that filter — but only for ExistingSessionScheduledTaskCreateError. The eight RequestError.invalidParams(undefined, …) guards in handleCreateCurrentSessionScheduledTask pass no data, so all of them collapse to the same three words:

  • `callerSessionId` must name a session owned by this connection
  • `promptId` must be a non-empty string, `cron` …, `prompt` …, `recurring` …
  • The caller session does not own the active promptthe exact failure of round 1
  • The caller session no longer owns the active prompt
  • The caller session source cannot own a scheduled task

I confirmed the prompt-mismatch case too, by forging the id: identical Invalid params. So if this regressed again, the field signal would be no better than it was in round 1.

Suggested fix (small): give those guards the same treatment as the domain errors, e.g. RequestError.invalidParams({ errorKind: 'caller_prompt_mismatch', hint: '…' }, '…')logSafeRequestErrorData already passes errorKind/hint through, and Session.ts already renders errorKind: hint.

Test-realism note: cron-create.test.ts asserts on { code: -32602, message: 'The caller session does not own the active prompt' }. That message never reaches the child for that case — the wire value is Invalid params. The assertion is right about getErrorMessage, but it reads as if the guard text were user-visible; it is not.

For contrast, the REST/Web Shell surface does this well — the same class of rejection is rendered verbatim in the form:

busy guard message

The deferred UI-staleness item: deferral is reasonable

Still reproduces at head (open the form → leave → another client starts a turn → reopen: the option is still offered), and it is fail-closed with a good message: POST /scheduled-tasks returns 409 session_busy, the form renders the server's sentence, and nothing is persisted. A fresh load while busy correctly disables the option. Nothing here needs to block the merge.

Test teeth — 6 mutants on the three fix hunks

Mutation Suite Killed
getInvocationContext()?.promptId ?? req.promptIdreq.promptId Session
Drop the errorKind mapping in Session.ts Session
getErrorMessage(error) → the old ternary cron-create
Drop .catch(preserveScheduledTaskCreateErrorOverAcp) bridgeClient
Drop the ?? req.promptId fallback Session ⚠️ survives — untested, and inert (a missing context sends undefined, which the bridge rejects anyway)
err.status >= 500 ? -32603 : -32602 → always -32602 bridgeClient ⚠️ survives — untested, and inert (Session.ts only branches on -32601)

Suites at this head, run locally: bridgeClient 126/126, cron-create 17/17, scheduled-task routes 105/105, ScheduledTasksDialog 53/53, Session (targeted) 2/2.

Not covered by this run

The pending-interaction guard on the private path could not be exercised live — the engine serializes approvals, so cron_create never executes while another interaction of the same turn is pending (I drove a parallel write_file + cron_create batch and the tool simply never ran); it stays defense-in-depth. Also uncovered: the injected-bridge capability negative (not reachable from the CLI), PATCH rebinding, multiple tasks per session, the #9415 teardown-versus-reuse race, Windows/macOS.

中文说明

#9838 第二轮本地真实 daemon 验证 —— 阻塞问题已修复,遗留一个可诊断性问题

我针对当前 head cf63c6e82e1144d17252e0d3a606fd99f65d87a1 重新搭建验证环境,并在运行中的 qwen serve daemon 上完整重跑了端到端流程(真实 HTTP、真实 ACP 子进程、真实时钟触发、Chromium 中的真实 Web Shell、真实 daemon 重启、真实多 workspace daemon)。

结论:第一轮的阻塞问题确实已修复,同意合入(LGTM)。 cron_create(sessionMode: 'current') 现在在真实 daemon 上可以成功(4/4),工具创建的绑定其完整生命周期正常(9/9),三个 runtime 安装点(primary、启动时 secondary、动态挂载)都能真实绑定(6/6)。遗留一个不阻塞的后续项:一半的拒绝路径仍然只向用户显示 Invalid params,而这正是当初掩盖第一轮阻塞问题的同一处接缝。

环境

工作树 /root/git/pr9838,head cf63c6e82e,完整 npm run build,场景执行期间未修改源码
Daemon node packages/cli/dist/index.js serve --port 4371 --workspace <ws> [--workspace <ws2>] [--token …]
模型 本地 OpenAI 兼容 mock(marker 驱动:工具调用 / 文本 / 延迟回复 / 两个并行工具调用)
UI daemon 自带的 Web Shell 构建,headless Chromium(Playwright),deviceScaleFactor: 2
抓包 QWEN_CLI_ENTRY stdio tap,记录 daemon 与 ACP 子进程之间的每一帧 JSON-RPC
A/B 把第一轮验证过的 head 8ca008d370 作为第二份真实 dist 保留并换入

1. 阻塞问题已修复 —— 真实的双构建 A/B

分支 cron_create 在线上发送的 promptId 结果
8ca008d370(第一轮 head,换回其 dist) d74fb300-…-3235373a659e########1 —— core 的轮次 id TOOL failedError creating cron job: [object Object],0 条持久化(0/2)
cf63c6e82e(当前 head) e7d91d82-6a34-47a4-9ec5-dc4e943b8486 —— daemon prompt id,与 session/prompt 上的 _meta["qwen-code/invocation"].promptId 完全一致 TOOL completed,持久化 sessionId = 调用方会话,sessionOwnedByTask: false(4/4)

修复并没有削弱身份校验。 我把编译后的子进程改成发送伪造的 promptId,其余保持 head 原样:daemon 拒绝了请求,也没有任何持久化。适配层改的是「发哪个 id」,不是「是否校验」。

2. 工具创建绑定的完整生命周期 —— 9/9

这是本 head 之前无法覆盖的部分,因为工具入口此前根本产生不了绑定。真实时钟、* * * * *,跨重启观察约 7 分钟。

# 检查项 结果
L1 工具绑定调用方会话 sessionOwnedByTask: falsesessionId = 调用方
L2/L3 用户切到另一个会话后,定时执行仍落在绑定会话 ✅ 2/2 次运行,runs[].sessionId 全部等于调用方
L4 绑定会话中确实有定时轮次 ✅ transcript 中出现 3 次定时 prompt
L5 用户切换过去的会话未受影响 ✅ 其中没有定时 prompt
L6 绑定在 daemon 重启后保留 ✅ 重新加载,sessionId 不变
L7 重启后再次触发仍落在同一会话 ✅ 运行次数 2 → 3,最后一次 = 调用方
L8 重启后调用方会话仍存活 200
L9 删除任务后调用方会话仍可用 200 且能完成新一轮对话

3. Runtime 接线 —— 三个安装点都真实绑定(6/6)

Reviewer 测试计划第 5 步是最大的未验证声明;我在双 workspace daemon 上驱动,并通过 POST /workspaces 在运行时挂载了第三个。

# 检查项 结果
W0 多 workspace daemon 广告 capability scheduled_task_session_reuse
W1 primary workspace runtime 可绑定 ✅ caller-owned
W2 启动时 secondary workspace runtime 可绑定 ✅ caller-owned
W3 动态挂载的 workspace runtime 可绑定(运行时通过 POST /workspaces 挂载,201 ✅ caller-owned
W4 跨 workspace 的 REST 绑定被拒绝 400 session_workspace_mismatch
W5 每个绑定只写入各自 workspace 的 cron 文件 ✅ ws/ws2/ws3 相互隔离

4. Web Shell 入口 —— 无回归

# 检查项 结果
U1 广告 capability
U2 没有打开会话时选项禁用 "Open an existing conversation before selecting the current conversation."
U3 默认「独立任务会话」
U4 空闲会话下可选「当前会话」 "Future runs continue in the conversation that is open now."
U5 已绑定会话时选项禁用 "The current conversation is already bound to a scheduled task."
U6 默认分支不发送 sessionId {"cron":"0 9 * * *","prompt":"PING_UI_DEDICATED",…}
U7 显式分支发送打开会话的 id …,"sessionId":"7adeb182-14a3-4f67-b99b-748de845c260"
U8 用删除行为验证所有权语义 ✅ 调用方会话存活(200,仍可用);独立任务会话被销毁(404

5. 本地 fail-closed 规则

检查项 结果
sessionMode: 'current' 但没有 durable: true "Current-session scheduling requires durable: true because session-only jobs cannot survive a daemon session switch."
在 daemon 之外调用同一工具(qwen -p … "current_session_scheduling_unavailable: Current-session scheduling requires an active daemon prompt." —— 不会回退为未绑定任务
调用方已绑定任务 "session_already_bound: The requested session is already bound to another scheduled task",无持久化

后续项(不阻塞)—— 一半的拒绝仍然只显示 Invalid params

[object Object] 的修复对 daemon route 的领域错误 是有效的:它们现在携带 data.errorKind/status/hintsession_already_bound 能以完整句子送达模型。但 bridge 自身的守卫 —— 也就是身份或来源不符时触发的那些 —— 送到用户手里时消息仍然被抹掉。

真实复现,无需插桩,且是用户真的会遇到的场景:一个定时任务的运行请求把后续运行留在它当前所在的会话中。调用方是任务自有会话,来源守卫正确拒绝 —— 而用户和模型看到的全部内容就是:

Error creating cron job: Invalid params

同一守卫的抓包(为便于隔离帧,改用一个 scheduled_task 来源的会话驱动)显示 daemon 确实没有发送更多信息:

OUT {"jsonrpc":"2.0","id":0,"method":"qwen/control/scheduled-task/create-current",
     "params":{"callerSessionId":"a23482c7-…","promptId":"e7d91d82-…","cron":"*/13 * * * *","prompt":"R4_SOURCED","recurring":true}}
IN  {"jsonrpc":"2.0","id":0,"error":{"code":-32602,"message":"Invalid params"}}

根因。 withLogSafeAcpErrorpackages/acp-bridge/src/bridge.ts:531)会把每个向外的 RequestError 消息替换为 logSafeRequestErrorMessage(code),并丢弃所有不含 errorKinddatapreserveScheduledTaskCreateErrorOverAcp 正是为了绕过这层过滤而加的 —— 但只覆盖 ExistingSessionScheduledTaskCreateErrorhandleCreateCurrentSessionScheduledTask 中那 8 个 RequestError.invalidParams(undefined, …) 守卫都没有传 data,因此全部塌缩成同样的两个词:

  • `callerSessionId` must name a session owned by this connection
  • `promptId` must be a non-empty string`cron` …`prompt` …`recurring` …
  • The caller session does not own the active prompt第一轮阻塞问题的确切失败点
  • The caller session no longer owns the active prompt
  • The caller session source cannot own a scheduled task

我也通过伪造 id 验证了 prompt 不匹配这条路径:同样是 Invalid params。也就是说,如果这个问题再次回归,现场信号并不会比第一轮更好。

建议修复(改动很小): 给这些守卫与领域错误相同的待遇,例如 RequestError.invalidParams({ errorKind: 'caller_prompt_mismatch', hint: '…' }, '…') —— logSafeRequestErrorData 本就会透传 errorKind/hintSession.ts 也已经会渲染 errorKind: hint

关于测试真实性: cron-create.test.ts 断言的是 { code: -32602, message: 'The caller session does not own the active prompt' }。这个消息在该场景下永远不会到达子进程 —— 线上值是 Invalid params。断言对 getErrorMessage 的验证是对的,但读起来像是守卫文案对用户可见,实际并不可见。

作为对比,REST / Web Shell 这一侧处理得很好 —— 同类拒绝的原文会原样显示在表单里。

关于被推迟的 UI 陈旧态问题:推迟是合理的

在当前 head 仍可复现(打开表单 → 退出 → 另一个客户端开始一轮对话 → 重新打开:选项仍然可选),并且是 fail-closed 且带有良好提示POST /scheduled-tasks 返回 409 session_busy,表单直接渲染服务端的原文,没有任何持久化。忙碌状态下重新加载页面则正确禁用该选项。这一项不需要阻塞合入。

测试牙齿 —— 对三处修复做了 6 个变异

变异 套件 是否被杀死
getInvocationContext()?.promptId ?? req.promptIdreq.promptId Session
删除 Session.ts 中的 errorKind 映射 Session
getErrorMessage(error) → 旧的三元表达式 cron-create
删除 .catch(preserveScheduledTaskCreateErrorOverAcp) bridgeClient
删除 ?? req.promptId 兜底 Session ⚠️ 存活 —— 无测试覆盖,且无实际影响(无 context 时发送 undefined,bridge 同样会拒绝)
err.status >= 500 ? -32603 : -32602 → 恒为 -32602 bridgeClient ⚠️ 存活 —— 无测试覆盖,且无实际影响(Session.ts 只对 -32601 分支)

本地在该 head 上跑的套件:bridgeClient 126/126、cron-create 17/17、scheduled-task routes 105/105、ScheduledTasksDialog 53/53、Session(定向)2/2。

本轮未覆盖

私有路径上的 pending interaction 守卫无法在真实环境触发 —— 引擎会串行处理审批,因此同一轮中另一个交互处于 pending 时 cron_create 根本不会执行(我驱动了 write_file + cron_create 的并行批次,工具压根没有运行);它仍属于纵深防御。另外未覆盖:注入 bridge 时不广告 capability 的反例(从 CLI 无法触达)、PATCH 改绑、单会话绑定多个任务、#9415 的 teardown-versus-reuse 竞态、Windows/macOS。

@doudouOUC

doudouOUC commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough round-2 verification. I independently checked the exact head and agree with the diagnosability finding: these bridge guards still call RequestError.invalidParams(undefined, ...), while the log-safe ACP wrapper strips the raw message unless structured errorKind data is present.

I am deferring this as a follow-up rather than widening #9838 again. The paths fail closed, the issue is explicitly non-blocking, and this PR has already gone through more than the repository threshold of roughly five review rounds where only Critical fixes should continue landing. The current lifecycle fix remains merge-ready; the structured error taxonomy and wire-realistic coverage should be handled separately.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

The bot already has a review of its own on cf63c6e82e1144d17252e0d3a606fd99f65d87a1, which still stands.

机器人在 cf63c6e82e1144d17252e0d3a606fd99f65d87a1 上已有自己的评审,且仍然有效。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking findings.

CI: Test (ubuntu-latest, Node 22.x) ✅ · Real daemon E2E ✅ · Serve A/B ✅ · web-shell E2E Smoke ✅ · Desktop Shell (ubuntu + windows) ✅ · Integration Tests (CLI, No Sandbox) ⚠️ SKIPPED · Test (macos-latest / windows-latest, Node 22.x) ⚠️ SKIPPED. The integration test suite is SKIPPED, and the updated qwen-serve-routes.test.ts:306 assertion lives outside every npm workspace so it is not exercised by CI.

Authorization chain verified:

  • ACP Session stamps its own sessionId + executing promptId (not caller-supplied). Bridge validates callerSessionId is owned by this connection AND entry.activePromptId === promptId. Both hold at the initial check and at write-lock recheck via assertCallerPromptActive — the closure captures the original entry object so object-identity change or prompt-end between calls is caught.
  • assertReusableScheduledTaskSession is called twice: once before the task object is built, and once inside the updateCronTasks write-lock callback. Both calls invoke assertCallerPromptActive?.() first, serializing eligibility + prompt-active with the atomic write.
  • REST path keeps the idle rule (allowActivePrompt = false); cron-tool path allows an active prompt and rejects pendingInteractionCount > 0 at both call sites.
  • Cross-workspace sessions rejected before session lookup; ambiguous owner resolution fails closed with 500.
  • preserveScheduledTaskCreateErrorOverAcp converts ExistingSessionScheduledTaskCreateError to typed RequestError with errorKind — structured rejections surface to the tool caller.
  • Retry-After: 1 header is set on the workspace_runtime_unavailable path ✓
  • Optional chain fixed to workspace.capabilities?.features?.includes(...)
  • Bootstrap advertises scheduled_task_session_reuse: false; capability appears only after the managed runtime mounts ✓

One minor finding (R1-1, inline): the source-eligibility check in handleCreateCurrentSessionScheduledTask reads parentSessionId/sourceType/sourceId from the in-memory BridgeClientSessionEntry. After a daemon restart, a reconnected session's bridge entry may not carry this lineage metadata. The fast-path check would pass for a parented sub-session whose entry fields are zero-valued on reconnect. Defense-in-depth is provided by the second authoritative check via bridge.getSessionSummary(), which reads from persisted session data. Not a blocker given the fallback.

Cross-check against existing reviews:

  • yiliang114 (head 1180fcd): optional-chain crash → fixed in current head
  • wenshao (CHANGES_REQUESTED → APPROVED 2026-08-26): bootstrap capability timing, Retry-After header, promptId threading, pendingInteractionCount staleness, sessionMode reset on capability loss → addressed in current head per wenshao's approval
  • qwen-code-ci-bot deferred Suggestion-level (D2-2 through D3-8): missing test pins for source-matrix, control-method payload-validation, classifier projection, submit-time fail-closed — recorded; none are blockers under the convergence posture
  • R1-1 → same concern as qwen-code-ci-bot bridgeClient.ts:660 finding — confirmed, mitigated by getSessionSummary fallback

Not covered: execution rungs 1-3 not run (no local toolchain). Windows/macOS platform behavior (SKIPPED CI). Integration test suite (SKIPPED CI). Design doc reviewed for algorithmic consistency only.

Reviewed with AI assistance.

);
}
if (
entry.parentSessionId !== undefined ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Source-eligibility from in-memory bridge entry may be incomplete after restart (minor): The check here reads parentSessionId, sourceType, and sourceId from the live BridgeClientSessionEntry. If a session reconnects after a daemon restart and the bridge entry is rebuilt without restoring these lineage fields, the guard passes for a parented sub-session or scheduled-task-source session whose entry fields are undefined.

The assertReusableScheduledTaskSession call downstream — both the pre-validation pass and the write-lock recheck — repeats an equivalent check via bridge.getSessionSummary(), which reads from persisted session data. That fallback provides defense-in-depth. If entries are always rehydrated with lineage fields on reconnect, the fast-path check here is redundant but correct; if they are not, only getSessionSummary catches the case.

Not a blocker given the defense-in-depth path, but worth confirming that BridgeClientSessionEntry entries are always populated with parentSessionId/sourceType/sourceId during session rehydration.

@doudouOUC
doudouOUC dismissed qwen-code-ci-bot’s stale review August 26, 2026 07:58

Already have 2 approves,3ks.

@doudouOUC
doudouOUC added this pull request to the merge queue Aug 26, 2026
Merged via the queue into QwenLM:main with commit 7b69293 Aug 26, 2026
310 of 315 checks passed
@doudouOUC
doudouOUC deleted the docs/scheduled-task-current-session-entrypoints branch August 26, 2026 07:59
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ⚠️ not run — skipped - workflow run

Skipped because the PR is not open for verification (state=MERGED, draft=false).

中文 — 判定:⚠️ 未运行 · 已跳过

跳过原因:the PR is not open for verification (state=MERGED, draft=false)。

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.22.2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants