Merge remote-tracking branch 'origin/master' into fs-overwrite-diff-bound-v2
# Conflicts: # docs/subsystems/lsp.i18n.yaml # packages/fs/fs-local/README.i18n.yaml # packages/fs/fs-local/README.md # packages/fs/fs-local/README.zh.md # packages/fs/fs-local/src/index.ts
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.md
|
||||
2026-07-20-config-hot-reload-resilience.md: 0f15bb0aaacb6e06c416cbe35b44155279497eee
|
||||
2026-07-20-config-hot-reload-resilience.zh.md: 8a185c1915b5247150d8bb1dd5c42d69bd4f2a35
|
||||
2026-07-20-config-hot-reload-resilience.md: 2b98ee73e1c7734da9c0ae6df5e3de1af6a5cbeb
|
||||
2026-07-20-config-hot-reload-resilience.zh.md: 4ddf1f07f4ff5449f3bd91aa2b38f6045eb4e1c6
|
||||
|
||||
@@ -38,4 +38,4 @@ HMR contains live refresh rejection. Its `registerConfig(filename, refresh)` met
|
||||
|
||||
## Testing
|
||||
|
||||
`packages/ui/app-boot/tests/config-reload.spec.ts` boots real temporary Loader/Include trees and covers parse and shape rejection, import-before-dispose, plugin/config restoration, multi-entry rollback, ancestor disablement, overlay convergence, option identity, failed direct-update persistence, and failed programmatic moves. `packages/ui/app-boot/tests/hmr-config.spec.ts` covers existing and missing exact paths, add/change/removal, serialized coalescing, disposal drainage, non-`Error` normalization, failure broadcast, and rejecting-observer containment. `packages/host/webserver/tests/webserver.spec.ts` proves a service-gated startup failure rejects Loader composition with its bind diagnostic, `packages/typert/loader/tests/loader.spec.ts` exercises awaited programmatic removal through a real Loader consumer, and the ACP `pty-tools` snapshot guards concurrent composition from reordering equal-priority prompt sections.
|
||||
`packages/boot/app-boot/tests/config-reload.spec.ts` boots real temporary Loader/Include trees and covers parse and shape rejection, import-before-dispose, plugin/config restoration, multi-entry rollback, ancestor disablement, overlay convergence, option identity, failed direct-update persistence, and failed programmatic moves. `packages/boot/app-boot/tests/hmr-config.spec.ts` covers existing and missing exact paths, add/change/removal, serialized coalescing, disposal drainage, non-`Error` normalization, failure broadcast, and rejecting-observer containment. `packages/host/webserver/tests/webserver.spec.ts` proves a service-gated startup failure rejects Loader composition with its bind diagnostic, `packages/typert/loader/tests/loader.spec.ts` exercises awaited programmatic removal through a real Loader consumer, and the ACP `pty-tools` snapshot guards concurrent composition from reordering equal-priority prompt sections.
|
||||
|
||||
@@ -38,4 +38,4 @@ HMR 收容实时刷新 rejection。其 `registerConfig(filename, refresh)` 方
|
||||
|
||||
## Testing
|
||||
|
||||
`packages/ui/app-boot/tests/config-reload.spec.ts` 启动真实的临时 Loader/Include 树,并覆盖对解析和形状错误的拒绝、先导入再 dispose、插件/配置恢复、多配置项回滚、祖先禁用、overlay 收敛、option 对象身份、失败的直接更新不持久化以及失败的程序化移动。`packages/ui/app-boot/tests/hmr-config.spec.ts` 覆盖现有和缺失的确切路径、添加/变更/移除、串行化合并、dispose 排空、非 `Error` 值的规范化、失败广播以及对发生 rejection 的观察者的收容。`packages/host/webserver/tests/webserver.spec.ts` 证明受服务门控的启动失败会让 Loader 组合以其 bind 诊断 reject;`packages/typert/loader/tests/loader.spec.ts` 则通过真实 Loader 消费方演练可等待的程序化移除;ACP(Agent Client Protocol)的 `pty-tools` 快照会防止并发组合改变同优先级提示词段的顺序。
|
||||
`packages/boot/app-boot/tests/config-reload.spec.ts` 启动真实的临时 Loader/Include 树,并覆盖对解析和形状错误的拒绝、先导入再 dispose、插件/配置恢复、多配置项回滚、祖先禁用、overlay 收敛、option 对象身份、失败的直接更新不持久化以及失败的程序化移动。`packages/boot/app-boot/tests/hmr-config.spec.ts` 覆盖现有和缺失的确切路径、添加/变更/移除、串行化合并、dispose 排空、非 `Error` 值的规范化、失败广播以及对发生 rejection 的观察者的收容。`packages/host/webserver/tests/webserver.spec.ts` 证明受服务门控的启动失败会让 Loader 组合以其 bind 诊断 reject;`packages/typert/loader/tests/loader.spec.ts` 则通过真实 Loader 消费方演练可等待的程序化移除;ACP(Agent Client Protocol)的 `pty-tools` 快照会防止并发组合改变同优先级提示词段的顺序。
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md
|
||||
2026-07-31-fail-loud-releases-the-terminal.md: 2a6e7fcbbdd5d35bcf70dee09fdb9e5592486b78
|
||||
2026-07-31-fail-loud-releases-the-terminal.zh.md: f75c21cf79b241e6714c10ec7df9ac25f3d978b4
|
||||
2026-07-31-fail-loud-releases-the-terminal.md: 5fe812e5fe64a1fab8ba9808d87f3581b6f5b206
|
||||
2026-07-31-fail-loud-releases-the-terminal.zh.md: 64459614dc3376a1c773de72cd36aa455894a03c
|
||||
|
||||
@@ -52,7 +52,7 @@ The guarantee belongs to whichever bin owns the terminal: a surface that grabs t
|
||||
|
||||
## Testing
|
||||
|
||||
`packages/ui/app-boot/tests/app-boot.spec.ts` covers the release contract: the hook is awaited before the exit commits, a rejecting hook still exits 1, a never-settling hook exits after `FAIL_LOUD_RELEASE_TIMEOUT_MS`, and a burst of rejections reports only the first while the release still completes.
|
||||
`packages/boot/app-boot/tests/app-boot.spec.ts` covers the release contract: the hook is awaited before the exit commits, a rejecting hook still exits 1, a never-settling hook exits after `FAIL_LOUD_RELEASE_TIMEOUT_MS`, and a burst of rejections reports only the first while the release still completes.
|
||||
|
||||
Those fake-process tests cannot observe the two failure modes that matter most — process exit code with a real event loop, and terminal state after exit — so the regression lives in `apps/cli/tests/tui-keyless-smoke.e2e.ts`. It boots the shipped tree in a real PTY over `fixtures/tui-invalid-provider.cordis.yml` (a list-shaped `providers`, the mistake users actually make), expects exit 1, and asserts the captured bytes contain both the labelled boot rejection (`dsh: plugin tree failed to load:`) and `ESC[?2004l`. The same case pins the boot path end to end: it caught the [HMR initial-scan boot deadlock](2026-08-03-hmr-initial-scan-boot-deadlock.md) that silently exited 13 with the terminal stranded.
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Loader 并发挂载各个条目,因此条目失败的顺序并不等于启动
|
||||
|
||||
## Testing
|
||||
|
||||
`packages/ui/app-boot/tests/app-boot.spec.ts` 覆盖 release 契约:退出提交前会等待该回调;回调 rejection 时仍退出 1;永不结算的回调会在 `FAIL_LOUD_RELEASE_TIMEOUT_MS` 后退出;以及一连串 rejection 只报告第一个,同时 release 仍能跑完。
|
||||
`packages/boot/app-boot/tests/app-boot.spec.ts` 覆盖 release 契约:退出提交前会等待该回调;回调 rejection 时仍退出 1;永不结算的回调会在 `FAIL_LOUD_RELEASE_TIMEOUT_MS` 后退出;以及一连串 rejection 只报告第一个,同时 release 仍能跑完。
|
||||
|
||||
这些基于假进程的测试无法观测到最关键的两种失败形态——真实事件循环下的进程退出码,以及退出之后的终端状态——因此回归用例放在 `apps/cli/tests/tui-keyless-smoke.e2e.ts`。它在真实 PTY 中以 `fixtures/tui-invalid-provider.cordis.yml`(`providers` 为列表形状,正是用户真实会犯的错误)启动出厂配置树,期望退出码为 1,并断言捕获到的字节流同时包含带标签的启动 rejection(`dsh: plugin tree failed to load:`)与 `ESC[?2004l`。同一用例端到端钉住了启动路径:正是它发现了以 13 静默退出、终端状态被残留的 [HMR 初始扫描启动死锁](2026-08-03-hmr-initial-scan-boot-deadlock.md)。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md
|
||||
2026-08-03-cli-signal-shutdown-escalation.md: 7c9715c37ee57be9fa0f67af0c19f0bfa84845da
|
||||
2026-08-03-cli-signal-shutdown-escalation.zh.md: f3485edc9e453c0b774f442bfce6d678d63f2224
|
||||
2026-08-03-cli-signal-shutdown-escalation.md: c8aac6e2be927bd1f4a445c00e0aaa870b10a465
|
||||
2026-08-03-cli-signal-shutdown-escalation.zh.md: 66fe7062ea35a6cc91dbdd6644088cb086a15439
|
||||
|
||||
@@ -49,4 +49,4 @@ The controller is launcher infrastructure rather than a Cordis plugin: it makes
|
||||
|
||||
`apps/cli/tests/headless-shutdown.e2e.ts` boots the real shipped Web/headless Loader tree in a PTY with a test-only plugin whose disposer announces entry and never settles. The test sends SIGINT after the observation URL, waits for proof that disposal started, sends SIGINT again, and requires exit 130. The source/artifact launch resolver keeps the same regression on both execution planes. This PTY case covers the user-visible process state; no model-output snapshot changes.
|
||||
|
||||
`packages/telemetry/session-telemetry-otel/tests/otel.spec.ts` holds a real OTLP request open after timer export begins and pins that Cordis disposal returns at `shutdownTimeoutMillis`, despite the SDK's `forceFlush()` remaining pending. The collector is then released so the still-observed provider Promise settles cleanly.
|
||||
`packages/session/session-telemetry-otel/tests/otel.spec.ts` holds a real OTLP request open after timer export begins and pins that Cordis disposal returns at `shutdownTimeoutMillis`, despite the SDK's `forceFlush()` remaining pending. The collector is then released so the still-observed provider Promise settles cleanly.
|
||||
|
||||
@@ -49,4 +49,4 @@ headless 对完成的轮次仍以 0 退出,对其他轮次结束原因或 API
|
||||
|
||||
`apps/cli/tests/headless-shutdown.e2e.ts` 在 PTY 中启动真实交付的 Web/headless Loader 插件树,并挂载一个仅用于测试的插件;该插件的 disposer 会声明已经进入清理流程,但永不结算。测试在观察地址出现后发送 SIGINT,等待 dispose 已启动的证据,再次发送 SIGINT,并要求进程以 130 退出。源码/产物启动解析器使两个执行平面都覆盖同一项回归。该 PTY 用例覆盖用户可见的进程状态;模型输出快照没有变化。
|
||||
|
||||
`packages/telemetry/session-telemetry-otel/tests/otel.spec.ts` 在定时器导出开始后保持一条真实 OTLP 请求打开,并固定以下行为:即使 SDK 的 `forceFlush()` 仍待结算,Cordis dispose 也会在 `shutdownTimeoutMillis` 到期时返回。随后测试释放 collector,使仍受观察的提供方 Promise 干净结算。
|
||||
`packages/session/session-telemetry-otel/tests/otel.spec.ts` 在定时器导出开始后保持一条真实 OTLP 请求打开,并固定以下行为:即使 SDK 的 `forceFlush()` 仍待结算,Cordis dispose 也会在 `shutdownTimeoutMillis` 到期时返回。随后测试释放 collector,使仍受观察的提供方 Promise 干净结算。
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md
|
||||
2026-08-03-hmr-initial-scan-boot-deadlock.md: 4b3e259c216d258c321ab06c41225b33ed240d19
|
||||
2026-08-03-hmr-initial-scan-boot-deadlock.zh.md: ce1bc8396ac6e7fb6ecb1647fe2b29cdc788c7e1
|
||||
2026-08-03-hmr-initial-scan-boot-deadlock.md: 1a70fbafc74a1bf7024c675ddad82acefc995b72
|
||||
2026-08-03-hmr-initial-scan-boot-deadlock.zh.md: 5f8d50c4c0103eb2209eafb89cf34233c4c1bc35
|
||||
|
||||
@@ -38,4 +38,4 @@ One latent gap remains: a config edit made during a *failing* initial apply can
|
||||
|
||||
## Testing
|
||||
|
||||
The `dsh` invalid-provider PTY case in `apps/cli/tests/tui-keyless-smoke.e2e.ts` pins the end-to-end contract: exit 1, the labelled `dsh: plugin tree failed to load:` diagnostic naming `$.providers`, and the bracketed-paste reset proving the tree was disposed. Before this fix the same case observed exit 13 with no diagnostic. Reload behavior stays covered by `packages/ui/app-boot/tests/config-reload.spec.ts` and `packages/ui/app-boot/tests/hmr-config.spec.ts`.
|
||||
The `dsh` invalid-provider PTY case in `apps/cli/tests/tui-keyless-smoke.e2e.ts` pins the end-to-end contract: exit 1, the labelled `dsh: plugin tree failed to load:` diagnostic naming `$.providers`, and the bracketed-paste reset proving the tree was disposed. Before this fix the same case observed exit 13 with no diagnostic. Reload behavior stays covered by `packages/boot/app-boot/tests/config-reload.spec.ts` and `packages/boot/app-boot/tests/hmr-config.spec.ts`.
|
||||
|
||||
@@ -38,4 +38,4 @@
|
||||
|
||||
## 测试
|
||||
|
||||
`apps/cli/tests/tui-keyless-smoke.e2e.ts` 中 `dsh` 无效 provider 的 PTY 用例钉住了端到端契约:以 1 退出、带标签的 `dsh: plugin tree failed to load:` 诊断指明 `$.providers`、以及证明整棵树已被释放的 bracketed-paste 复位序列。此修复之前,同一用例观察到的是无诊断的 exit 13。重载行为仍由 `packages/ui/app-boot/tests/config-reload.spec.ts` 与 `packages/ui/app-boot/tests/hmr-config.spec.ts` 覆盖。
|
||||
`apps/cli/tests/tui-keyless-smoke.e2e.ts` 中 `dsh` 无效 provider 的 PTY 用例钉住了端到端契约:以 1 退出、带标签的 `dsh: plugin tree failed to load:` 诊断指明 `$.providers`、以及证明整棵树已被释放的 bracketed-paste 复位序列。此修复之前,同一用例观察到的是无诊断的 exit 13。重载行为仍由 `packages/boot/app-boot/tests/config-reload.spec.ts` 与 `packages/boot/app-boot/tests/hmr-config.spec.ts` 覆盖。
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md
|
||||
2026-08-07-cancel-convergence-wake-latch.md: fe00c78bdfadac0cc6c9c173fd04256f77a22051
|
||||
2026-08-07-cancel-convergence-wake-latch.zh.md: f76ad360e2d8b70f76c6baf53e2fde963a80b6c4
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Latch wake-ups that land in the cancel-convergence window
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-08-07-cancel-convergence-wake-latch.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`Agent.cancel(cause, { keepInbox: true })` returns immediately after firing the abort signal, but the active driver may not have converged to `idle` yet: LLM stream teardown, tool cancellation, and the `turn/end` append all unwind asynchronously after `abort()` returns. A waking send arriving in that window was placed into `next-turn` while `wakeDriver()` returned early on the still-`running` phase, and the exiting driver never replayed the wake — the message stayed parked until another waking send arrived. The same dropped-wake window existed around aborted `runMaintenance` activities. Several tests enshrined the parked behavior ("waits for another wakeup"); the bug broke both `session.cancel` and the `subagent.interrupt` composition path (issue #1838). The owning cancellation and send contracts are the [explicit turn cancellation](../architecture/2026-07-16-explicit-turn-cancellation.md) and [unified send](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md) decisions; the production `keepInbox` consumer is [web stop preserves queue](2026-07-31-web-stop-preserves-queue.md).
|
||||
|
||||
## Decision
|
||||
|
||||
The `running` phase carries a `wakeRequested` latch, mirroring the existing `maintenance` phase field. `wakeDriver()` latches whenever the current activity cannot deliver the wake — a maintenance task never reads the queue, and an aborted activity converges without restarting — while a live driver needs no latch because it claims queued work itself. The exiting activity replays the latch at its own convergence boundary (`kick`'s `finally` and `runMaintenance`'s `finally`): this placement guarantees `turn/end N` lands before the replayed driver opens `turn/start N+1`, and that `whenIdle()` sees the replayed driver through its `activityDone` loop. The replay sites run only while `inbox.hasPending`, so a latched wake removed from the inbox before convergence does not start an empty driver. A wake sent while the agent is already idle keeps its turn boundary even when its message is cleared before the driver claims — that `idle → running → idle` transition is an observable contract: the goal-session driver's pause/disarm fallback fires on the `idle` transition after a cancelled reservation (CI caught this when the guard was moved into `wakeDriver()` and suppressed the boundary). `cancel()` without `keepInbox` clears the latch together with the inbox.
|
||||
|
||||
The `signal.aborted` discriminator is load-bearing: it separates pre-abort queued work — which `keepInbox` parks for a later wake (acceptance criterion 1) — from post-abort explicit wakes, which must run after convergence.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Have `cancel()` set the phase to `idle` immediately.** Rejected: the driver is still unwinding, so this overlaps two drivers. The replay lives in the old driver's `finally`, which then never runs — 14 of 83 tests failed, several deadlocked. Repairing it requires identity-based phase ownership plus a turn-open quiescence barrier, which is strictly more machinery and is the latch in disguise.
|
||||
|
||||
**Latch unconditionally for every non-idle wake.** Rejected: pre-abort wakes would auto-start after a `keepInbox` cancel, violating acceptance criterion 1; the "parks queued work" test and the error-window steering test both failed.
|
||||
|
||||
**Replay through a chained promise (`activityDone.then(...)`).** Rejected: the replay would run outside the activity's own settlement, so `whenIdle()`'s loop can resolve before the replayed driver starts; fixing that requires replacing `activityDone` at send time and depends on microtask reaction ordering — more fragile than a synchronous flag.
|
||||
|
||||
**Wait for quiescence in the subagent adapter.** Rejected by the issue scope: the cancel/wake state machine owns the fix, not a consumer.
|
||||
|
||||
## Consequences
|
||||
|
||||
The `running` phase gains a `wakeRequested` field; `cancel()` without `keepInbox` clears it alongside the inbox, and a `disposed` cancel never latches, so a wake landing after disposal begins stays parked and `whenIdle()` does not wait on a full model turn over the session being torn down. A wake arriving in the sub-microtask gap between the driver's final `hasPending` check and its exit still parks — no latch fires because the phase is `running` and not aborted; closing that gap requires the unconditional latch and is deliberately out of scope. Between the aborted turn and the replayed driver, status transitions emit a transient `idle → running` pair. A waking send whose message is cleared before any driver claims it still opens an empty completed turn, preserving the observable wake boundary.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: 锁存取消收敛窗口内到达的唤醒请求
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-08-07-cancel-convergence-wake-latch.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
`Agent.cancel(cause, { keepInbox: true })` 在触发 abort 信号后立即返回,但活动 driver 可能尚未收敛到 `idle`:LLM 流拆除、工具取消与 `turn/end` 落盘都会在 `abort()` 返回后异步展开。在该窗口内到达的唤醒 send 被放入 `next-turn`,而 `wakeDriver()` 对仍处于 `running` 的 phase 直接返回,退出的 driver 也从不重放这次唤醒——消息会一直停放到下一条唤醒 send 到达。被中止的 `runMaintenance` 活动周围也存在同样的唤醒丢失窗口。多个测试固化了停放行为(「等待下一次唤醒」);该缺陷同时破坏了 `session.cancel` 与 `subagent.interrupt` 组合路径(issue #1838)。拥有取消与发送契约的既有决策是[显式轮次取消](../architecture/2026-07-16-explicit-turn-cancellation.md)与[统一发送](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md);生产环境中的 `keepInbox` 消费方是[Web 停止保留队列](2026-07-31-web-stop-preserves-queue.md)。
|
||||
|
||||
## 决策
|
||||
|
||||
`running` phase 携带 `wakeRequested` 锁存,与既有的 `maintenance` phase 字段对称。`wakeDriver()` 在当前活动无法投递唤醒时锁存——maintenance 任务从不读取队列,被中止的活动收敛后不会重启——而存活的 driver 不需要锁存,因为它自己会认领排队的工作。退出中的活动在其自身收敛边界(`kick` 的 `finally` 与 `runMaintenance` 的 `finally`)重放锁存:这一位置保证 `turn/end N` 先于重放 driver 打开 `turn/start N+1` 落盘,并保证 `whenIdle()` 通过其 `activityDone` 循环看到重放 driver。两个重放点仅在 `inbox.hasPending` 时执行,因此收敛前被从 inbox 移除的锁存唤醒不会启动空 driver。而 agent 已处于 idle 时发送的唤醒,即使消息在 driver 认领前被清除,仍会打开自己的 turn 边界——这趟 `idle → running → idle` 转换是可观察契约:goal-session driver 的 pause/disarm 回退依赖取消预订后的 `idle` 转换触发(把守卫放进 `wakeDriver()` 后该边界被抑制,CI 发现了这一点)。不带 `keepInbox` 的 `cancel()` 会连同 inbox 一起清除锁存。
|
||||
|
||||
`signal.aborted` 判别项是承重的:它区分「中断前已排队的工作」——`keepInbox` 将其停放以待后续唤醒(验收条件 1)——与「abort 后显式的唤醒」,后者必须在收敛后执行。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**让 `cancel()` 立即把 phase 置为 `idle`。** 不予采用:driver 仍在展开收尾,这会重叠两个 driver。重放逻辑位于旧 driver 的 `finally`,而该 `finally` 此后不再执行——83 个测试中有 14 个失败,多个死锁。修复它需要基于身份的 phase 所有权外加 turn 打开时的 quiescence 屏障,机制上严格更重,而且该屏障就是换了个形态的锁存。
|
||||
|
||||
**对每个非 idle 唤醒无条件锁存。** 不予采用:中断前的唤醒会在 `keepInbox` 取消后自动启动,违反验收条件 1;「停放排队工作」测试与错误窗口的 steering 测试双双失败。
|
||||
|
||||
**通过链式 promise(`activityDone.then(...)`)重放。** 不予采用:重放会运行在活动自身结算之外,`whenIdle()` 的循环可能在重放 driver 启动前就 resolve;修复它需要在 send 时同步替换 `activityDone`,并依赖微任务反应顺序——比同步 flag 更脆弱。
|
||||
|
||||
**在 subagent adapter 中等待 quiescence。** 被 issue 范围否决:修复由取消/唤醒状态机拥有,而不是消费方。
|
||||
|
||||
## 影响
|
||||
|
||||
`running` phase 新增 `wakeRequested` 字段;不带 `keepInbox` 的 `cancel()` 会连同 inbox 一起清除它,且 `disposed` 取消从不锁存——dispose 开始后到达的唤醒保持停放,`whenIdle()` 不会在拆除中的会话上等待一个完整模型 turn。落在 driver 最后一次 `hasPending` 检查与退出之间微任务间隙的唤醒仍会停放——没有锁存触发,因为 phase 是 `running` 且未 abort;关闭该间隙需要无条件锁存,刻意留作范围外。在被中止的 turn 与重放 driver 之间,状态转换会发出一次瞬态 `idle → running` 对。唤醒 send 的消息在任何 driver 认领前被清除时,仍会打开一个空的 completed turn,保留可观察的唤醒边界。
|
||||
Reference in New Issue
Block a user