feat(cli): Add standalone daemon session API - #10179
Conversation
E2E verification reportBuilt and verified commit Baseline against globally installed Qwen Code 0.22.0: After this change, the real-daemon route integration suite passed all 36 tests. It verifies complete-runtime capability/route mounting, incomplete-embed omission, prompt-less creation, lifecycle operations, top-level isolation, structured failures, and normal daemon teardown. Real HTTP socket tests also verify that an interrupted create response detaches only its response client while preserving the committed session, and that an interrupted list request aborts its catalog scan. The focused CLI suites passed 176 tests and the core SessionService suite passed 207 tests, including deletion journal identity validation, pre-commit rollback, post-commit cleanup continuation, cleanup-pending reporting, foreign-owner rejection, child-session hiding, and internal LiveTask child restoration. The full credential-dependent integration matrix was not runnable in this environment because external model credentials and deferred-tool infrastructure were unavailable. Windows/Linux platform coverage and the complete process-crash injection matrix remain reviewer/CI validation items; no result is claimed for them here. |
|
Re-run at the author's request. The head moved from Template looks good ✓ Problem: unchanged from the first pass and still real, not theoretical. Issue #8908 is the roadmap source of truth for standalone sessions, the predecessors (PR0 #8890, PR2B #9978) are merged, and the daemon-layer API for prompt-less top-level sessions is the explicitly planned next step. Direction: aligned — this completes the daemon contract the roadmap describes while keeping standalone sessions isolated from the primary workspace and preserving LiveTask's internal child-restore path. It introduces a new public daemon REST surface ( Size: now 4,236 production-logic lines (3,888 test, 1,015 docs; total +8,755/−384 across 33 files — up from ~2,610 production lines at the first pass). Core production share: Approach: the two commits added since the first pass are on-purpose. Risk: no elevated risk signals — no Stage 1e high-risk paths matched. Moving on to re-review. 🔍 The Stage 0 core-size escalation still applies: the gate completes a full review but will not auto-approve. 中文说明应作者请求重跑。自首次门禁以来,head 已从 模板完整 ✓ 问题:与首轮判断一致,是真实问题而非理论性问题。Issue #8908 是 standalone session 的路线图来源,前序 PR(PR0 #8890、PR2B #9978)已合并,为无初始 prompt 的顶层 session 提供 daemon 层 API 是路线图中明确规划的下一步。 方向:对齐——完成路线图描述的 daemon 契约,保持 standalone session 与 primary workspace 隔离,并保留 LiveTask 的内部 child 恢复路径。本 PR 引入新的公共 daemon REST 面( 规模:现为 4,236 行生产逻辑(测试 3,888 行,文档 1,015 行;共 33 个文件,+8,755/−384——高于首轮时的约 2,610 行生产代码)。核心部分占比: 方案:首轮之后新增的两个提交都目的明确。 风险:无升级风险信号——未命中 Stage 1e 高风险路径。 进入重新审查 🔍 Stage 0 核心模块规模升级仍然适用:门禁会完成完整审查,但不会自动批准。 — Qwen Code · qwen3.8-max Reviewed at |
Code review (re-run at
|
| File | What changed |
|---|---|
packages/cli/src/serve/routes/standalone-sessions.ts |
Route family: create, list, get, load/resume, repair, rename, export, batch archive/unarchive/delete |
packages/cli/src/serve/conversations/standalone-session-service.ts |
Lifecycle operations, crash-recovery reconciliation, fail-closed identity checks; round-1 fixes for memo eviction and listing stability |
packages/cli/src/serve/conversations/standalone-deletion-journal.ts |
Private deletion journal: atomic writes, identity validation, hardening pass |
packages/cli/src/serve/conversations/conversation-workspace.ts |
Directory staging/restore/removal and deletion-path inspection |
packages/core/src/services/sessionService.ts |
Split transcript removal from post-commit cleanup; lifecycle entry points |
packages/core/src/services/session-writer-lease.ts |
Writer-lease retention across retryable durability failures; mutual-delegation guards |
packages/core/src/config/config.ts |
Durability-pending lease-retention branches in chat-runtime activation |
packages/core/src/services/chatRecordingService.ts |
Origin-runtime response cleanup preserved across runtime rollover |
packages/cli/src/serve/server.ts |
Wiring: journal, foreign-owner check, route registration, availability flag |
packages/cli/src/serve/capabilities.ts |
Conditional standalone_sessions_v1 capability |
packages/cli/src/serve/server/serve-features.ts |
Feature toggle plumbing |
packages/cli/src/serve/server/error-response.ts |
Error-code to HTTP mapping for the new failure modes |
packages/cli/src/serve/server/session-export.ts |
runtimeBaseDir so export reads the Conversations storage namespace |
packages/cli/src/serve/session-id-admission.ts |
Exported persistence-existence check for foreign-owner scans |
packages/cli/src/utils/conversation-directory-identity.ts |
Staged directory names, stricter inode comparison, object-identity helper |
packages/cli/src/serve/live/live-task-service.ts |
Internal restore switched to resumeForInternalTask |
packages/acp-bridge/src/sessionAttachments.ts |
Parent-directory disappearance rejection for transcript sidecars and attachments |
docs/developers/qwen-serve-protocol.md |
Protocol table and semantics for the new route family |
docs/design/standalone-daemon-sessions.md |
Design doc updates for route ownership and recovery |
docs/plans/2026-08-25-standalone-pr3-daemon-api.md |
Implementation plan |
| 13 test files | Route, service, journal, workspace, server, lease, config, and attachment coverage |
Testing
Evidence carried here: the PR's own CI checks on the reviewed commit, fetched via the API (per gate rules I do not build or run PR code). All lanes are green on c7937b3d — including the main unit suite Test (ubuntu-latest, Node 22.x) and the daemon Serve A/B lane that were still running during the first pass. Integration Tests (CLI, No Sandbox) is skipped by the workflow's own gating (as before; the /review pass also flagged it as an uncovered lane). Separately, the bot serve A/B against head c7937b3 reported no response changes across 12 scenarios, and maintainer @qqqys drove a live-daemon tmux E2E on the earlier head 15ef36de (capability gating, bearer-token posture on loopback, strict 400 validation, fail-closed creation under a real ACP child crash) — that is the human reviewer's evidence, not mine, and it predates the two hardening commits.
CI results for c7937b3 (auto-updated by the triage finalize job if checks move):
| Check | Conclusion |
|---|---|
Classify PR |
✅ success |
Dependency CVE audit |
✅ success |
Desktop Shell (ubuntu-22.04) |
✅ success |
Desktop Shell (windows-2022) |
✅ success |
Live Host (macos-latest) |
✅ success |
Post Coverage Comment (ubuntu-latest, 22.x) |
✅ 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 |
macos-latest / Java 21 |
✅ 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,失败项排在最前。
The crash paths in the unit suite are exercised through mocks, so green CI pins the tests but not the live behaviour itself. Sandboxed verification would settle the remainder: a @qwen-code /verify run is already marked in flight at the time of writing — it is the lane that would A/B-prove the fail-closed guards and journal phase checks actually pin the suite (i.e. that the suite fails with the guards removed) and drive the disconnect/recovery semantics against a live daemon. No TUI surface, so /tmux is not needed.
Not verified: Windows/Linux behavior (author's local testing was macOS-only), the real process-crash injection matrix, and the author's self-reported suite numbers were not independently re-run here — the CI green run above is the independent signal.
中文说明
代码审查(在 c7937b3d 上重跑)
首轮已在 15ef36de 上通读全部 diff,设计与我的独立方案一致,未发现关键阻塞。此后新增两个提交:加固提交 ab7290fe(由 /review 流程深度覆盖,共 35 项发现)与评审修复提交 c7937b3d——本轮已对照代码逐项核实后者。
第一轮评审的两个 Critical 均已正确修复:
- R1-1(调和记忆化)。
reconcilePendingDeletions现在会在拒绝时逐出被记忆化的 sweep,且以 promise 身份校验(reconciliations.get(runtime) === pending)确保只移除仍指向失败 promise 的条目。在途调用方仍会收到拒绝(保持失败即关闭),下一次操作触发全新 sweep 而非重抛守护进程生命周期内缓存的拒绝,且该处理器消费了拒绝、不会留下未处理的 rejection。新的瞬态失败测试固定了这一点:第一次rename以EIO失败,第二次成功,日志被列出两次。 - R1-19(列表稳定性)。 每行稳定性探测现在只吞掉
SessionArchivingError——我已在源头(session-archive.ts)核实:该错误恰是"此行当前持有独占生命周期锁"的信号,由runSharedMany内的assertNotTransitioning抛出。正在转换中的行(归档/重命名/删除进行中)被省略,稳定行仍会返回,其余一切——包括DaemonDrainingError与中止——仍然向上传播。这个范围是正确的:drain 对整个请求是终结性的,不是逐行条件。新的转换行测试固定了"省略但不隐藏稳定行"。
增量卫生: 修复提交恰好触及两个文件(服务 +34/−23,对应测试 +97)——对第一轮评审的最小响应,无顺手改动。
持续披露: 对加固提交本身(ab7290fe,+3,438 行)的深度依赖 /review 流程,本轮未做独立的逐行重读;该轮其余 33 项 Suggestion 由作者按评审轮次预算推迟,仍保留在各自线程中——属于维护者后续跟进事项,不构成门禁阻塞。
删除生命周期与首轮一致且仍然准确(时序图见英文部分):崩溃发生在 unlink 之前 → 恢复还原 staged 目录;之后 → 恢复继续完成清理。任何情况下都由日志裁决,绝不猜测。
测试
本节证据:通过 API 获取的被审查提交上的 PR 自身 CI 检查(按门禁规则不构建、不运行 PR 代码)。c7937b3d 上所有泳道全绿——包括首轮时仍在运行的主单测泳道 Test (ubuntu-latest, Node 22.x) 与 daemon Serve A/B。Integration Tests (CLI, No Sandbox) 因工作流自身门控被跳过(与之前一致;/review 也将其标记为未覆盖泳道)。另外,bot serve A/B 在 head c7937b3 上报告 12 个场景无响应差异;维护者 @qqqys 在较早 head 15ef36de 上做过真实 daemon 的 tmux E2E(capability 门控、loopback 上的 bearer token 姿态、严格 400 校验、真实 ACP 子进程崩溃下的失败即关闭创建)——那是人类评审者的证据而非我的,且早于两个加固提交。
单测套件中的崩溃路径通过 mock 演练,绿色 CI 锁定的是测试本身而非真实行为。沙箱验证可补足剩余部分:写作时已有一条进行中的 @qwen-code /verify 运行——它将用 A/B 证明 fail-closed 守卫与日志阶段检查真正锁定套件(移除守卫后套件应失败),并在真实 daemon 上演练断连/恢复语义。无 TUI 改动,不需要 /tmux。
未验证:Windows/Linux 行为(作者本地测试仅覆盖 macOS)、真实进程崩溃注入矩阵;作者自述的测试数字未在此独立复现——上方绿色 CI 是独立信号。
— Qwen Code · qwen3.8-max
Reviewed at c7937b3d8e15dfd939a61cf581768443ca7ff339 · re-run with @qwen-code /triage
|
Confidence: 3/5 — clean re-review with both Critical findings verifiably fixed and CI fully green on the reviewed head, but the core-size policy cap still reserves the decision for a maintainer. Stepping back: nothing in this round changed my read of the PR's shape, and several things strengthened it. My independent proposal and the implementation still converge — I have not found a materially simpler construction for restart recovery that never guesses. The review-fix commit is exactly what a fix round should look like: two files, one focused change per Critical, each pinned by a regression test, nothing else touched. Both fixes hold up under inspection at the source level, not just in the diff: the memo eviction is identity-guarded so it cannot clobber a newer sweep, and the listing catch is scoped to precisely the "row holds an exclusive lifecycle lock" error, with drain and abort still propagating as they should. The evidence stack is materially better than the first pass: every CI lane green on Why 3/5 and not an approval: policy, not doubt. The PR touches core infrastructure at 4,236 production-logic lines and introduces a new public daemon REST contract — the Stage 0 maintainer-awareness escalation caps the gate's confidence and no approve-on-green marker is emitted: the final call is a human's even with every check green, which they now are. One housekeeping note: the standing 中文说明置信度:3/5 —— 重新审查干净,两个 Critical 发现已被可核实地修复,被审查 head 上 CI 全绿,但核心模块规模的政策上限仍将决定权保留给维护者。 退一步看:本轮没有改变我对 PR 形态的判断,反而有几方面增强了它。我的独立方案与实现依然一致——对于"重启恢复绝不猜测"这一目标,我没有找到更简单的构造。评审修复提交正是一轮修复该有的样子:两个文件、每个 Critical 一处聚焦的改动、各由一个回归测试固定、没有任何其他改动。两处修复在源头层面而不仅是 diff 层面经得起检查:记忆化逐出有身份保护,不会误删更新的 sweep;列表的 catch 精确限定在"该行持有独占生命周期锁"的错误上,drain 与中止仍按应有方式向上传播。 证据链比首轮显著更充分: 为什么是 3/5 而非批准:是政策,不是疑虑。本 PR 以 4,236 行生产逻辑触及核心基础设施并引入新的公共 daemon REST 契约——Stage 0 的维护者关注升级限制了门禁的置信度,因此不发 approve-on-green 标记:即使所有检查全绿(现在确实全绿),最终决定也由人类做出。一项事务性说明:本 PR 上悬而未决的 — Qwen Code · qwen3.8-max Reviewed at |
|
⏸️ Still deferred to @chiga0 @yiliang114 — this one needs a human call. Re-run status at The decision stays out of the gate's hands for the same two reasons as before: the core-module policy escalates any For sign-off: the standing — Qwen Code · qwen3.8-max |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 12 scenario(s). — Qwen Code · serve A/B |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): chunk 29: none — I did not run the suite; verification was by static trace of each test against the implementation, which was conclusive for every assertion in the chunk.; chunk 21: execute standalone-sessions.test.ts to confirm it compiles and passes (worktree has no node_modules /built dist ; a full install + build exceeds this chunk'….
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
Test Plan (not a blocker): 027 passed — this review observed 1741, 25078, 21930, 1662, 605, 4296, 638 passed; 179 passed — this review observed 1741, 25078, 21930, 1662, 605, 4296, 638 passed; 53 passed — this review observed 1741, 25078, 21930, 1662, 605, 4296, 638 passed.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):chunk 29:none — I did not run the suite; verification was by static trace of each test against the implementation, which was conclusive for every assertion in the chunk.;chunk 21:execute standalone-sessions.test.ts to confirm it compiles and passes (worktree has no node_modules /built dist ; a full install + build exceeds this chunk'…。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
Test Plan(非阻断):027 passed — this review observed 1741, 25078, 21930, 1662, 605, 4296, 638 passed; 179 passed — this review observed 1741, 25078, 21930, 1662, 605, 4296, 638 passed; 53 passed — this review observed 1741, 25078, 21930, 1662, 605, 4296, 638 passed。
— qwen3.8-max via Qwen Code /review (v0.22.2)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Review round summary for
Verification: independent pre-fix reproduction of both Critical findings; |
tmux E2E test report (head
|
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (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: 58 passed · 1 failed · 59 total Flakiness gate: 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:58 通过 · 1 失败 · 59 总计 抖动门: Verification reportVerification report — PR 10179
|
| cell | oracle | result |
|---|---|---|
| head: capability gate | /capabilities.features contains standalone_sessions_v1 once runtime is up |
PASS (absent only in the pre-existing bootstrap window, same as other runtime features) |
| head: create | 200, echoes caller UUID, sourceType=standalone, context.kind=standalone, managed dirs present |
PASS |
| head: duplicate create | fail closed ≥400 | PASS (409 parity: FAIL — see F1, got 500) |
| head: lookup / list | 200 + session present | PASS |
| head: resume / rename / export | 200; rename persisted; export 200 + attachment disposition |
PASS |
| head: archive while busy | per-id session_busy error, batch 200 |
PASS |
| head: archive/unarchive after idle | archived list ↔ active list membership flips both ways | PASS |
| head: repair-directory | 200 with directory info | PASS |
| head: validation probes | non-UUID 400, unknown field 400, malformed cursor 400 invalid_cursor, unknown query 400, unknown id 404, no-auth 401, 101-id batch 400 |
PASS |
| head: delete | removed outcome; lookup 404; absent from active+archived lists; journal record cleared; re-delete → notFound |
PASS |
| base: capability gate | feature absent | PASS (5/5) |
| base: route family | POST/GET/lookup/delete all 404 | PASS |
Witnesses: evidence/01-ab-base-routes-absent.png (base 5/5), evidence/02-ab-head-lifecycle.png (head 34/35, the one red line is F1). Raw logs: logs/head-final-capture.log, logs/base-run.log.
Crash-safety primitives (secondary B)
04-journal-harness.mjs drives the compiled StandaloneDeletionJournal and ConversationWorkspace staging primitives on a real filesystem: 15/15 (journal durability of prepared/staged phases across simulated restarts; truncated record → compromised; staged-without-prepared rejected; stage→restore preserves inode; stage→remove cleans; parent-root disappearance after open fails closed for both restore and remove and keeps replacement data). Witness: evidence/03-journal-crash-safety.png.
Reviewer Test Plan walk-through
- Capability with/without complete dependency graph — verified live (head advertises after runtime swap; base absent); the partial-graph cell is pinned by
server.test.ts"mounts and advertises…" (mutation B below proves the pin). - Full lifecycle without prompt/workspace fields — verified live, 34/35 (F1 is the duplicate-create status).
- Fail-closed on child/Live/project/worktree/ambiguous/unreadable/foreign-owned — wire-level: unknown id 404, duplicate ≥400, unauth 401; the record-class matrix is covered by the PR's service tests (147/147) but not independently re-driven at wire level (Not covered).
- Disconnect-during-create / list-scan abort — not re-driven end-to-end (Not covered); covered by PR tests.
- Delete interrupted pre/post transcript unlink — verified at the primitive level (journal harness) + PR service tests; daemon-kill E2E not run (Not covered).
- Parent-directory disappearance + malformed cursor — both verified live/primitive (400
invalid_cursorat wire; fail-closed with replacement data kept at primitive level).
Findings
F1 (Suggestion) — duplicate standalone create returns 500 instead of 409
Repro (head build): POST /standalone/sessions {"sessionId":"<existing>"} → 500 {"code":"session_id_conflict"}. The same RequestedSessionIdAdmissionError is explicitly mapped to 409 by the sibling workspace create route (routes/session.ts sendRequestedSessionIdAdmissionError), but the standalone routes funnel it into sendBridgeError, which has no branch for it and falls through to the generic 500 — also logging it as an unexpected internal error. Fail-closed is preserved (no duplicate is created), but the status misclassifies a client conflict as a server fault, breaking parity with the workspace surface and with the PR's own standalone_* 409 mappings. The PR's standalone tests do not pin this wire status (grep confirms no session_id_conflict case in standalone-*.test.ts).
Measured minimal fix (not applied to the PR; scratch-verified then reverted)
Add to packages/cli/src/serve/server/error-response.ts (after the StandaloneSessionServiceError branch):
if (err instanceof RequestedSessionIdAdmissionError) {
res
.status(err.code === 'session_id_admission_unavailable' ? 503 : 409)
.json({ error: err.message, code: err.code, sessionId: err.sessionId });
return;
}Measured with the patch: head harness 35/35 (was 34/35); error-response.test.ts + standalone-sessions.test.ts 56 passed (unchanged); server.test.ts -t standalone 7 passed (unchanged); package typecheck/build clean. Logs: logs/head-run-with-fix.log.
Mutation (vacuity) matrix
| mutation | suite that should catch it | result |
|---|---|---|
A: delete InvalidCursorError → 400 mapping in routes/standalone-sessions.ts |
standalone-sessions.test.ts |
killed: 1 failed / 21 passed (exactly the pinning test) |
B: delete standalone_sessions_v1 conditional entry in capabilities.ts |
server.test.ts "mounts and advertises…" |
killed: 1 failed |
Both central new behaviors are pinned by their tests; no vacuous tests found among the checked pair.
Targeted gates (head)
packages/clistandalone suites (routes, service, journal, workspace): 147 passed (4 files).packages/cliserver.test.ts -t standalone: 7 passed (incl. the dependency-graph gating test).packages/core(sessionService, rename, writer-lease, chatRecordingService, config): 1029 passed, 2 skipped (5 files).packages/acp-bridgesessionAttachments.test.ts: 53 passed.npm run build -w packages/cli(tsc) clean at HEAD and with the scratch fix.
Not covered
- Wire-level fail-closed probes for child/Live/project/worktree/ambiguous/unreadable/foreign-owned records (constructing those states needs a second daemon/child sessions; covered by the PR's 147-test service+route suites instead).
- Response-disconnect-during-create and list-scan-abort E2E (step 4 of the plan); covered by PR tests only.
- Daemon kill -9 mid-delete recovery E2E; the durable primitives are verified (15/15) and the service recovery logic by PR tests.
- Full
server.test.ts(1091 tests) and the credential-dependent integration matrix; per-commit attribution (checkout is depth 2 — only merge/base-tip/PR-head reachable; the 6-commit history in the snapshot was verified as an aggregateHEAD^1..HEADdiff). - The bootstrap-window
/capabilitiesunder-reportsstandalone_sessions_v1until runtime swap; this matches the pre-existing bootstrap design for other runtime-dependent features and is not counted as a finding. - Windows (PR author also did not test it).
Methodology
Environment: CI merge-ref checkout (depth 2) in the lane's node:22-bookworm container; npm ci+npm run build pre-ran at HEAD. A/B control: scratch worktree at HEAD^1 with its own npm ci (lockfile untouched by the PR) and prepare build; internal workspace symlinks' realpaths asserted inside the base tree. Harnesses (01/02/03-lifecycle…mjs, 04-journal-harness.mjs) are mock-free: real daemon child processes, real loopback HTTP, real compiled dist/ modules on a real filesystem; every check is a scripted comparison. Mutations applied in-place, suites run, then git checkout -- restored (worktree verified clean). Raw logs in logs/, harnesses and captures in this directory. Assertion counts in assertions.json map one-to-one to the checks above (58 expected-outcome passes incl. 2 mutation-kills and 2 fix-verification cells; 1 unexpected failure = F1).
Flakiness gate log
rounds=5 files=13 skipped=0
file packages/acp-bridge/src/sessionAttachments.test.ts: (cd packages/acp-bridge) npx --no-install vitest run ./src/sessionAttachments.test.ts
file packages/cli/src/serve/conversations/conversation-workspace.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/conversations/conversation-workspace.test.ts
file packages/cli/src/serve/conversations/standalone-deletion-journal.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/conversations/standalone-deletion-journal.test.ts
file packages/cli/src/serve/conversations/standalone-session-service.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/conversations/standalone-session-service.test.ts
file packages/cli/src/serve/live/live-task-service.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/live/live-task-service.test.ts
file packages/cli/src/serve/routes/standalone-sessions.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/standalone-sessions.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/cli/src/serve/server/error-response.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/error-response.test.ts
file packages/core/src/config/config.test.ts: (cd packages/core) npx --no-install vitest run ./src/config/config.test.ts
file packages/core/src/services/chatRecordingService.test.ts: (cd packages/core) npx --no-install vitest run ./src/services/chatRecordingService.test.ts
file packages/core/src/services/session-writer-lease.test.ts: (cd packages/core) npx --no-install vitest run ./src/services/session-writer-lease.test.ts
file packages/core/src/services/sessionService.rename.test.ts: (cd packages/core) npx --no-install vitest run ./src/services/sessionService.rename.test.ts
file packages/core/src/services/sessionService.test.ts: (cd packages/core) npx --no-install vitest run ./src/services/sessionService.test.ts
per-file results (P=pass F=fail I=infra-exit, one letter per run):
packages/acp-bridge/src/sessionAttachments.test.ts: PPPPP
packages/cli/src/serve/conversations/conversation-workspace.test.ts: PPPPP
packages/cli/src/serve/conversations/standalone-deletion-journal.test.ts: PPPPP
packages/cli/src/serve/conversations/standalone-session-service.test.ts: PPPPP
packages/cli/src/serve/live/live-task-service.test.ts: PPPPP
packages/cli/src/serve/routes/standalone-sessions.test.ts: PPPPP
packages/cli/src/serve/server.test.ts: PPPP
packages/cli/src/serve/server/error-response.test.ts: PPPP
packages/core/src/config/config.test.ts: PPPP
packages/core/src/services/chatRecordingService.test.ts: PPPP
packages/core/src/services/session-writer-lease.test.ts: PPPP
packages/core/src/services/sessionService.rename.test.ts: PPPP
packages/core/src/services/sessionService.test.ts: PPPP
verdict: timeout
summary: only 4 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/sessionAttachments.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/conversations/standalone-deletion-journal.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/conversations/standalone-session-service.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/live/live-task-service.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/standalone-sessions.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 1 · packages/core/src/config/config.test.ts: P (exit 0)
round 1 · packages/core/src/services/chatRecordingService.test.ts: P (exit 0)
round 1 · packages/core/src/services/session-writer-lease.test.ts: P (exit 0)
round 1 · packages/core/src/services/sessionService.rename.test.ts: P (exit 0)
round 1 · packages/core/src/services/sessionService.test.ts: P (exit 0)
round 2 · packages/acp-bridge/src/sessionAttachments.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/conversations/standalone-deletion-journal.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/conversations/standalone-session-service.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/live/live-task-service.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/routes/standalone-sessions.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 2 · packages/core/src/config/config.test.ts: P (exit 0)
round 2 · packages/core/src/services/chatRecordingService.test.ts: P (exit 0)
round 2 · packages/core/src/services/session-writer-lease.test.ts: P (exit 0)
round 2 · packages/core/src/services/sessionService.rename.test.ts: P (exit 0)
round 2 · packages/core/src/services/sessionService.test.ts: P (exit 0)
round 3 · packages/acp-bridge/src/sessionAttachments.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/conversations/standalone-deletion-journal.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/conversations/standalone-session-service.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/live/live-task-service.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/routes/standalone-sessions.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 3 · packages/core/src/config/config.test.ts: P (exit 0)
round 3 · packages/core/src/services/chatRecordingService.test.ts: P (exit 0)
round 3 · packages/core/src/services/session-writer-lease.test.ts: P (exit 0)
round 3 · packages/core/src/services/sessionService.rename.test.ts: P (exit 0)
round 3 · packages/core/src/services/sessionService.test.ts: P (exit 0)
round 4 · packages/acp-bridge/src/sessionAttachments.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/conversations/standalone-deletion-journal.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/conversations/standalone-session-service.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/live/live-task-service.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/routes/standalone-sessions.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 4 · packages/core/src/config/config.test.ts: P (exit 0)
round 4 · packages/core/src/services/chatRecordingService.test.ts: P (exit 0)
round 4 · packages/core/src/services/session-writer-lease.test.ts: P (exit 0)
round 4 · packages/core/src/services/sessionService.rename.test.ts: P (exit 0)
round 4 · packages/core/src/services/sessionService.test.ts: P (exit 0)
round 5 · packages/acp-bridge/src/sessionAttachments.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/conversations/conversation-workspace.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/conversations/standalone-deletion-journal.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/conversations/standalone-session-service.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/live/live-task-service.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/routes/standalone-sessions.test.ts: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): chunk 22: running standalone-sessions.test.ts under vitest to confirm the suite is green (worktree lacks node_modules/dist; install+build not feasible in budget).
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
Test Plan (not a blocker): 027 passed — this review observed 1741, 25080, 21930, 1662, 605, 4296, 638 passed; 179 passed — this review observed 1741, 25080, 21930, 1662, 605, 4296, 638 passed; 53 passed — this review observed 1741, 25080, 21930, 1662, 605, 4296, 638 passed.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
docs/plans/2026-08-25-standalone-pr3-daemon-api.md:457 — [review] plan recovery table promises a vanished-child record the code never writespackages/cli/src/serve/conversations/standalone-session-service.test.ts:692 — [review] deletion phase ordering pinned by no testpackages/cli/src/serve/conversations/standalone-session-service.test.ts:610 — [review] closeLiveStandaloneIfPresent never exercised by delete/archive testspackages/cli/src/serve/conversations/standalone-session-service.ts:511 — [review] raw journal error escapes the structured error contract on read pathspackages/cli/src/serve/conversations/standalone-session-service.test.ts:2029 — [review] repairDirectory give-up path has no test witnesspackages/core/src/services/sessionService.test.ts:2219 — [probe] remove-path parent-identity check unpinnedpackages/core/src/services/sessionService.test.ts:2343 — [probe] ENOENT-skip branch of openDurableDirectoryIfPresent unpinneddocs/plans/2026-08-25-standalone-pr3-daemon-api.md:590 — [probe] Required local checks block cannot run as writtenpackages/cli/src/serve/server.ts:1598 — [probe] hasForeignSessionOwner wiring loops exercised by no testpackages/core/src/services/sessionService.ts:2480 — [review] public cleanupRemovedSessionState() has zero production callers
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):chunk 22:running standalone-sessions.test.ts under vitest to confirm the suite is green (worktree lacks node_modules/dist; install+build not feasible in budget)。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
Test Plan(非阻断):027 passed — this review observed 1741, 25080, 21930, 1662, 605, 4296, 638 passed; 179 passed — this review observed 1741, 25080, 21930, 1662, 605, 4296, 638 passed; 53 passed — this review observed 1741, 25080, 21930, 1662, 605, 4296, 638 passed。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 10 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
Review follow-up for 1e29893 (rebased equivalent of faf47aa):
Verification before rebase: full repository build and typecheck passed; focused StandaloneSessionService and LiveTaskService suites passed 108/108. Independent before/after probes reproduced the baseline failure and verified read_thread, wait_threads, and send_message_to_thread on a cold child, including resume and prompt dispatch. Rebase verification: rebased onto current origin/main, resolving the sole import conflict by preserving both main’s session PR backfill route and this PR’s standalone route/admission helper. Range-diff shows the other patches are equivalent. Standalone/live/route tests pass 130/130; server.test.ts passes 1101/1101; affected files pass ESLint and Prettier. Current origin/main itself has unrelated build/typecheck errors in files untouched by this PR, so the post-rebase full build/typecheck cannot complete without expanding scope. Two consecutive post-rebase diff audits found no actionable issue. The historical cancelled Qwen Autofix route runs were superseded comment-triggered runs rather than code failures, so they were not rerun. The PR is now mergeable; fresh CI is pending. |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
faf47aa to
1e29893
Compare
|
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)为单个提交。 |
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>
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>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
1e29893 to
7dd7f52
Compare
|
Rebased onto the latest Verification on the rebased head
The two transient failures seen when multiple large CLI suites were combined in one Vitest invocation both passed in isolation; the complete server suite also passed when run on its own. |
|
@qwen-code /triage |
yiliang114
left a comment
There was a problem hiding this comment.
Verified at head 7dd7f52 — the round-2 Critical (R2-1) is closed:
readStandaloneSummarynow takes arequiredScopeand hides child standalone sessions only on the'top-level'path;get()keeps the public top-level contract whilegetForInternalTask()passes'any'.LiveTaskService.locateTaskresolves thread ids throughgetForInternalTask(both call sites), sosend_message_to_thread/read_thread/wait_threadsreach child standalone sessions again, matching theresumeForInternalTaskscope widening.
Scope note: this approval rests on that verification plus the resolved thread history and green CI on this head; the earlier review's disclosed gaps (integration and windows lanes skipped by gating, round-2 deferred test-witness suggestions) remain on record for follow-up.
chiga0
left a comment
There was a problem hiding this comment.
Cross-check review (see existing R1-1 – R1-41 from qwen-code-ci-bot).
CI disclosure (mandatory): build-cli, Test (windows-latest), Test (macos-latest), and Integration Tests (CLI, No Sandbox) are all SKIPPED. The diff contains process.platform === 'win32' branches, Windows-specific fsync error tolerance (EACCES/EINVAL/EPERM), and inode-based identity checks that return 0 on Windows. None of these paths have CI coverage.
Cross-check against existing findings:
R1-1 (Critical — CONFIRMED): reconcilePendingDeletions memoizes the sweep via a WeakMap<WorkspaceRuntime, Promise<void>>. The cleanup handler uses .catch(), which only fires on rejection — a resolved promise is never evicted. After the first successful sweep this line never runs again for this runtime lifetime, and listSessionIds is never called a second time. The CI bot's probe (listSessionIds call count: 1 on the second call) confirms this behavior. Fix: replace void pending.catch(...) with void pending.finally(...).
R1-19 (Critical — REFUTED at this head): The stability loop in list() contains catch (error) { if (!(error instanceof SessionArchivingError)) throw error; } at line 646. SessionArchivingError is caught per-row, stable stays false, and the session is silently omitted — not propagated as a 409 to the whole-page response. The finding's premise ("nothing catches it") does not hold against SHA 7dd7f52.
R1-12 (Suggestion — REFUTED): writeStaged runs under lifecycle.runExclusiveAfterShared(sessionId, ...), so no concurrent writePrepared can race it for the same session ID. The pendingClears interleave described requires two concurrent exclusive locks on the same session, which the lifecycle coordinator prevents.
Unreviewed dimensions: Windows platform paths (CI SKIPPED) · macOS paths (CI SKIPPED) · Integration tests (CI SKIPPED) · creation/restore lifecycle (lines 1400-2400 of standalone-session-service.ts)
Reviewed with AI assistance.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Review follow-up for head
Verification: independent baseline reproduction confirmed the successful-sweep cache defect; |
chiga0
left a comment
There was a problem hiding this comment.
Re-review of the range since my prior review (7dd7f52b → fbd3bf32). No blocking findings at this head.
Changes since round 1:
-
R1-1 fixed (
fbd3bf32— "Re-run standalone deletion reconciliation"):reconcilePendingDeletionsnow usespending.then(clearPending, clearPending)instead ofpending.catch(clearPending). Both settled states (resolved and rejected) evict the map entry, so the next call creates a fresh sweep. The added regression test (repeats deletion reconciliation after a successful sweep) directly pins the fix by assertinglistSessionIdsis called twice after two sequential operations. Fix is correct and fully witnessed. ✓ -
R2-1 fixed (commit
7dd7f52b— "Preserve internal child task lookup", already in head when I last reviewed):LiveTaskService.locateTaskcallsgetForInternalTaskatlive-task-service.ts:1215for standalone sessions, using'any'-scope lookup consistent withresumeForInternalTask. The CI bot probe path (get(C)→standalone_session_not_found→exists: false→SessionNotFoundError) no longer applies. ✓ -
R1-19 not a blocker (confirmed in round 1, no change):
SessionArchivingErroris caught per-row at the list stability loop; the CI bot's premise was refuted at this codebase. ✓
Round-1 Suggestions (deferred by author, unchanged): R1-7 (E2E daemon integration tests missing), R1-29/R1-37/R1-38/R1-39 (test coverage for various lifecycle paths), R1-35 (Windows fsync tolerance unwitnessed). These remain recorded but are not blockers.
CI: build-cli, Test (windows-latest), Test (macos-latest), and Integration Tests (CLI, No Sandbox) still SKIPPED. review-pr automation is in_progress for the new head. No new build/test failures observed.
Reviewed with AI assistance.
yiliang114
left a comment
There was a problem hiding this comment.
LGTM,approve。
快速过了一遍源码主体(standalone-session-service / deletion-journal / routes / server 接线 / core 侧 lease 与生命周期改动):两阶段删除 journal + 崩溃恢复、生命周期准入与锁顺序、条件能力通告、输入严格校验都很扎实,测试覆盖足够。上一轮 R2-1(内部任务访问子会话)已通过 getForInternalTask/resumeForInternalTask + requiredScope 解决,冷子会话定位/恢复有测试钉住。
两个非阻塞的小点,可顺手改可不改:
- docs/plans/2026-08-25-standalone-pr3-daemon-api.md 里"实现后必跑"的 bash 块:cd packages/core 之后接 cd packages/cli 会解析成不存在的路径,整段按顺序跑会断链。
- conversation-workspace.ts 的 syncStandaloneRoot 用 Number.isSafeInteger(ino) && ino > 0 判断 inode 可验证性,其他地方统一用 hasVerifiableInode(ino !== 0),建议统一谓词(触发条件极苛刻,方向是 fail-closed,不影响正确性)。
|
Released in v0.22.3. |



What this PR does
This PR publishes the PR2B standalone-session primitives as a prompt-less, process-global daemon REST lifecycle for top-level standalone sessions. It adds create, list, exact lookup, load/resume, managed-directory repair, rename, export, archive, unarchive, and delete operations, and advertises
standalone_sessions_v1only when the complete Conversations runtime dependency graph is installed.Deletion uses transcript unlink as the durable commit point, with a private deletion journal and atomic managed-directory staging so restart recovery can either restore a pre-commit directory or finish post-commit cleanup without guessing. Public routes fail closed for child, Live, project, worktree, ambiguous, unreadable, foreign-owned, or identity-mismatched sessions, while the existing internal child restore path remains available to LiveTask.
The final review hardening also keeps lifecycle writer leases and directory handles pinned across retryable durability failures, rejects parent-directory disappearance after a successful open for transcript sidecars and attachments, preserves origin-runtime response cleanup across runtime rollover, aborts disconnected list scans, and maps malformed standalone list cursors to the existing
400 invalid_cursorcontract.The protocol and implementation plan document route ownership, compatibility, response-disconnect behavior, batch outcomes, recovery semantics, and the boundary for later SDK and UI work.
Why it's needed
PR2B established the durable standalone transcript, lineage, runtime, and managed-directory primitives, but clients still had no stable daemon surface for creating and managing those sessions without a workspace selector. This PR completes that daemon-layer contract while keeping standalone sessions isolated from the primary workspace and preserving existing LiveTask compatibility.
Reviewer Test Plan
How to verify
/capabilitiesincludesstandalone_sessions_v1; start an embed without the complete dependency graph and confirm both the feature and route family are absent.fileCleanupPendinguntil cleanup succeeds.400 invalid_cursorrather than an internal error.The latest affected suites pass with Core at 1,027 passed and 3 skipped, CLI at 179 passed, and ACP attachment coverage at 53 passed.
npm run build,npm run typecheck,npm run lint, formatting, and diff checks pass on the final commit. Two consecutive broad review passes found no remaining actionable issue. The wider integration suite requires external model credentials and deferred-tool environment that are not available locally; the focused real-daemon standalone-route integration coverage passed earlier in this PR.Evidence (Before & After)
N/A — daemon API, persistence lifecycle, tests, and documentation only; no TUI or Web UI change.
Tested on
Environment (optional)
macOS 26.4.1, Node.js 24.12.0, npm 10.9.8, local daemon and package-specific Vitest suites without a sandbox.
Risk & Scope
featrather than a large-scoperefactor.standalone_sessions_v1and the new routes.Linked Issues
Related to #8908.
中文说明
本 PR 做了什么
本 PR 将 PR2B 的 standalone session 基础能力发布为无初始 prompt、进程级的 daemon REST 生命周期接口,面向顶层 standalone session。新增创建、列表、精确查询、load/resume、托管目录修复、重命名、导出、归档、取消归档和删除操作,并且仅在完整的 Conversations runtime 依赖图已安装时才声明
standalone_sessions_v1。删除以 transcript unlink 作为持久化提交点,并通过私有删除日志与托管目录原子 staging,使重启恢复能够在提交前恢复目录,或在提交后继续完成清理,而无需猜测删除结果。公共路由会对 child、Live、project、worktree、歧义、不可读、外部 runtime 持有或目录身份不匹配的 session 采取 fail-closed;现有 LiveTask 所需的内部 child 恢复路径保持可用。
最终审查加固还会在可重试的持久化失败期间继续固定生命周期 writer lease 与目录 handle;在 transcript sidecar 或 attachment 的父目录成功打开后又消失时拒绝继续清理;在 runtime rollover 时仍从产生响应的原始 runtime 完成清理;中止已断连请求的列表扫描;并将格式错误的 standalone 列表 cursor 映射到既有的
400 invalid_cursor契约。协议与实现计划记录了路由所有权、兼容性、响应断连行为、批量操作结果、恢复语义,以及后续 SDK 和 UI 工作的边界。
为什么需要
PR2B 已建立持久化 standalone transcript、lineage、runtime 和托管目录基础能力,但客户端仍缺少不携带 workspace selector 即可创建和管理这些 session 的稳定 daemon 接口。本 PR 完成 daemon 层契约,同时确保 standalone session 与 primary workspace 隔离,并保留现有 LiveTask 兼容性。
Reviewer 测试计划
如何验证
/capabilities包含standalone_sessions_v1;在依赖图不完整的 embed 中启动,确认 feature 与整套路由都不存在。fileCleanupPending报告状态。400 invalid_cursor,而不是内部错误。最新受影响测试集全部通过:Core 1,027 passed、3 skipped,CLI 179 passed,ACP attachment 53 passed。最终提交上的
npm run build、npm run typecheck、npm run lint、格式检查和 diff 检查均通过。连续两轮广泛审查未发现剩余可采纳问题。更广泛的集成测试依赖本地不可用的外部模型凭据和 deferred-tool 环境;本 PR 较早阶段已通过聚焦的真实 daemon standalone 路由集成验证。证据(修改前后)
N/A——仅包含 daemon API、持久化生命周期、测试与文档变更,没有 TUI 或 Web UI 变更。
测试平台
环境(可选)
macOS 26.4.1、Node.js 24.12.0、npm 10.9.8,本地 daemon 与各 package 的 Vitest 测试,无 sandbox。
风险与范围
feat而非大范围refactor,仍需要仓库规则规定的 maintainer-awareness 审查。standalone_sessions_v1与新增路由。关联 Issue
关联 #8908。