feat(workflows): support opt-in background runs - #8303
Conversation
Real-terminal E2E evidenceTested the production bundle in a real macOS Ink/node-pty/xterm terminal with the repository's deterministic fake OpenAI provider. Two consecutive runs returned the background Workflow handle in 51 ms and 45 ms while the subagent request remained deliberately suspended. During that suspension, the same main session answered a second prompt. After release, the model received exactly one Workflow completion containing the completed status and expected result; the PTY bell was observed before that model request, confirming the bell and completion subscribers remain independent. The approval/cancellation path displayed Boundary: the long-running condition used a controlled deferred provider rather than a literal three-minute wait. This evidence exercises the real bundled CLI and terminal path, but not live external-provider authentication or network latency. 真实终端 E2E 证据在 macOS 的真实 Ink/node-pty/xterm 终端中运行 production bundle,并使用仓库内确定性的 fake OpenAI provider。连续两次运行分别在 51 ms 和 45 ms 返回后台 Workflow handle,此时子 Agent 请求仍被受控挂起;挂起期间,同一主会话可以正常回答第二个问题。释放后,模型只收到一次包含 completed 状态和预期结果的 Workflow completion;在该模型请求前 PTY 已观测到 bell,证明 bell 与 completion 两个订阅互不覆盖。 审批/取消路径会显示 边界:长运行条件使用受控 deferred provider 等价覆盖,没有实际等待三分钟。本证据覆盖真实 bundle CLI 与终端链路,但不覆盖外部 provider 的真实认证和网络延迟。 |
|
Thanks for the PR! Template looks good ✓ — all required sections present, bilingual, evidence table, risk/scope, linked issue. Problem: this is a real, observed limitation tied to the roadmap issue #8105 (labelled Direction: aligned. Background execution for long-running orchestration is explicitly on the roadmap ( Size: 270 production lines (18 CLI + 252 core) vs 449 test lines. Cross-package (cli + core). Below the 500-line maintainer-awareness threshold. Test-to-production ratio is healthy (~1.7:1). Approach: scope feels right. The PR adds exactly the minimum plumbing: a detached abort controller, a completion callback slot on the registry, validation gates in the tool, and a queue subscriber in the CLI hook. No drive-by refactors, no unrelated changes. The foreground path is provably untouched (byte-for-byte test). I don't see a materially simpler path — the completion has to flow through something, and reusing the existing notification queue pattern (same as shell/monitor registries) is the obvious choice. Risk: no elevated risk signals — none of the changed files match the high-revert-correlation paths. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 所有必填段落齐全,双语,有证据表格、风险/范围说明和关联 issue。 问题:这是一个真实的、已观测到的限制,关联路线图 issue #8105(标签 方向:对齐。长时间编排的后台执行明确在路线图上( 规模:270 行生产代码(18 CLI + 252 core)vs 449 行测试代码。跨包(cli + core)。低于 500 行维护者关注阈值。测试/生产比健康(约 1.7:1)。 方案:范围合理。PR 恰好添加了最少的管道:分离的 abort controller、registry 上的 completion callback 槽位、tool 中的验证门控、CLI hook 中的队列订阅。无顺手重构,无无关改动。前台路径可证明未变(逐字节测试)。没有看到更简路径——completion 必须通过某种方式传递,复用现有通知队列模式(与 shell/monitor registry 相同)是显而易见的选择。 风险:无升级风险信号——改动文件均未匹配高回滚关联路径。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
Code ReviewIndependent proposal: I would have solved this the same way — detach the abort controller from the caller signal for background runs, add a model-completion callback slot on the registry (separate from the terminal-bell subscriber), validate that a completion channel exists before allowing background mode, and subscribe in the CLI hook using the same notification-queue pattern as shells and monitors. Comparison with the diff: the PR matches this approach closely and adds several edge-case protections I would have wanted:
No critical blockers found. No AGENTS.md violations — the code follows existing patterns (registry callback slots, notification queue, tool validation), ESM conventions, and kebab-case file naming. The comment updates in One observation (non-blocking): the Testing
All non-skipped checks pass. macOS/Windows tests and integration tests were skipped (fork PR — these require maintainer approval to run). The Ubuntu unit suite (which covers the new tests in this PR) passed. Sandboxed verification would settle the remaining behavioural claim: 中文说明代码审查独立方案: 我会用相同方式解决——为后台 run 分离 abort controller(不继承调用者 signal),在 registry 上增加模型 completion callback 槽位(与 terminal-bell 订阅分离),在允许后台模式前验证 completion channel 存在,并在 CLI hook 中使用与 shell/monitor 相同的通知队列模式订阅。 与 diff 对比: PR 与此方案高度一致,并增加了多项边界保护:
未发现关键阻塞问题。无 AGENTS.md 违规——代码遵循现有模式(registry callback 槽位、通知队列、tool 验证)、ESM 规范和 kebab-case 文件命名。 一个观察(非阻塞): 测试所有非跳过检查均通过。macOS/Windows 测试和集成测试被跳过(fork PR——需要维护者批准才能运行)。Ubuntu 单元测试套件(覆盖本 PR 新增测试)通过。 沙盒验证可以解决剩余的行为性声明: — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 4/5 — clean, well-scoped feature that follows established patterns; only reservation is the skipped cross-platform CI (fork PR limitation, not a code concern). This is what a good incremental feature PR looks like. The motivation is concrete (roadmap issue, real user pain), the scope is tight (interactive TUI only, foreground untouched), and the implementation reuses the existing notification-queue pattern instead of inventing a parallel delivery mechanism. The test coverage is thorough — foreground byte-compatibility, turn-signal detachment, preflight cancellation, duplicate registration, XML injection, callback error isolation, and the completion-exactly-once guarantee all have dedicated tests. Going back to my independent proposal: the PR matches it and goes further on edge-case hardening (the duplicate run-ID guard and the preflight abort check are things I would have wanted but might have deferred). I don't see a simpler path that preserves the safety properties. The one thing I'd watch post-merge: the completion callback is a single slot ( CI is settled (all available checks green on 中文说明置信度:4/5 —— 干净、范围合理的功能 PR,遵循既有模式;唯一保留是跨平台 CI 被跳过(fork PR 限制,非代码问题)。 这是一个好的增量功能 PR 应有的样子。动机具体(路线图 issue,真实用户痛点),范围紧凑(仅交互式 TUI,前台不变),实现复用现有通知队列模式而非发明平行投递机制。测试覆盖全面——前台逐字节兼容、turn signal 解耦、预检取消、重复注册、XML 注入、callback 错误隔离、completion 恰好一次保证都有专门测试。 回到我的独立方案:PR 与之匹配并在边界加固上走得更远(重复 run-ID 防护和预检中止检查是我想要但可能推迟的东西)。没有看到更简路径能保留这些安全属性。 合并后需关注:completion callback 是单槽位( CI 已结算( — Qwen Code · qwen3.8-max-preview Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
— qwen3.8-max-preview via Qwen Code /review
| expect.objectContaining({ | ||
| type: SendMessageType.Notification, | ||
| notificationDisplayText: displayText, | ||
| }), |
There was a problem hiding this comment.
[Suggestion] The expect.objectContaining matcher in this new test asserts type and notificationDisplayText but not todoWorkChainId, even though the callback is invoked with { todoWorkChainId: 'workflow-chain' }. A mutation that drops todoWorkChainId: meta.todoWorkChainId from the queue push in useGeminiStream.ts would therefore survive this test green.
Failure scenario: if todoWorkChainId is silently dropped from the queue entry, the queue drain's contiguous-batch split (which groups notifications by todoWorkChainId) would merge completions from unrelated work chains into one batched sendMessageStream call and deliver no chain-routing metadata to the model loop — and this test would still pass.
| expect.objectContaining({ | |
| type: SendMessageType.Notification, | |
| notificationDisplayText: displayText, | |
| }), | |
| expect.objectContaining({ | |
| type: SendMessageType.Notification, | |
| notificationDisplayText: displayText, | |
| todoWorkChainId: 'workflow-chain', | |
| }), |
中文说明
[Suggestion] 这个新增测试里的 expect.objectContaining 匹配器断言了 type 和 notificationDisplayText,但没有断言 todoWorkChainId,尽管 callback 是以 { todoWorkChainId: 'workflow-chain' } 调用的。因此,一个把 useGeminiStream.ts 队列 push 中的 todoWorkChainId: meta.todoWorkChainId 删掉的 mutation 仍能让该测试通过。
失败场景:如果 todoWorkChainId 被静默地从队列条目中丢弃,队列 drain 的连续分批逻辑(按 todoWorkChainId 分组通知)会把来自不同 work chain 的 completion 合并进同一个批量 sendMessageStream 调用,并且不会把 chain 路由元数据传给模型 loop——而本测试仍会通过。
— qwen3.8-max-preview via Qwen Code /review
There was a problem hiding this comment.
已修复:已补充 workflow completion 的 todoWorkChainId 路由断言。\n\n验证:cd packages/cli && npx vitest run src/ui/hooks/useGeminiStream.test.tsx(169 passed)。
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
中文说明
已审查。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
— qwen3.8-max-preview via Qwen Code /review
|
@qwen-code /triage |
|
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: 53 passed · 0 failed · 53 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:53 通过 · 0 失败 · 53 总计 Verification reportPR 8303 —
|
| cell | build | call | dispatch | observable oracle | result |
|---|---|---|---|---|---|
| HEAD background | head | run_in_background: true |
deferred (pending) | execute() resolves inside 1500 ms window |
resolved in 4 ms; status running; isBackgrounded=true; llmContent Workflow started in background.\nRun ID: wf_…\nStatus: running; no live-update callback |
| HEAD background (settle) | head | (resolve dispatch) | resolved | completion callback count | exactly 1; modelText <kind>workflow</kind> + <status>completed</status>; status completed |
| BASE control | base | run_in_background: true (ignored — no such path) |
deferred (pending) | execute() resolves inside 1500 ms window |
did NOT resolve (still pending at 1500 ms) — base only has the foreground path, which awaits settlement |
| HEAD foreground | head | omitted | immediate | llmContent / returnDisplay |
byte-identical to BASE foreground |
| BASE foreground | base | omitted | immediate | llmContent / returnDisplay |
byte-identical to HEAD foreground |
HEAD false vs omitted |
head | run_in_background: false vs omitted |
immediate | full ToolResult deep-equal |
equal (default path unchanged) |
The base control is the load-bearing proof: the same call blocks on base and returns in 4 ms on head. Foreground byte-compat was measured with a fixed resumeFromRunId (wf_1234abcd) and the usage banner skipped, so the random runId / banner cannot introduce noise; the deep-equal held on both llmContent and returnDisplay.
Secondary claims (wire-oracle harnesses)
All harnesses are mock-free w.r.t. the unit under test (real registry / tool / runner / orchestrator); callbacks are real subscribers that record what the registry emits. Witnesses: 02-oracle-registry-gate.png, 03-e2e-cancel-ownership.png.
Completion channel semantics (oracle.mjs A1–A6, e2e-cancel.mjs E1–E3):
- Background
completeandfaileach fire the model completion exactly once; duplicatecomplete/failafter settlement are ignored (one-shot). The terminal-bell subscriber fires independently exactly once. - Background
cancelandabortAll(shutdown) emit no model completion and no bell — matching "cancel sends no success completion" (in fact no completion at all). End-to-end through the runner: a run stopped viaregistry.cancelstayscancelled(not reclassified), sends no completion, and releases its handle. - A background run survives the caller turn's abort (session-owned controller): aborting the caller signal leaves it
running; it later completes and delivers completion exactly once. A run that fails on its own after the caller is gone is classifiedfailed(notcancelled) and emits exactly one failed completion. - Foreground
completefires the bell but no model completion (isBackgrounded=false). - A throwing bell subscriber does not stop completion, and a throwing completion subscriber does not propagate to the call site (channels isolated).
Validation gate (oracle.mjs C1–C4): headless (non-interactive), interactive-without-completion-channel, and ACP/Zed each reject run_in_background: true at build() — before registration (registry stays empty in every rejected case). The gate does not block foreground use in headless (default path unaffected).
XML escaping / sanitization (oracle.mjs B1–B5): a hostile result containing </task-notification><script>alert(1)</script> is escaped so the whole payload contains exactly one </task-notification> close tag (no envelope injection); ANSI/control bytes are stripped from the label; the script body is not leaked into the model payload; a circular (non-JSON) result degrades to a placeholder without throwing.
Duplicate-register guard (oracle.mjs D1–D3): re-registering an active runId throws /already active/; a settled run whose handle is still held is still refused; after releaseHandle the same runId re-registers cleanly. Blast radius is confined: WorkflowRunner.start (workflow-runner.ts:112) is the only production caller of WorkflowRunRegistry.register, foreground starts use fresh random runIds (no collision), and a legitimate foreground resume after release is not broken (D3).
Vacuity / mutation matrix
Witness: 04-mutation-matrix.png. Each mutation is interface-preserving and restored via git checkout afterward (tree confirmed clean); a positive control is quoted beside each so a survivor cannot be misread.
| mutation | suite | result | positive control |
|---|---|---|---|
A: execute() background early-return disabled (if (false && runInBackground)) |
workflow.test.ts background-handle test |
KILLED — target test fails (execute blocks, no immediate handle; times out at the await execution line) |
other 38 workflow tests unaffected by this branch |
B: registry.emitCompletion() disabled (no-op) |
workflow-run-registry.test.ts (full) |
KILLED — 4 completion tests fail (channels-independent, failure-completion, non-JSON degrade, todo-chain routing) | 51 other registry tests still pass, incl. the independent bell test "notification callback fires on complete and fail, not on cancel" |
| CONTROL: no mutation (source as shipped) | registry + workflow suites | GREEN — 94 passed, 0 failed | confirms the mutations, not the harness, caused the reds |
The CLI wiring (useGeminiStream.ts completion subscriber → notification queue) is pinned by its own new test "queues background workflow completions for the model loop", which ran green in the 169-test suite (it asserts setCompletionCallback is registered and that invoking it enqueues displayText and calls sendMessageStream with the modelText + todoWorkChainId).
Findings
None. No blocking or non-blocking defects found. The change is carefully scoped (opt-in, default-preserving), the new model-facing payload is correctly escaped, and the completion/cancel/ownership semantics behave as the description claims under direct, mock-free probing.
Not covered
- Per-commit attribution. The checkout is depth-2 shallow;
git rev-list HEAD^1..HEAD^2returns only5aeeda272(the test commit). The first commit3b0fe3b0("feat(workflows): support opt-in background runs") is beyond the shallow boundary, so the two commits could not be exercised individually. Verification is of the aggregateHEAD^1..HEADdiff. - Windows / Linux real-terminal behavior. Author-flagged
⚠️ . The changed logic (registry / tool / runner) is platform-agnostic and was verified headlessly here; no real TUI was driven on any OS. - Approval bridging end-to-end (Reviewer Test Plan step 5). The registry's approval methods are unchanged by this PR, and the runner still passes
bridgeApprovalEventsto the production dispatch only (runner test "passes the registry approval bridge only to production dispatch" passes). A full subagent-approval-from-a-background-workflow scenario was not driven, as it requires a real subagent dispatch rather than the injected seam. - Author-declared out of scope: pause/resume, restart recovery, remote workflows, tracing; completion delivery uses the in-memory interactive notification queue and process termination cancels (does not persist) the session-owned run.
- Repo-wide gates not re-run: lint / typecheck / full test suite were not re-run; the PR's own CI covers them, and HEAD's
dist/(used throughout this round) was built successfully by CI. (Building the base worktree surfaced type-resolution errors in modules unrelated to this PR —mime/lite,fdir,ignore,ajv— which are an artifact of compiling a bare worktree against the sharednode_modules, not PR defects; the workflow subsystem emitted and ran cleanly.)
Methodology
Environment: the CI verify container (node:22-bookworm), working tree at refs/pull/8303/merge (HEAD = merge commit, HEAD^1 = base tip, HEAD^2 = PR head), npm ci + npm run build already completed at HEAD. Four mock-free harnesses (ab.mjs, oracle.mjs, e2e-cancel.mjs, mutate.mjs, all kept in this directory) drive the compiled dist/ of the real Workflow subsystem; only the agent dispatch is injected, via the seam WorkflowRunnerOptions.dispatch. The base control was built from HEAD^1 in a scratch git worktree (tmp/base-tree) with tsc --build, wired to the installed root node_modules and the lockfile-pinned packages/core/node_modules via symlinks (both unchanged by the PR); it was imported by absolute path and asserted to contain none of the new feature code, so the two cells differ only by the change under test. Targeted gates ran the affected vitest suites from within each package (packages/core, packages/cli). Raw per-harness output lives in logs/; the four evidence/*.png captures are rendered from live runs via scripts/verify-capture.mjs. Assertion totals in assertions.json count the 53 scripted checks authored across the three behavioral harnesses; expected-red mutation/vacuity runs and the vitest gate counts (271 tests) are reported in prose, not as fail.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
wenshao
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
— qwen3.8-max-preview via Qwen Code /review (v0.21.2)
| const callerWasAbortedBeforeStart = options.signal.aborted; | ||
| const controller = runInBackground | ||
| ? createAbortController() |
There was a problem hiding this comment.
[Suggestion] callerWasAbortedBeforeStart is dead logic — it can never be the deciding factor in the catch-block error-classification condition. Failure scenario: no runtime misbehavior, but a concrete maintenance cost — for background runs the pre-start guard (if (runInBackground && options.signal.aborted) throw) fires before this capture, so the variable is always false there; for foreground runs AbortSignal.aborted is monotonic, so whenever this variable is true the adjacent !runInBackground && options.signal.aborted disjunct is also true and subsumes it. A future maintainer changing the abort-classification logic must trace two async preflight operations plus the monotonicity invariant to discover the variable covers no case. Suggested fix (spans two locations — remove the variable here, and simplify the catch condition):
if (
(!runInBackground && options.signal.aborted) ||
entry?.status === 'cancelled'
) {中文说明
callerWasAbortedBeforeStart 是死逻辑——它永远无法成为 catch 块错误分类条件中的决定因素。失败场景:没有运行时错误,但有具体的维护成本——对于后台运行,启动前的守卫(if (runInBackground && options.signal.aborted) throw)在这次捕获之前就会触发,所以该变量在那里恒为 false;对于前台运行,AbortSignal.aborted 是单调的,因此只要该变量为 true,相邻的 !runInBackground && options.signal.aborted 分支也必然为 true 并将其涵盖。未来修改 abort 分类逻辑的维护者必须追踪两个异步 preflight 操作加上单调性不变量,才能发现该变量不覆盖任何情况。建议修复(涉及两处——删除此处的变量,并简化 catch 条件):
if (
(!runInBackground && options.signal.aborted) ||
entry?.status === 'cancelled'
) {— qwen3.8-max-preview via Qwen Code /review (v0.21.2)
|
Released in v0.21.3. |




What this PR does
This adds an opt-in
run_in_backgroundmode to the Workflow tool while keeping foreground execution as the default. An interactive background workflow returns a run ID and current status after startup, remains visible and cancellable in Background Tasks, and reports its final result back to the conversation through a dedicated completion channel.Background workflows are owned by the session instead of the completed parent turn. The existing terminal-bell subscriber remains independent from the model-completion subscriber, and background progress never calls the finished tool invocation's live-output callback.
Why it's needed
Long workflows currently occupy the parent tool turn until every phase finishes, so the user cannot continue the main conversation while orchestration is still running. Detaching only the requested run lets the main session stay usable without changing existing foreground behavior or weakening approval handling.
The first version is deliberately limited to the interactive TUI. Headless and ACP callers reject an explicit background request, and interactive startup also requires a trusted completion channel, preventing silent loss of the final result.
Reviewer Test Plan
How to verify
run_in_background, and confirm the tool waits for completion and returns the same result and display payload as before.run_in_background: truein the interactive TUI, and confirm the tool returns a run ID and running status before the workflow settles.run_in_background: truethrough headless or ACP mode, and confirm it is rejected before registration or dispatch.Evidence (Before & After)
Before: the Workflow tool always waits for the full run, and the main tool turn remains occupied until orchestration settles.
After: the explicit background mode returns immediately, the main session remains usable, the run stays observable and cancellable, and completion is delivered exactly once through the existing external-input queue. Deterministic tests cover foreground byte compatibility, turn-signal detachment, preflight cancellation, session-owned cancellation, approval bridging, callback isolation, XML escaping, and queue delivery. Two consecutive bundled-CLI PTY runs returned the background handle in 51 ms and 45 ms while a deferred subagent remained active; the same session answered another turn, completion reached the model exactly once after the terminal bell, an approval stayed blocked, and stopping that run executed no command and emitted no success completion. The long-running condition used a controlled deferred provider rather than a literal three-minute wait.
Tested on
Environment (optional)
macOS, Node.js >=22, repository fake OpenAI provider, real bundled CLI, Ink/node-pty/xterm PTY harness.
Risk & Scope
run_in_backgrounddefaults tofalse, and omitted or explicit false follows the existing foreground path.Linked Issues
Part of #8105
中文说明
本 PR 做了什么
本 PR 为 Workflow 工具增加可选的
run_in_background模式,同时继续以 foreground 作为默认行为。交互式后台 Workflow 完成启动后会立即返回 run ID 和当前状态,运行期间可在 Background Tasks 中查看和取消,结束后通过独立 completion channel 将最终结果送回当前会话。后台 Workflow 由 session 持有,不再依赖已经结束的父 turn。原有 terminal bell 订阅与模型 completion 订阅相互独立,后台进度也不会再调用已经结束的工具 invocation 的实时输出回调。
为什么需要
当前长 Workflow 会一直占用父工具 turn,直到所有阶段结束,用户无法在编排执行期间继续主会话。仅对显式请求的 run 做后台化,可以让主会话继续使用,同时不改变现有 foreground 行为,也不削弱权限审批。
第一版有意只开放给交互式 TUI。Headless 和 ACP 会在注册前拒绝显式后台请求,交互式启动也必须先存在可信 completion channel,从而避免最终结果被静默丢失。
Reviewer 测试计划
如何验证
run_in_background运行 Workflow,确认工具仍等待完成,并返回与之前相同的结果和展示内容。run_in_background: true,确认 Workflow 尚未完成时工具已经返回 run ID 和 running 状态。run_in_background: true,确认在注册和 dispatch 前明确拒绝。前后证据
改动前:Workflow 工具始终等待整个 run,主工具 turn 会一直占用到编排结束。
改动后:显式后台模式立即返回,主会话可继续使用,run 可观察、可取消,并通过现有 external-input queue 恰好一次地送回 completion。确定性测试覆盖 foreground 逐字兼容、turn signal 解耦、preflight 取消、session owner 取消、审批 bubbling、callback 隔离、XML 转义和队列投递。真实 bundled CLI 连续两次 PTY 运行分别在 51 ms 和 45 ms 返回后台 handle;受控延迟的子 Agent 仍在运行时,同一会话可以继续回答,terminal bell 之后模型只收到一次 completion;审批保持阻塞,停止该 run 后命令未执行,也没有误发成功 completion。长运行条件使用受控 deferred provider 等价覆盖,没有实际等待三分钟。
测试平台
环境
macOS、Node.js >=22、仓库 fake OpenAI provider、真实 bundle CLI、Ink/node-pty/xterm PTY harness。
风险与范围
run_in_background默认false,省略或显式 false 都继续走现有 foreground 路径。关联 Issue
Part of #8105