@sema-agent/server 7.43.0 → 7.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,74 +1,20 @@
1
- /**
2
- * MF-Fleet (shell-host data-contract, the dynamic-stream obligation) — the SERVICE-side live fleet
3
- * aggregation bus + state, backing `GET /v1/fleet/stream`.
4
- *
5
- * The fleet view (CC 187 0616_053) animates a MULTI-ROW tree: every active task's `status/elapsedMs/tokens/
6
- * queuedCount` and every workflow's `doneCount N/M · failedCount` TRANSITION over time. That is a *push* stream,
7
- * not a one-shot read: the backend must emit every transition for as long as a run is live, with STABLE per-row
8
- * identity so the UI updates the right row in place.
9
- *
10
- * This bus is the in-process aggregator: the run lifecycle (createRun → queued/running, per-turn token accrual,
11
- * terminal → completed/failed) and the workflow lifecycle (WorkflowRun put/update → doneCount/status) publish
12
- * their per-row deltas here; `GET /v1/fleet/stream` subscribes, sends a SNAPSHOT on connect, then forwards every
13
- * delta. Identity: tasks keyed by `id` (the durable runId/taskId), workflows by their workflow runId, subagent
14
- * sub-rows (round 5, pending core's `task_progress` arm) by `parentId`+agent id (E2 `parentToolCallId`).
15
- *
16
- * 🔒 boundary (design/99 §0): the FLEET aggregation + the SSE wire is **service** (UI/deployment-state view over
17
- * the runs ledger + workflow store). The engine semantics (a run's own events, a subagent's usage ticks) are
18
- * core's — the bus only AGGREGATES what the lifecycle already produces. It carries NO new engine state.
19
- *
20
- * NOT durable / NOT cross-replica: a live fleet is this replica's active runs (mirrors `steerableRuns` —
21
- * replica-local). A cross-replica fleet roll-up is a fleet-token-gated trace-API concern (separate), not this.
22
- */
23
1
  import { EventEmitter } from "node:events";
24
2
  import { deriveAgentDisplayStatus, isDelegatedAgentTerminal } from "@sema-agent/core";
25
3
  import { recordFailOpen } from "../observability/fail-open.js";
26
4
  import { redactSecrets } from "../trace/redact.js";
27
- /** [2687-cli] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
28
- * `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
29
- * 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
30
- * agent 形瞬态终帧,壳终态留存池把这帧渲 60s = footer 幽灵子行;monitor 的 `status:"event"` 还被
31
- * 终态映射折成 `failed`。
32
- *
33
- * 🔴 A-033.1(#256):判别**本体已换 core 的 {@link isDelegatedAgentTerminal}**。此前这里是一份手写语义
34
- * 孪生(自己枚举 `completed|failed|killed|cancelled`),头注自陈「与 core 语义同源——core 根导出后换装
35
- * import 删本镜像」。core 现已从包根导出(`index.d.ts:109`),且它的终态集与 pending 店的驱逐偏好共用
36
- * 同一份 `TERMINAL_STATUSES`(core 原话:「so "terminal" can never mean two things in this module」)——
37
- * 孪生留在这里等于让 fleet 面板对「终态」的理解可以和引擎分家。本函数保留为**具名的 fleet 语义位**
38
- * (调用点读的是「这条通知该不该打 fleet 的 onChildTerminal」,不是「core 怎么判终态」)。 */
39
5
  export function isFleetAgentTerminalNotification(n) {
40
6
  return isDelegatedAgentTerminal(n);
41
7
  }
42
- /**
43
- * durable {@link WorkflowRun} → 一条 fleet workflow 行的**纯投影**(`build*`=纯数据,无行为、不发布)。
44
- *
45
- * 从 `JournalingWorkflowRunStore.publishFleet` **原样**抽出,因为它现在有两个调用点,而两处各写一遍
46
- * 必然漂移(本仓在 startedCount 口径上已吃过同款):
47
- * ① 活写路径 —— put/update 的写观察点(行的唯一写者,见该类注);
48
- * ② `/v1/fleet/stream` 连接时快照的**有界终态行窗**(#189 修方向 1)—— 引擎重启后 boot 扫描把前世
49
- * running run 判死,终帧与撤行在同一同步栈内背靠背发出,之后才连上的客户端连快照都看不见那一行。
50
- */
51
8
  export function buildFleetWorkflowRow(id, run) {
52
9
  const agents = run.agents ?? [];
53
- // [2336] doneCount 与 failedCount **不相交**:done 只数 completed。契约以此为前提(本文件 startedCount
54
- // 注的回退式 done+failed ≤ started);把 failed 也计进 done 会让全失败 workflow 的终帧渲成 "N done"
55
- // (cli 4.1.3 实测 done=2 failed=2 started=2)。
56
10
  const done = agents.filter((a) => a.status === "completed").length;
57
11
  const failed = agents.filter((a) => a.status === "failed").length;
58
- // cli [1726] 二①:CC 规模告警的分母是 **started**(已启动),而 `totalCount`(= agents.length)是**计划总数**
59
- // (含排队中)。判别口径走 core 导出的 `deriveAgentDisplayStatus` —— 一个 agent 已在 `run.agents` 里但
60
- // `startedAt` 未置就是排队中。**不自己写 `startedAt !== undefined`**:那是 core 的显示契约,两端各写一遍
61
- // 必然漂移,用它的函数则 core 一改、两端同时跟随。
62
12
  const started = agents.filter((a) => deriveAgentDisplayStatus(a, run.status) !== "queued").length;
63
13
  return {
64
14
  id,
65
- // [WF2-A parity] redact the workflow label surfaces for parity with the run + subagent-child names (below):
66
- // a tool-launched (LLM-authored) workflow's meta.name/description is task-controlled and could carry a secret shape.
67
15
  name: redactSecrets(run.name ?? "Dynamic workflow"),
68
16
  ...(run.description ? { description: redactSecrets(run.description) } : {}),
69
17
  scope: run.scope,
70
- // codex-6 F2:sessionId 必须随行——streamFleet 对无 sessionId 的行按「同 principal 全会话可见」
71
- // 兜底,漏发=A 会话的 workflow 名/进度/token 泄进 B 会话的 ?session= 过滤流。
72
18
  ...(run.originatingSessionId ? { sessionId: run.originatingSessionId } : {}),
73
19
  status: run.status,
74
20
  doneCount: done,
@@ -76,86 +22,45 @@ export function buildFleetWorkflowRow(id, run) {
76
22
  failedCount: failed,
77
23
  startedCount: started,
78
24
  tokens: (run.stats?.tokens ?? 0) + (run.stats?.nested?.tokens ?? 0),
79
- // [1294]:跑动中也带时长(1.232 只在 endedAt 后带——clay 验收轮实锚面板恒显 0s)。终态用
80
- // endedAt 定格,活跑用 now-startedAt(每次 put/update 观察点刷新,壳侧读帧即当前时长)。
81
25
  elapsedMs: (run.endedAt ?? Date.now()) - run.startedAt,
82
26
  };
83
27
  }
84
- /**
85
- * The process-local fleet aggregation bus. Holds the current active set + fans out deltas to SSE subscribers.
86
- * Upserts MERGE (a partial delta patches the existing row), so a publisher can emit just the field that changed
87
- * (e.g. only `tokens`) and the row stays whole. Removal drops the row (terminal). Subscribers get a snapshot of
88
- * the current set immediately, then every subsequent delta.
89
- */
90
28
  export class FleetEventBus {
91
29
  now;
92
30
  emitter = new EventEmitter();
93
31
  tasks = new Map();
94
32
  workflows = new Map();
95
- /** #261 / codex R1-F2:**代际墓碑** —— 已离场行的最后已知 `cycleSeq`(行本身已从 `tasks` 删掉,
96
- * 代际状态却必须活得比行久一点,否则前代迟到帧会在空 Map 上把幽灵行重铸出来)。有界 LRU。 */
97
33
  retiredGen = new Map();
98
34
  static RETIRED_GEN_MAX = 4096;
99
35
  constructor(now = () => Date.now()) {
100
36
  this.now = now;
101
- this.emitter.setMaxListeners(0); // unbounded SSE subscribers (each /v1/fleet/stream connection)
37
+ this.emitter.setMaxListeners(0);
102
38
  }
103
- /** Upsert a task row (MERGE onto any existing row by `id`) + fan out the merged row. Publishers may send a
104
- * partial `{ id, tokens }` etc.; the row keeps its other fields. */
105
39
  publishTask(delta) {
106
40
  const existing = this.tasks.get(delta.id);
107
- // 🔴 #261 §2 判代闸(**merge 前判**):同 id 上一个**更小**代际的帧 = 前代迟到帧 ⇒ 整帧忽略,
108
- // 既不 merge 也不扇出。为什么必须在 merge 之前:MERGE 语义下一个迟到的前代 delta 会把行内累计量
109
- // (tokens / elapsedMs / toolUses)**回退**到上一代的读数,而回退过的行再也没人会修正它 ——
110
- // 消费端看到的是一条"复活之后 token 越跑越少"的行,且无从判别。判据只在**两边都有**代际号时生效:
111
- // 缺席 = 「无此概念」(顶层 run 行恒缺席),不参与比较,行为逐字回落到本闸之前。
112
41
  if (delta.cycleSeq !== undefined && existing?.cycleSeq !== undefined && delta.cycleSeq < existing.cycleSeq)
113
42
  return;
114
- // 🔴 同一条判据在**行已离场**之后的半场(codex 对抗复审 R1-F2 [high],红先复现)。上面那条闸只跟
115
- // **内存行**比对,而 `removeTask` 顺手把行连同它的代际状态一起删了 —— 于是 `retire(N)` 之后一条
116
- // `frame(N)` 会在空 Map 上把行**重新铸出来**,而那条行的发布方恰恰是已经死了(那就是我们退它的理由),
117
- // 面板上于是又立起一条永不离场的幽灵行。墓碑补上这一半:行缺席时与**被退那一代**比,不前进的帧一律
118
- // 不铸;更大代际(真复活)照常出生。
119
- // ⚠️ 例外恰好一个:带 `retiredBy` 的帧**就是**对账腿对"被退那一代"的结算(行已不在 bus,故它必须
120
- // 先瞬时重铸再撤 —— 见 fleet-reconciler.ts 的候补窗 flip-through)。它与墓碑说的是**同一代**,拦掉
121
- // 它等于让"陈述级退场 → 后来 durable 翻终局 → 补发成对终帧"这条设计承诺静默失效(红先复现)。
122
43
  if (existing === undefined && delta.retiredBy === undefined) {
123
44
  const tomb = this.retiredGen.get(delta.id);
124
45
  if (tomb !== undefined && delta.cycleSeq !== undefined && delta.cycleSeq <= tomb)
125
46
  return;
126
47
  }
127
- // [2070]③:懒铸底座**不带 name** —— 一条只有 `{id,…}` 的首帧此前会把行 id 铸成显示名,而这里
128
- // 恰恰是「谁都没给过名字」的位置。真名后到时照常 MERGE 上行(见本文件末的 [2070]③ 第 4 条钉)。
129
48
  const merged = { ...(existing ?? { id: delta.id, status: "running" }), ...delta };
130
49
  this.tasks.set(delta.id, merged);
131
50
  this.emit({ type: "task", row: merged, ts: this.now() });
132
51
  }
133
- /** 推一帧 hook 判定未完成的观测通知(事件语义,不进 snapshot —— 与 `bg_notification` 同族)。 */
134
52
  publishHookNotice(notice) {
135
53
  this.emit({ type: "hook_notice", notice, ts: this.now() });
136
54
  }
137
- /** Drop a task row (terminal + swept) + fan out the removal. Idempotent (a no-op if already gone).
138
- *
139
- * `opts`(#261,两键均 ADDITIVE):`removeReason:"orphaned"` = **陈述级退场**(没有结算语义,见
140
- * {@link FleetFrame} 的 `task_remove` 臂);`cycleSeq` = 被退那一代的代际号。发布方的常规清场
141
- * (`onTerminal`/`onChildTerminal`)不传 opts ⇒ 帧逐字节与本改动之前相同。 */
142
55
  removeTask(id, opts) {
143
56
  const existing = this.tasks.get(id);
144
- // 🔴 **代际条件删**(codex 对抗复审 R1-F2 反向臂 [high],红先复现):调用方拿的是一份快照,而它
145
- // 在 await 里等 durable 读的那几十毫秒里,同一 id 完全可能已经**复活成新一代**(BCE revive spawn /
146
- // run-leg 新腿)。无条件删就是拿过期快照把一条**活着的新行**从面板上摘掉 —— 而顶层 run 行没有代际号,
147
- // 那一格由调用方的 await-后复核兜(见 fleet-reconciler.ts);有代际号的子代行由这条闸兜。
148
- // 请求方没带代际(发布方的常规清场:它就是行的属主,不存在"拿旧快照删新行")⇒ 照旧无条件删。
149
57
  if (existing !== undefined && opts?.cycleSeq !== undefined && existing.cycleSeq !== undefined && existing.cycleSeq > opts.cycleSeq)
150
58
  return;
151
59
  if (this.tasks.delete(id)) {
152
- // 代际墓碑:记下**被退那一代**(优先取行上的真值,退化取请求值)。见 publishTask 的墓碑臂。
153
60
  const gen = existing?.cycleSeq ?? opts?.cycleSeq;
154
61
  if (gen !== undefined) {
155
62
  this.retiredGen.delete(id);
156
63
  this.retiredGen.set(id, gen);
157
- // 容量兜底:逐最老。丢一条墓碑 = 那个 id 退回本闸之前的行为(迟到帧可重铸,下一个对账周期再退它一次)
158
- // —— 可见性回退,不是错行,与 bceClaims 同一条取舍。
159
64
  if (this.retiredGen.size > FleetEventBus.RETIRED_GEN_MAX) {
160
65
  const oldest = this.retiredGen.keys().next();
161
66
  if (!oldest.done)
@@ -171,81 +76,54 @@ export class FleetEventBus {
171
76
  });
172
77
  }
173
78
  }
174
- // ┌─ [2070]①/[2080] 让位登记簿(BCE 车道 → run-leg 车道的单向让位信号)────────────────────────────┐
175
- // 为什么登记簿而不是"查快照有没有 a* 行":两条车道用**不同 id 域**给同一个 agent 建行(BCE=`a*`/`wa*`
176
- // 稳定 handle;run-leg=`${runId} ${childUuid}` 复合键),快照里没有可直接比对的键。join 键 = core 在
177
- // BCE 帧上给的 `progressTaskId`(= run-leg tick 的 `taskId`,即子代 uuid),[2080] 明确它已在 tick/terminal
178
- // 帧上。BCE 车道学到 uuid 即 claim,terminal 即 release;run-leg 车道 claim 在场就让位不铸。
179
- // 单向 + 只增可见性:claim 丢了(容量逐出/旧 core 不发 progressTaskId)= 退回双生行的旧行为,不会错杀。
180
- bceClaims = new Map(); // `${scope}\u0000${childUuid}` → a* 行 id
79
+ bceClaims = new Map();
181
80
  static BCE_CLAIM_MAX = 4096;
182
81
  static bceKey(scope, childUuid) { return `${scope ?? "default"}\u0000${childUuid}`; }
183
- /** BCE 车道:本车道已为该子代 uuid 建了真行 —— run-leg 车道自此让位。幂等。 */
184
82
  claimBackgroundChildLane(scope, childUuid, rowId) {
185
83
  const k = FleetEventBus.bceKey(scope, childUuid);
186
84
  this.bceClaims.delete(k);
187
85
  this.bceClaims.set(k, rowId);
188
- // 容量兜底:逐最老。丢一条 claim 只是让那个 agent 退回"双生行"旧行为(可见性回退,不是错行),
189
- // 所以这里可以简单逐出——与 Meta 表"活溯源绝不逐出"的取舍不同,因为丢 claim 无归因损失。
190
86
  if (this.bceClaims.size > FleetEventBus.BCE_CLAIM_MAX) {
191
87
  const oldest = this.bceClaims.keys().next();
192
88
  if (!oldest.done)
193
89
  this.bceClaims.delete(oldest.value);
194
90
  }
195
91
  }
196
- /** BCE 车道:该子代已终态离场 —— 让位解除(后续同 uuid 的迟到 tick 回到旧行为)。幂等。 */
197
92
  releaseBackgroundChildLane(scope, childUuid) {
198
93
  this.bceClaims.delete(FleetEventBus.bceKey(scope, childUuid));
199
94
  }
200
- /** run-leg 车道:该子代是否已有 BCE 真行(有 ⇒ 让位,不铸复合 id 行)。 */
201
95
  backgroundChildLaneRow(scope, childUuid) {
202
96
  return this.bceClaims.get(FleetEventBus.bceKey(scope, childUuid));
203
97
  }
204
- /** [2268] 让位迁移料的读面:迁移「只补缺席位」需要看得见存活行现有什么(BCE 是权威车道,不覆盖)。 */
205
98
  taskRow(id) {
206
99
  return this.tasks.get(id);
207
100
  }
208
- // └────────────────────────────────────────────────────────────────────────────────────────────────┘
209
- /** Upsert a workflow row (MERGE by `id`) + fan out. */
210
101
  publishWorkflow(delta) {
211
102
  const merged = { ...(this.workflows.get(delta.id) ?? { id: delta.id, status: "running" }), ...delta };
212
103
  this.workflows.set(delta.id, merged);
213
104
  this.emit({ type: "workflow", row: merged, ts: this.now() });
214
105
  }
215
- /** Drop a workflow row + fan out the removal. Idempotent. */
216
106
  removeWorkflow(id) {
217
107
  if (this.workflows.delete(id))
218
108
  this.emit({ type: "workflow_remove", id, ts: this.now() });
219
109
  }
220
- /** design/129-B 缺口②: fan out a background-child completion notification (EVENT — not stored, never in the
221
- * snapshot; a subscriber that connects later reads the durable inbox instead). */
222
110
  publishBgNotification(notification) {
223
111
  this.emit({ type: "bg_notification", notification, ts: this.now() });
224
112
  }
225
- /** The current full state (the connect-time snapshot frame). */
226
113
  snapshot() {
227
114
  return { type: "snapshot", tasks: [...this.tasks.values()], workflows: [...this.workflows.values()], ts: this.now() };
228
115
  }
229
- /** Subscribe to deltas. Returns an unsubscribe thunk. The caller typically sends `snapshot()` first, then every
230
- * delta this delivers — the snapshot+delta sequence is the live fleet view.
231
- *
232
- * 每个回调各自隔离(#183 复审 R3 HIGH):扇出是同步的,修前一个抛错的订阅方会把异常传回**发布方**——
233
- * 而发布方是 JournalingWorkflowRunStore.put/update 的投影点,core 持久化对 put 抛错 catch{} 且不推进
234
- * storeRev ⇒ durable 行冻在 running 而 notify 已 ack,无恢复路径。投影消费方永远不该能楔死 durable
235
- * 写路;抛错记 {@link recordFailOpen}(F 类,登记闸),不上抛、不影响其余订阅方。 */
236
116
  subscribe(cb) {
237
117
  const isolated = (frame) => {
238
118
  try {
239
119
  cb(frame);
240
120
  }
241
121
  catch (err) {
242
- // detail 构造自身也包住:抛出值若带会抛错的 toString,String() 本身会抛并逃出隔离层(fresh 复审观察项)。
243
122
  let detail = "subscriber threw a value whose String() also threw";
244
123
  try {
245
124
  detail = String(err);
246
125
  }
247
126
  catch {
248
- /* keep the fallback detail */
249
127
  }
250
128
  recordFailOpen("server.fleet.subscriber-callback-threw", detail);
251
129
  }
@@ -253,7 +131,6 @@ export class FleetEventBus {
253
131
  this.emitter.on("frame", isolated);
254
132
  return () => this.emitter.off("frame", isolated);
255
133
  }
256
- /** Live counts (test/observability). */
257
134
  get size() {
258
135
  return { tasks: this.tasks.size, workflows: this.workflows.size };
259
136
  }
@@ -261,75 +138,33 @@ export class FleetEventBus {
261
138
  this.emitter.emit("frame", frame);
262
139
  }
263
140
  }
264
- /** A run-scoped lifecycle publisher — wire it into a run's TaskEvent loop so the fleet row TRANSITIONS with the
265
- * run. `onStart()` adds the top-level row (running); `onEvent(ev)` accrues tokens (turn_end usage) + adds/updates
266
- * subagent CHILD rows (core `task_progress`, keyed by its `taskId`, nested under the run via `parentId`);
267
- * `onTerminal(status)` flips the row terminal + removes it (and its children) from the active set. No-op when no
268
- * bus is wired. The token accrual is CUMULATIVE across the run's turns (turn_end carries a per-turn delta). */
269
141
  export function fleetRunPublisher(bus, run) {
270
142
  if (!bus)
271
143
  return { onStart: () => undefined, onEvent: () => undefined, onForwardEvent: () => undefined, onChildTerminal: () => false, onTerminal: () => undefined };
272
144
  let tokens = 0;
273
145
  const startedAt = Date.now();
274
- // #3 fleet per-session scope (clay 2026-06-30): tag every row of THIS run with its rootTaskId (= sessionId) so
275
- // /v1/fleet/stream can filter to the caller's CURRENT session (a TUI footer no longer shows other sessions of the
276
- // same principal). publishTask MERGEs, so tagging the CREATE frame suffices — status/token updates keep the field.
277
146
  const sessionField = run.rootTaskId ? { sessionId: run.rootTaskId } : {};
278
- const children = new Set(); // subagent child row ids (removed with the run)
279
- const childStartedAt = new Map(); // [1332]④:child 行时长轴(首 tick 起表)
280
- // codex R2:spawning tool-call id → cid。core 的失败/killed 收尾通知不带 sessionId(成功路径才有条件带),
281
- // 此时 task_id(a*)解不到 uuid 键的 tick 行——但 tick 的 ident() 带 parentToolCallId,通知带同源 toolUseId,
282
- // 这是两 id 域之外唯一贯穿 spawn→terminal 的关联轴。一次 Task 调用只 spawn 一个 child,键不撞。
147
+ const children = new Set();
148
+ const childStartedAt = new Map();
283
149
  const childByToolCall = new Map();
284
- // Once the leg SETTLED, a LATE subagent tick must not resurrect a child
285
- // row — the settle's removeTask IS the shell's release signal (auto-exit watches the evict); a post-settle
286
- // re-publish left a zombie "running" row with no terminal/remove ever coming. Freeze all publishes after
287
- // onTerminal (a resume runs on a FRESH publisher — this leg's handle is done).
288
150
  let settledLeg = false;
289
151
  const childId = (taskId) => `${run.runId} ${taskId}`;
290
- /** [1415]③:task_progress.status 值域映射(1.354 settle 终态 tick)——completed/failed 直译,其余(含
291
- * undefined/"running"/suspended 不发的诚实面)恒 running(旧 core 行为逐位不变)。 */
292
152
  const tickStatus = (st) => (st === "completed" ? "completed" : st === "failed" ? "failed" : "running");
293
- /** [1748] 从一拍 usage 里取**累计**工具调用次数。与 `sumTokens` 同样防御式取值(该位在这两处的
294
- * 静态类型是 `{}`)。⚠️ 缺席就返回 undefined —— 调用点据此**不发这个键**(缺席 ≠ 0)。 */
295
153
  const toolUsesOf = (usage) => {
296
154
  const t = usage?.toolUses;
297
155
  return typeof t === "number" ? t : undefined;
298
156
  };
299
- /**
300
- * 🔴 [2070]①/[2080] 让位判据(run-leg 侧半场,server 3.6.0)。
301
- *
302
- * 病灶(cli [2070]① 实测):本车道给**每一条**子代 tick 铸复合 id 行 `${runId} ${childTaskId}`,而
303
- * BCE 车道(`fleetBackgroundChildPublisher`)对同一个 agent 已有 `a*`/`wa*` 稳定 handle 真行 ⇒ 同屏两行
304
- * (424 次采样 180 次 max=2,连续可见窗约 15s)。BCE 侧此前只有**反应式**清扫(首 tick 一次性后缀扫),
305
- * 清完本车道下一拍又铸回来,所以 6 双生帧 : 7 真帧。
306
- *
307
- * [2080] 裁定=**双生臂让位、不铸新 id 域**(join 键 progressTaskId 已在 BCE 帧上)。本函数即让位闸:
308
- * 同 (scope, childTaskId) 已有 BCE claim ⇒ 返回 true,调用点**不发行帧**;并把本车道**先前**可能已铸的
309
- * 复合行清场(claim 可能晚于我们第一拍到达),此后该 agent 单源。
310
- *
311
- * 为什么不是"直接删掉复合行铸造":前台 delegation(同步腿委派的子代)**不过** background-child
312
- * registry ⇒ 永远没有 BCE 真行,让位就等于让这些 agent 从面板消失。那一形保留复合行,并在帧上带
313
- * `sourceLane:"run-leg"` 明示这是过渡形——消费端不必再自己发明集合级去重判据。
314
- */
315
157
  const yieldToBackgroundChildLane = (childTaskId, cid, tickName) => {
316
158
  const bceRowId = bus.backgroundChildLaneRow(run.scope, childTaskId);
317
159
  if (bceRowId === undefined)
318
160
  return false;
319
161
  if (children.has(cid)) {
320
- // 我们早于 claim 铸过一行 —— 清场(BCE 侧的后缀扫是同一意图的反应式兜底,两者幂等叠加无害)
321
162
  bus.removeTask(cid);
322
163
  children.delete(cid);
323
- // ⚠️ childStartedAt 故意不删 —— 它是下面 elapsed 供给的计时基准([2268])
324
164
  for (const [k, v] of childByToolCall)
325
165
  if (v === cid)
326
166
  childByToolCall.delete(k);
327
167
  }
328
- // 🔴 [2268] 迁移料([2269] 认领「迁移」臂):此前让位=删「有名的 run-leg 行」留「无名的 BCE 行」
329
- // ((unnamed)/0s/详情空白三症状同源)。tick 手上的显示料补给存活行,**只补缺席位** —— BCE 是权威
330
- // 车道,它已有的 name/真 elapsed 一律不碰。elapsed 供给是持续的(本闸每拍都过),不是一次性冻结;
331
- // elapsedDonor 记住「这行的 elapsed 是我们供的」,避免把 BCE 自己的真 elapsed 误当缺席重置。
332
- // transcriptId 不在 run-leg tick 手上,迁不了(诚实范围;那半归 BCE 行自身后续 tick)。
333
168
  const row = bus.taskRow(bceRowId);
334
169
  if (row) {
335
170
  const donorName = row.name === undefined && tickName !== undefined ? redactSecrets(tickName) : undefined;
@@ -339,9 +174,6 @@ export function fleetRunPublisher(bus, run) {
339
174
  elapsedDonor.add(cid);
340
175
  const elapsed = elapsedDonor.has(cid) ? Date.now() - childStartedAt.get(cid) : undefined;
341
176
  if (donorName !== undefined || elapsed !== undefined) {
342
- // cli [2297]①:donorName 只进 `name` 位。run-leg tick 手上没有独立的类型名([1364]③/core 1.350:
343
- // name=display 标签、agentType=类型名),把 display 值同时写进 agentType 会让 fleet 的 TYPE 列显示
344
- // description。缺源的键不铸(与「匿名行不铸假名」同一条原则);BCE 行自己的帧带真 agentType 时自然补上。
345
177
  bus.publishTask({
346
178
  id: bceRowId,
347
179
  ...(donorName !== undefined ? { name: donorName } : {}),
@@ -351,24 +183,13 @@ export function fleetRunPublisher(bus, run) {
351
183
  }
352
184
  return true;
353
185
  };
354
- const elapsedDonor = new Set(); // [2268] 我们在为哪些 cid 供 elapsed(见上)
186
+ const elapsedDonor = new Set();
355
187
  const sumTokens = (usage) => {
356
188
  const u = usage;
357
189
  if (!u)
358
190
  return 0;
359
191
  if (typeof u.totalTokens === "number")
360
192
  return u.totalTokens;
361
- // core 3.0.0([2296] 装车单 S2, [2298] R#9 二次复审并入):`turn_end.usage.inputTokens` 的语义在 3.0.0
362
- // **翻转**——此前(core ≤2.x)它是 NORMALIZED TOTAL INPUT *INCLUDING CACHE*(usage-accounting
363
- // promptTokensOf:Anthropic 族 = input+cacheRead+cacheWrite,OpenAI 族已把 cache 折进 input),本函数当时
364
- // 直接 `inputTokens + outputTokens` 就是整轮总量;3.0.0 起 `inputTokens` 恒为 cache-MISS 分量,新必填的
365
- // `totalInputTokens` 才是那个「含 cache 的归一化总量」(旧 inputTokens 语义的正统继承者)。继续只加
366
- // `inputTokens` 会在有 cache 命中时**漏计**(leader 行的展示 token 数偏低)——故总量优先取
367
- // `totalInputTokens`,旧核帧(尚未升级到 3.0.0、该键缺席)回落 `inputTokens`(防御性:本引擎恒 ≥3.0.0
368
- // 时两键并存;异常帧宁少算不 NaN)。Display-only(leader fleet 行的 token 数);billing(costMicroUsd)+
369
- // context/compaction 走 totalTokens,不受影响;child 行走 task_progress.usage.totalTokens(上面的早退臂)。
370
- // [2354] 兼容面全清:旧核帧回落臂(?? u.inputTokens)已删——floor ^4.0.0 下 totalInputTokens 恒在;
371
- // 键缺席=越契约帧,按 0 计(宁少算不借异语义键充数)。
372
193
  return (u.totalInputTokens ?? 0) + (u.outputTokens ?? 0);
373
194
  };
374
195
  return {
@@ -377,111 +198,48 @@ export function fleetRunPublisher(bus, run) {
377
198
  },
378
199
  onEvent(ev) {
379
200
  if (settledLeg)
380
- return; // no post-settle publishes (zombie-row resurrection)
201
+ return;
381
202
  if (ev.type === "turn_end" && !ev.parentToolCallId) {
382
- // #9 orchestration token attribution (core 1.180 TaskEventIdentity on turn_end): accrue ONLY the LEADER's own
383
- // turns onto the run row. A turn_end WITH a parentToolCallId is an orchestration/subagent SUB-turn (a
384
- // run_workflow agent or a spawned subagent) — its usage belongs to that child's own row / the workflow's
385
- // budget, NOT the leader. Before core threaded the identity onto turn_end (1.121 left it the only boundary
386
- // event without it), the leader row over-counted sub-turn usage on self-orchestration runs.
387
- tokens += sumTokens(ev.usage); // turn_end carries a per-turn usage delta → accumulate
203
+ tokens += sumTokens(ev.usage);
388
204
  bus.publishTask({ id: run.runId, tokens, elapsedMs: Date.now() - startedAt });
389
205
  }
390
206
  else if (ev.type === "task_progress" && ev.taskId) {
391
- // core 1.147 subagent usage tick (per-turn, cumulative) → a CHILD fleet row nested under the run.
392
207
  const cid = childId(ev.taskId);
393
208
  if (yieldToBackgroundChildLane(ev.taskId, cid, ev.name))
394
- return; // [2070]① BCE 真行在场 ⇒ 让位不铸([2268] 随迁显示料)
209
+ return;
395
210
  children.add(cid);
396
- // BC-2 (core 1.151): use the subagent's sanitized display `name` (taskName/agent-type) for the child row.
397
- // [2070]③:name 缺席时**不铸**(此前回落 taskId=子代 uuid,人眼乱码)——行 IDENTITY 一直是 `cid`
398
- // (runId+taskId),显示名与身份是两件事,没有显示名就让客户端渲占位。
399
- // [WF2-A] redact the subagent display name for PARITY with the top-level run name (fleetRunLabels): core
400
- // sanitizes it for RENDERING (ANSI/NUL strip + truncate) but does NOT secret-redact, so a secret-shaped
401
- // taskName would leak verbatim on the child row without this.
402
- // §K-7: `ev.name` IS the subagent TYPE (BC-2: taskName / selected agent-type) → ALSO surface it as
403
- // `agentType` (the picker's short-label field), not only the display `name`. Absent name (anonymous
404
- // subagent) → 两位一起缺席(name/agentType 同源同命)。One redacted value (name === agentType).
405
211
  const childName = ev.name !== undefined ? redactSecrets(ev.name) : undefined;
406
212
  if (!childStartedAt.has(cid))
407
213
  childStartedAt.set(cid, Date.now());
408
214
  if (ev.parentToolCallId)
409
215
  childByToolCall.set(ev.parentToolCallId, cid);
410
- // [1415]③ 点亮半场:core 1.354 起 settle 时补终态 tick(status 值域 additive 扩 completed|failed;
411
- // 此前恒 "running"=行为逐位不变)——workflow 子的 uuid 行终于有完成态可见(running→removed 的
412
- // [1414]#3 形就此闭)。
413
- // #261 §2:`seq` 透传成行的 `cycleSeq`(**缺席不铸** —— 旧 core 不发这一位,而「缺席禁读作 cycle 1」)。
414
216
  bus.publishTask({ id: cid, sourceLane: "run-leg", ...(childName !== undefined ? { name: childName, agentType: childName } : {}), parentId: run.runId, scope: run.scope, ...sessionField, status: tickStatus(ev.status), tokens: sumTokens(ev.usage), ...(typeof ev.seq === "number" ? { cycleSeq: ev.seq } : {}), ...(() => { const t = toolUsesOf(ev.usage); return t !== undefined ? { toolUses: t } : {}; })(), elapsedMs: Date.now() - childStartedAt.get(cid) });
415
217
  }
416
218
  else if (ev.type === "tool_start" && ev.toolName) {
417
- // BC-1 (clay 2026-06-27): the top-level run row's `description` = LIVE ACTIVITY (the tool now running), NOT
418
- // the objective. `name` already carries an objective preview (fleetRunLabels); ALSO putting the objective in
419
- // `description` showed it TWICE (the dup the shell flagged). So `description` tracks the current tool, CC-like.
420
- // Tool NAME only — args are untrusted/redacted, a row label must never leak them. Subagent CHILD rows keep
421
- // their task_progress display name (BC-2); this updates the TOP-LEVEL run row only.
422
- // PascalCase double-belt (core 1.176): canonicalize so a legacy snake CONTROL verb (e.g. a
423
- // pre-1.176 emit / a stray report_blocked) renders as its CC name (ReportBlocked) and the shell's PascalCase
424
- // suppression Set still hides it — never re-leaking a control verb as fleet-row activity.
425
- bus.publishTask({ id: run.runId, description: ev.toolName }); // RAW(5.0.0):live 名恒 canonical
219
+ bus.publishTask({ id: run.runId, description: ev.toolName });
426
220
  }
427
221
  },
428
222
  onForwardEvent(ev) {
429
- // core 1.154 (design/99 nested-subagent live tree): a SUBAGENT's `task_progress` tick forwarded from its
430
- // ISOLATED stream to the top run's opt-in sink (`RunInternals.onForwardEvent`). This is the ONLY path a
431
- // subagent tick reaches the top consumer — the top run's own stream does NOT carry it (the tick lives in the
432
- // child's stream, consumed by the parent's runTask). So this builds the fleet's per-subagent CHILD rows at ANY
433
- // depth: `parentTaskId` names the spawner — the TOP run (= `rootTaskId` → nest under run.runId) or another
434
- // subagent (→ nest under that subagent's row). core sets a child's `parentTaskId` = its spawner's task_progress
435
- // `taskId` (prepare-task.ts), so the tree keys line up. Forwarded ticks are UNTRUSTED display hints (name is
436
- // core-sanitized; we carry NAME + token rollup only, never args).
437
- // 🟡 parentTaskId is NOT validated against known runs (documented as LOW). It needn't be: the
438
- // isolation guarantee is the SCOPE BOUND, not a parentTaskId check — every child row we publish is tagged
439
- // `scope: run.scope` (this run's owner) and id-prefixed with the real `run.runId`. So a tool forging a
440
- // `parentTaskId` can at worst MIS-NEST a row WITHIN this run's own scope view (cosmetic); it can never cross
441
- // into another run / tenant. (core corrected its prepare-task note that had over-promised "deployment
442
- // validates parentTaskId" — the bound is structural here.) Validation would be optional defense-in-depth only.
443
223
  if (settledLeg)
444
- return; // a late forwarded tick must not resurrect a removed child row
224
+ return;
445
225
  if (ev.type !== "task_progress" || !ev.taskId)
446
226
  return;
447
227
  const cid = childId(ev.taskId);
448
228
  if (yieldToBackgroundChildLane(ev.taskId, cid, ev.name))
449
- return; // [2070]① BCE 真行在场 ⇒ 让位不铸([2268] 随迁显示料)
229
+ return;
450
230
  children.add(cid);
451
231
  const parentId = ev.parentTaskId && ev.parentTaskId !== run.rootTaskId ? childId(ev.parentTaskId) : run.runId;
452
- // [WF2-A] redact the forwarded subagent name for parity with the top-level run name (see onEvent above).
453
- // §K-7: ev.name IS the subagent type → also surface it as `agentType` (picker short label). Reuse the value.
454
- // [2070]③ 同 onEvent 臂:无 name ⇒ name/agentType 两位一起缺席(不拿 taskId 冒充显示名)。
455
232
  const fwdName = ev.name !== undefined ? redactSecrets(ev.name) : undefined;
456
233
  if (!childStartedAt.has(cid))
457
234
  childStartedAt.set(cid, Date.now());
458
235
  if (ev.parentToolCallId)
459
236
  childByToolCall.set(ev.parentToolCallId, cid);
460
- bus.publishTask({ id: cid, sourceLane: "run-leg", ...(fwdName !== undefined ? { name: fwdName, agentType: fwdName } : {}), parentId, scope: run.scope, ...sessionField, status: tickStatus(ev.status), tokens: sumTokens(ev.usage), ...(typeof ev.seq === "number" ? { cycleSeq: ev.seq } : {}), ...(() => { const t = toolUsesOf(ev.usage); return t !== undefined ? { toolUses: t } : {}; })(), elapsedMs: Date.now() - childStartedAt.get(cid) }); // [1415]③ 同上;[1748] toolUses 同 bg lane 口径;#261 seq→cycleSeq 缺席不铸
237
+ bus.publishTask({ id: cid, sourceLane: "run-leg", ...(fwdName !== undefined ? { name: fwdName, agentType: fwdName } : {}), parentId, scope: run.scope, ...sessionField, status: tickStatus(ev.status), tokens: sumTokens(ev.usage), ...(typeof ev.seq === "number" ? { cycleSeq: ev.seq } : {}), ...(() => { const t = toolUsesOf(ev.usage); return t !== undefined ? { toolUses: t } : {}; })(), elapsedMs: Date.now() - childStartedAt.get(cid) });
461
238
  },
462
239
  onChildTerminal(taskId, status, altId, toolUseId) {
463
- // A background subagent/bash CHILD settles — task_progress never emits a terminal tick,
464
- // so without this the child row sat "running" until the PARENT leg settled (the shell measured 93s of
465
- // phantom running; its auto-exit release condition never fired). Flip the status so subscribers SEE the
466
- // terminal frame, then remove (mirroring onTerminal's leave-the-fleet posture). Idempotent on an unknown
467
- // child. ⚠️ the notification's task_id is the a* domain while ticks key by the child uuid — a live
468
- // mid-turn flip can therefore miss the tick-built row (flip-through mints the a* row instead); id-domain
469
- // unification is core's half. Post-settle: rows are ALREADY removed (the settle evict is the
470
- // shell's release signal) — a flip-through here would mint a fresh orphan row. No-op.
471
240
  if (settledLeg)
472
241
  return false;
473
- // [1332]①:id 双域候选——tick 行键域(child sessionId uuid)与通知键域(a* task_id)因 core 版本/
474
- // lane 而异,单键押注押错=真行永不落 completed(绿点不回,行滞留 running 到父 settle)。两候选谁
475
- // 有活行翻谁;都没有才按首候选合成瞬态终帧(bash 无 tick 形,原语义)。
476
- // codex R3:killed 保桶(与 a* 车道 fleetBackgroundChildPublisher 的终态映射一致)——操作员/系统
477
- // 取消不得错报为执行失败;同一 child 两车道给订阅者的终态必须同色。
478
242
  const fleetStatus = status === "completed" ? "completed" : status === "killed" || status === "cancelled" ? "killed" : "failed";
479
- // codex F1:两 id 域可能**同时**各有一行(重复/版本偏斜投递,onEvent 与 onForwardEvent 各按一域建)
480
- // ——单选一行会漏掉另一域的行,滞留 running 到父 settle(绿点不回复发)。翻**全部**属本 publisher 的
481
- // 候选行,逐一终帧+离场。
482
- // codex R2:第三候选 = 通知 toolUseId 经 spawn 关联解出的 cid。core 的失败/killed 收尾通知缺 sessionId
483
- // (subagent.js catch 分支两处只带 task_id;成功路径才条件带)——此时前两候选同为 a* 域,tick 建的 uuid
484
- // 行押不中;toolUseId↔tick.parentToolCallId 是同一次 Task 调用的同源 id,把真行找回来。
485
243
  const candidateCids = [childId(taskId)];
486
244
  if (altId !== undefined && altId !== taskId)
487
245
  candidateCids.push(childId(altId));
@@ -490,18 +248,11 @@ export function fleetRunPublisher(bus, run) {
490
248
  candidateCids.push(viaToolCall);
491
249
  const liveCids = candidateCids.filter((c) => children.has(c));
492
250
  const hadRow = liveCids.length > 0;
493
- // 🔴 F1(2026-07-30 复审):无活行时先查 BCE claim——claim 在场 = 该 agent 的全部帧归 BCE 车道
494
- // (run-leg 的 tick 臂已全程让位,children 恒空),终态臂同样让位:合成复合 id 瞬态终帧会让
495
- // sourceLane 文档承诺废除的「同 agent 双 id 域帧」在终态那一拍回潮(且合成帧不带 sourceLane)。
496
- // 返回 true=已被处理(BCE 车道自己发 a* 终帧);无 claim 的 bash 无 tick 形走下方原语义。
497
251
  if (!hadRow) {
498
252
  const claimed = [taskId, altId].some((t) => t !== undefined && bus.backgroundChildLaneRow(run.scope, t) !== undefined);
499
253
  if (claimed)
500
254
  return true;
501
255
  }
502
- // 都没活行 = bash 无 tick 形:按首候选合成瞬态终帧(建即离,诚实且无害)。
503
- // [2070]③:合成帧**不铸 name** —— 这条「瞬态」帧会被壳的终态留存池渲 60s,顶上去的 a\* handle
504
- // 在那 60s 里就是用户看到的名字。父链接/租户/会话三位照发(它们是归属事实,不是显示名)。
505
256
  for (const cid of hadRow ? liveCids : [childId(taskId)]) {
506
257
  const startedAtChild = childStartedAt.get(cid);
507
258
  bus.publishTask({ id: cid, ...(children.has(cid) ? {} : { parentId: run.runId, scope: run.scope, ...sessionField }), status: fleetStatus, ...(startedAtChild !== undefined ? { elapsedMs: Date.now() - startedAtChild } : {}) });
@@ -515,10 +266,9 @@ export function fleetRunPublisher(bus, run) {
515
266
  return hadRow;
516
267
  },
517
268
  onTerminal(status, residuals) {
518
- settledLeg = true; // freeze — nothing publishes on this leg's publisher after the settle
269
+ settledLeg = true;
519
270
  const fleetStatus = runStatusToFleet(status);
520
- // [1839]①/(a) 案:真终态帧带残局键(有源才带,键逐挑不 spread);parked 帧不带(行还活着)。
521
- const terminal = isTerminalFleetStatus(fleetStatus); // A-032 P1-④:removal 集不再手抄(穷举属主谓词)
271
+ const terminal = isTerminalFleetStatus(fleetStatus);
522
272
  const res = terminal && residuals
523
273
  ? {
524
274
  ...(residuals.usage ? { usage: residuals.usage } : {}),
@@ -527,27 +277,17 @@ export function fleetRunPublisher(bus, run) {
527
277
  }
528
278
  : {};
529
279
  bus.publishTask({ id: run.runId, status: fleetStatus, tokens, elapsedMs: Date.now() - startedAt, ...res });
530
- // Subagent CHILD rows leave on ANY settle — terminal OR parked (TOC review #8: a suspended/needs_review run
531
- // keeps its row, but its subagents are NOT running while the parent is parked — their work is checkpointed; a
532
- // task_progress never emits a terminal, so without this the child rows leak forever as stale "running" under a
533
- // parked parent). On resume, fresh task_progress/onForwardEvent re-adds the active children.
534
280
  for (const cid of children)
535
281
  bus.removeTask(cid);
536
282
  children.clear();
537
- childStartedAt.clear(); // codex F2:长寿 bg child 的计时项不得随 publisher 句柄泄漏累积(晚到回调可达期)
538
- childByToolCall.clear(); // codex R2 同理:spawn 关联表随叶 settle 清空
539
- // A truly-terminal run LEAVES the fleet (the shell saw the terminal frame first). A PARKED run
540
- // (suspended→waiting / needs_review→awaiting approval) is still active → its ROW STAYS in the fleet.
283
+ childStartedAt.clear();
284
+ childByToolCall.clear();
541
285
  if (terminal) {
542
286
  bus.removeTask(run.runId);
543
287
  }
544
288
  },
545
289
  };
546
290
  }
547
- /** [1839]①/(a) 案:TaskResult → 终态行帧残局键映射(sync/background/resume 三条腿各自 settle,共用一处
548
- * 映射——键名映射写三遍必漂移)。usage 与 {@link BgNotification.usage} 同形:stats.tokens→totalTokens、
549
- * stats.toolCalls→toolUses(名对齐,消费端一个形状)。errorCode "cancelled" ⇒ stoppedBy:"user"(与
550
- * markStopSource 的归因值同源);transcriptId 顶层 = run 的 sessionId([1840]§二拍)。缺源的键不铸。 */
551
291
  export function fleetRunResiduals(result) {
552
292
  if (!result)
553
293
  return {};
@@ -566,22 +306,6 @@ export function fleetRunResiduals(result) {
566
306
  ...(result.sessionId ? { transcriptId: result.sessionId } : {}),
567
307
  };
568
308
  }
569
- /**
570
- * design/129-B (core 1.240.0) — the PROCESS-LEVEL background-child fleet publisher, wired once
571
- * onto `RunnerDeps.onBackgroundChildEvent`. Unlike {@link fleetRunPublisher} (per-leg: dies at leg settle —
572
- * the root cause: a session-scoped bg child outliving its turn had NO forward path, fleet showed zero
573
- * child frames), this observer lives as long as the process and covers the child's WHOLE lifecycle:
574
- * spawn → row appears the moment the child registers (launch→turn-tail 零帧 fixed: no more waiting for a
575
- * first tick that a slow child never emits inside the turn);
576
- * tick → token rollup onto the row; ALSO best-effort removes the per-leg uuid-keyed TWIN row the leg's
577
- * onForwardEvent built for the same child (`${parentTaskId} ${progressTaskId}` composite — the
578
- * per-leg childId shape), so the child shows ONCE, keyed by its stable a* handle;
579
- * terminal → terminal frame under the a* row id, row leaves the fleet, and the contract
580
- * `bg_notification` frame rides the always-open stream (缺口②'s immediate idle delivery).
581
- * Rows are tagged `scope` (tenant filter) from the registry scope and `sessionId` (the `?session=` filter)
582
- * from the registry owner when session-scoped (owner IS the host sessionId; task-scoped children have no
583
- * session attribution → unscoped, back-compat visible). Names ride the same redactSecrets discipline.
584
- */
585
309
  export function fleetBackgroundChildPublisher(bus, log) {
586
310
  if (!bus)
587
311
  return () => undefined;
@@ -590,7 +314,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
590
314
  log?.(msg, fields);
591
315
  }
592
316
  catch {
593
- /* observability must never fault the observer */
594
317
  }
595
318
  };
596
319
  const meta = new Map();
@@ -600,11 +323,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
600
323
  meta.delete(id);
601
324
  meta.set(id, m);
602
325
  if (meta.size > MAX) {
603
- // codex 两轮([1338]④ 复审):逐出**只打墓碑**(done)。R1:逐最老会把在跑 child 的 spawnedAt 挤掉,
604
- // 下个 tick lazy-mint 重起表=elapsedMs 归零。R2(更重):全活时垫底逐活儿,重生条目 tenantScope 落
605
- // "default"、owner/parent 全失——**跨租户错归因**(scoped 流按 "default" 键还能收到别家 child 的终态
606
- // 通知残料)。活溯源绝不逐出:无墓碑=不逐,活基数由 registry 真并发界定(每条 Meta 极小);病态膨胀
607
- // 打一次 warn 供观测。墓碑仍受 MAX 界(terminal 后条目=墓碑,自然轮换)。
608
326
  let victim;
609
327
  for (const [k, v] of meta) {
610
328
  if (v.done) {
@@ -621,46 +339,18 @@ export function fleetBackgroundChildPublisher(bus, log) {
621
339
  }
622
340
  }
623
341
  };
624
- /**
625
- * [2070]①/[2080] 让位登记(BCE 侧半场)。把「本车道已为这个子代 uuid 建了真行」登记到 bus 上,
626
- * run-leg 车道的 task_progress 臂据此让位(不铸 `${runId} ${uuid}` 复合行)。
627
- *
628
- * uuid 从哪来(两源,先到先得):①`e.sessionId` —— fork 形 spawn 就带(= forkedId);
629
- * ②`e.progressTaskId` —— core 在 tick/terminal 帧上给的 join 键([2080] 逐字确认已在场),普通 bg
630
- * producer 要等首 tick 才现。两者都是**同一个** run-leg tick 的 `taskId`,这就是 join 的依据。
631
- *
632
- * `progressParentTaskId` 判别照 suppressedTwin 同款:只有**本 child 自己**的 tick 才算数——嵌套孙代的
633
- * 帧带的是父的 uuid,拿它 claim 会把父的 run-leg 行误让位掉(父在前台 delegation 形下没有 BCE 真行)。
634
- * 旧 core 不发这两键 ⇒ 无 claim ⇒ 退回双生行旧行为(可见性回退,不错杀)。
635
- */
636
342
  const claimIfKnown = (e, m) => {
637
343
  const own = !e.progressParentTaskId || e.progressParentTaskId === (m.parentTaskId ?? m.parentSessionLink);
638
344
  const uuid = e.sessionId ?? (own ? e.progressTaskId : undefined);
639
345
  if (uuid)
640
346
  bus.claimBackgroundChildLane(m.tenantScope, uuid, e.taskId);
641
347
  };
642
- // [1338]③ 换算臂:core spawn emit 现把 hostSessionId 装进 parentTaskId(会话 id,非行 id)→ 子行
643
- // parentId 指向不存在的行=孤儿伪影(值域修在 core)。server 兜:parentTaskId 不是现存行 id、却恰是
644
- // 某 TOP-LEVEL run 行的 sessionId 时,换算成该 run 行 id。guard 式——core 修后 parentTaskId 直接命中
645
- // 行 id,此臂自然失活;无命中(run 行未起/已离场)保持原值,孤儿顶格渲染,不误挂。
646
- // codex R3(M1):首次**成功**解析即冻结进 Meta.resolvedParentId——逐拍重解会把跨 run 存活的长寿
647
- // child 重挂到同会话后起的 run 行(launch 溯源被静默改写);孤儿(未成功)下拍再试。
648
348
  const resolveParentId = (parentInput, domain, childScope) => {
649
- // codex R5:**每一条**解析分支都同租户校验(r.scope === childScope)——submit 收任意 sessionId,
650
- // 碰撞可让 A 家的孙冻结到 B 家的行(fleet-wide 视图伪嵌套)。跨租户一律按未命中:孤儿顶格,fail-closed。
651
- // codex(1.350 四轮):解析带**域标**——session 域链接(parentSessionLink)跳过行 id 直查与 uuid 别名
652
- // 两分支(会话 id 由调用方任报,与既有 run 行 id 撞值时直查会把孩子冻到无关 run 上),只走 sessionId
653
- // 匹配译臂;task 域三分支全走。
654
349
  const rows = bus.snapshot().tasks;
655
350
  if (domain === "task") {
656
351
  if (rows.some((r) => r.id === parentInput && r.scope === childScope))
657
352
  return { id: parentInput, resolved: true };
658
353
  }
659
- // codex R4(M-B)+ PAIR-REVIEW F-1:嵌套 bg 孙的父链接=父 **agent 的 uuid**——core 1.350+ 真实帧形里
660
- // 该值落 **parentSessionId**(session 域;[1344]② 钳掉了洗域 parentTaskId),所以别名逆查必须对
661
- // **两个域**开放(此前只挂 task 域=1.351 下三臂死代码,PAIR-REVIEW F-1 CONFIRMED)。scope-safe:
662
- // 别名命中恒要求同租户(R5 的禁令针对**行 id 直查**分支——那才是任报值撞行的面;别名表是本
663
- // publisher 自记的 spawn 事实,加租户校验后无跨户面)。
664
354
  for (const [aStar, pm] of meta) {
665
355
  if (pm.aliasUuid === parentInput && pm.tenantScope === childScope) {
666
356
  if (rows.some((r) => r.id === aStar && r.scope === childScope))
@@ -669,20 +359,14 @@ export function fleetBackgroundChildPublisher(bus, log) {
669
359
  }
670
360
  let match;
671
361
  for (const r of rows) {
672
- // codex R7(high):会话译只认**真 run 行**——排除一切本 publisher 记账的 a* child 行(meta 键)。
673
- // session-scoped a* 孤儿行同样带 sessionId、R6 后无 parentId、同 scope,不排除则孤儿 tick 会把
674
- // **自己**(或同门孤儿)选成父并冻结=自环树,晚到的真 run 行永远接不上。
675
362
  if (r.sessionId === parentInput && r.parentId === undefined && r.scope === childScope && !meta.has(r.id))
676
- match = r.id; // 后写覆盖=取最近的同租户 run 行
363
+ match = r.id;
677
364
  }
678
365
  return match !== undefined ? { id: match, resolved: true } : { id: parentInput, resolved: false };
679
366
  };
680
367
  const rowTags = (m) => {
681
- // codex R6:未解析成功=**不发 parentId**(原值只留 Meta 供下拍重试)。发原值就是 [1338]③ 伪影本身
682
- // (悬空 parentId 指向不存在的行),且 fleet-wide 视图上一个撞值的外租户行 id 会在渲染端伪嵌套——
683
- // 行早发孤儿顶格、解析成功后 MERGE 升级补 parentId,两边都诚实。
684
368
  let parentId;
685
- const parentInput = m.parentTaskId ?? m.parentSessionLink; // 1.350 fallback 形:会话域链接同走解析臂(带域标)
369
+ const parentInput = m.parentTaskId ?? m.parentSessionLink;
686
370
  if (parentInput) {
687
371
  if (m.resolvedParentId !== undefined) {
688
372
  parentId = m.resolvedParentId;
@@ -699,8 +383,8 @@ export function fleetBackgroundChildPublisher(bus, log) {
699
383
  ...(m.tenantScope ? { scope: m.tenantScope } : {}),
700
384
  ...(m.hostSessionId ? { sessionId: m.hostSessionId } : {}),
701
385
  ...(parentId !== undefined ? { parentId } : {}),
702
- ...(m.workflowRunId !== undefined ? { workflowRunId: m.workflowRunId } : {}), // [1440] 附注位透传
703
- ...(m.parentToolCallId !== undefined ? { parentToolCallId: m.parentToolCallId } : {}), // P1-2(core 1.432)E2 原值域
386
+ ...(m.workflowRunId !== undefined ? { workflowRunId: m.workflowRunId } : {}),
387
+ ...(m.parentToolCallId !== undefined ? { parentToolCallId: m.parentToolCallId } : {}),
704
388
  ...(m.spawnedAt !== undefined ? { startedAt: m.spawnedAt, elapsedMs: Date.now() - m.spawnedAt } : {}),
705
389
  };
706
390
  };
@@ -713,121 +397,39 @@ export function fleetBackgroundChildPublisher(bus, log) {
713
397
  return u.tokens;
714
398
  return undefined;
715
399
  };
716
- // design/158 A13: the three event-kind branches below are named local functions — same closure scope/capture
717
- // as when they were inline `if (e.kind === "...") { ... }` blocks in the returned dispatcher (behavior
718
- // identical), alongside the already-named helpers above (dlog/remember/resolveParentId/rowTags/sumUsage).
719
400
  const handleSpawn = (e) => {
720
- // PAIR-REVIEW F-3:revive spawn(spawn-after-tombstone)缺父字段时**继承墓碑 Meta 的父链**——
721
- // 上一周期已解析冻结的 parentTaskId/resolvedParentId 不因重建丢失(core 1.351 revive spawn 不回放
722
- // 父链;1.352 回放件到货后此臂自然让位,双保险)。只在新帧零父字段时继承,新值恒赢。
723
401
  const tomb = meta.get(e.taskId);
724
402
  const inheritParent = !e.parentTaskId && !e.parentSessionId && tomb?.done === true;
725
403
  const m = {
726
- // codex 两轮(1.350 适配):core 1.350 起 fallback 形不再发洗域 parentTaskId([1364]②),补偿链接改走
727
- // 声明字段 parentSessionId(/v1/tasks/stream 主车道 spec.taskId 未设=恰此形)。**两域分存**——
728
- // parentTaskId=真任务域(可直出 wire),parentSessionLink=会话域(仅作行嵌套解析输入,绝不冒充
729
- // 任务 id 出 bg_notification)。
730
404
  ...(e.parentTaskId ? { parentTaskId: e.parentTaskId } : {}),
731
405
  ...(!e.parentTaskId && e.parentSessionId ? { parentSessionLink: e.parentSessionId } : {}),
732
406
  ...(inheritParent && tomb?.parentTaskId !== undefined ? { parentTaskId: tomb.parentTaskId } : {}),
733
407
  ...(inheritParent && tomb?.parentSessionLink !== undefined ? { parentSessionLink: tomb.parentSessionLink } : {}),
734
408
  ...(inheritParent && tomb?.resolvedParentId !== undefined ? { resolvedParentId: tomb.resolvedParentId } : {}),
735
- // session-scoped: registry owner IS the host sessionId (design/129 owner key); fork spawns also carry
736
- // the child's uuid sessionId — that is the a*↔uuid cross-system anchor, NOT the host session, so it is not used here.
737
- // [1522] LOW4 root-first。[2378]/[2380] RB-478 到货:三条 revive 发射腿全部无条件带
738
- // `root ?? parent` 链 ⇒ `?? e.owner` 回落臂成纯死臂,删([2370] 补审时显式排除在全清宣称外
739
- // 等的就是这一天)。rootSessionId 缺席=不打 host 标(over-filter 方向,durable drain 兜)。
740
409
  ...(e.sessionScoped && e.owner && e.rootSessionId ? { hostSessionId: e.rootSessionId } : {}),
741
- // 🔴 scope DOMAIN mapping: core's registry scope is `ctx.principal ?? deployment scope` —
742
- // UNDEFINED on a single-user no-principal deployment — while the service fleet domain keys the
743
- // no-principal caller as "default" (`gatedPrincipal ?? "default"`, streamFleet's callerScope). Without
744
- // this map the row/notification carried NO scope and the scoped stream dropped EVERYTHING (fail-closed
745
- // — the "terminal/bg_notification 零帧" repro). Same principal string in both domains when
746
- // a principal exists, so the map is exact.
747
410
  tenantScope: e.scope ?? "default",
748
411
  sessionScoped: e.sessionScoped,
749
- // [2354] 兼容面全清:旧 core 帧「回落收帧时刻」臂已删——floor ^4.0.0 下 startedAt 恒在;
750
- // 缺席=诚实不发(行键 optional,消费端本就容忍缺席)。
751
412
  ...(e.startedAt !== undefined ? { spawnedAt: e.startedAt } : {}),
752
413
  ...(e.rootSessionId ? { rootSessionId: e.rootSessionId } : {}),
753
- ...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}), // P1-2(core 1.432,12 发射点全带)
754
- ...(e.sessionId ? { aliasUuid: e.sessionId } : {}), // codex R4(M-B):uuid↔a* 别名(fork spawn 带 forkedId)
755
- ...(e.workflowRunId ? { workflowRunId: e.workflowRunId } : {}), // [1440] wa* 归属附注
756
- // codex 1.240-R1/R2(H 行半场):wa\* 帧 sessionScoped:false 无 host owner → 行无 sessionId=
757
- // 「absent=同 principal 全会话可见」(bg 兼容语义,对 wa\* 是泄露面)。归属源两级:①**信任源**
758
- // core 帧的 parentSessionId(发起会话,1.350 声明字段——R2:无 runStore 的瞬态 sync 部署没有宿主
759
- // fleet 行,行查 fallback 必空);②宿主行 sessionId 查表(同租户)兜底。
414
+ ...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}),
415
+ ...(e.sessionId ? { aliasUuid: e.sessionId } : {}),
416
+ ...(e.workflowRunId ? { workflowRunId: e.workflowRunId } : {}),
760
417
  ...(e.workflowRunId && !(e.sessionScoped && e.owner)
761
- // [2378]/[2380] RB-478:wa* 三级回落(root→parentSessionId→行查表)收单级——core 三条发射腿
762
- // 已无条件带 root 锚,后两级成死臂;缺席=不打标(over-filter,durable 兜),不再猜。
763
418
  ? (e.rootSessionId ? { hostSessionId: e.rootSessionId } : {})
764
419
  : {}),
765
- // [1493] cross-session isolation: a NON-session-scoped, non-wa* bg child (owner = the run taskId — the
766
- // norm when backgroundScope!="session", i.e. every multi-tenant deployment, and any local path where the
767
- // session isn't adopted at spawn) STILL carries a host session on core's declared parentSessionId
768
- // (subagent.js registerBackgroundAgent). Without promoting it to hostSessionId the row/notification is
769
- // session-UNTAGGED → streamFleet's `?session=` filter fails OPEN → the completion is broadcast (and
770
- // model-injected) into every same-principal shell (clay's dual-shell repro). Promote it so the filter
771
- // narrows precisely. (Session-scoped children already tag via the `owner` arm above; wa* via its own arm
772
- // just above — both are excluded by this condition, so no double-write.) The terminal event does NOT
773
- // repeat parentSessionId (core emits it on spawn only), so this MUST be captured into Meta here at spawn.
774
- //
775
- // codex R1 (nested-agent mis-tag): parentSessionId is core's `ctx.sessionId`, which for a NESTED bg child
776
- // (spawned BY a subagent) is the PARENT AGENT's child-session uuid, NOT the shell's root session. Tagging
777
- // that uuid as hostSessionId would make the outer host shell FAIL the ownerSessionId check (row +
778
- // completion suppressed, durable ack fence keyed wrong). So resolve through the alias table (same reverse
779
- // lookup resolveParentId uses): if some agent Meta's aliasUuid === parentSessionId, this is nested —
780
- // inherit THAT agent's already-resolved hostSessionId (the root shell session), or stay UNTAGGED when the
781
- // parent itself is untagged. Only a TOP-LEVEL bg child (parentSessionId is the shell root, aliased by no
782
- // agent) uses parentSessionId directly. Tenant-scoped like every other alias/parent resolution (R5).
783
- // fable5 R1 (honest residual): a nested child whose parent Meta is ABSENT (restart lazy-mint edge — live
784
- // Meta is never evicted, so within one process the parent always exists at spawn time) is
785
- // INDISTINGUISHABLE from top-level and gets tagged with the given uuid. That is still SAFE: a
786
- // child-session uuid is never any shell's own session, so no scoped subscriber matches it — the failure
787
- // direction is over-filtering (the true owner misses live delivery; durable inbox/stream-open drain
788
- // compensate), never cross-delivery. Root fix = core carrying an explicit root-host-session (or a
789
- // top-level/nested discriminator) on the spawn event — asked as a core seam follow-up ([1493]).
790
- // core 1.367 δ:[1493] fable5-R1 注里请的 root fix **到货**——spawn 帧显式带 rootSessionId
791
- // (固定点语义,嵌套孙代恒=root shell 会话),信任源直用;alias 反查臂降为旧 core 帧回落。
792
- // [2354] 兼容面全清:alias 反查臂(旧 core 帧无 rootSessionId 时经 meta 反查 parentSessionId)已删——
793
- // floor ^4.0.0 下 spawn 帧恒带 rootSessionId(core 1.367 δ 固定点语义);缺席=不打 host 标,
794
- // 失败方向仍是 over-filtering(durable inbox/stream-open drain 兜),永不 cross-delivery。
795
420
  ...(!(e.sessionScoped && e.owner) && !e.workflowRunId && e.rootSessionId
796
421
  ? { hostSessionId: e.rootSessionId }
797
422
  : {}),
798
423
  };
799
424
  remember(e.taskId, m);
800
- // [2070]① 让位登记(spawn 臂):fork 形 spawn 就带 sessionId=子代 uuid;普通 bg producer 的 spawn
801
- // 不带,要等首 tick 的 progressTaskId(下方 handleTick 补 claim)。
802
425
  claimIfKnown(e, m);
803
- // [1364]③:spawn 帧 1.349+ 也带 agentType(类型名)/name——行 name 位保 description(display 标签,
804
- // 历史语义),TYPE 列自 spawn 即亮(此前要等首 tick 自愈)。
805
- // [1827]①(cli):spawn 行也透传 transcriptId(委派 prompt 的映射,详情页运行中即可取件)。
806
- // 现状 core 只在 terminal 事件带此键(三发射点亲核)⇒ 本透传对旧 core 惰性;core 停 spawn/tick
807
- // 源头补上即通,tick 臂(下方 :78x)早已同姿。server 不自行以 sessionId 推导——fork lane 的
808
- // forkTranscriptId 与 sessionId 的等价关系归 core 所有,不在消费端烙死。
809
- // [2400] FLEET-WORKFLOWS-7:[2070]③「名不铸」的第四处——spawn 双缺 description/name 时此前 `?? e.taskId`
810
- // 拿 a* handle 冒充显示名;tick 臂只在 name 在场才发 + 总线 MERGE ⇒ 假名永久留行。缺席不铸,与其余三处同判。
811
426
  const spawnName = e.description ?? e.name;
812
- // #261 §2:core 5.36.0(#258)把 `seq` 从「只在 terminal 帧」放宽到 **spawn/tick 也带**(registry 行
813
- // 的 stop-cycle 代际)。本车道是设计稿点名的第二条供给点 —— 三帧一律透传成行的 `cycleSeq`,**缺席不铸**。
814
427
  bus.publishTask({ id: e.taskId, ...(spawnName !== undefined ? { name: redactSecrets(spawnName) } : {}), ...(e.agentType ? { agentType: redactSecrets(e.agentType) } : {}), ...rowTags(m), status: "running", tokens: 0, ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
815
428
  dlog("bg_child_event", { kind: "spawn", taskId: e.taskId, sessionScoped: e.sessionScoped, scope: m.tenantScope, hostSessionId: m.hostSessionId ?? null, parentTaskId: m.parentTaskId ?? null });
816
429
  };
817
430
  const handleTick = (e, m) => {
818
- // [2070]① 让位登记(tick 臂):普通 bg 形的 uuid 首次可见就在这里。**在**下面任何发布之前 claim,
819
- // 这样即便 run-leg 的 forward 与本 tick 同拍到达,run-leg 也已经能看到让位信号。
820
431
  claimIfKnown(e, m);
821
- // dedup the per-leg uuid-keyed twin ONCE: the leg's onForwardEvent keys the same child's rows by
822
- // `${runId} ${uuid}` and on the /v1/runs path runId === the host canonical taskId (spec.taskId is the
823
- // durable run id). The tick that carries the child's OWN frame has progressParentTaskId === the host
824
- // taskId (a NESTED grandchild carries the child's uuid instead — never suppressed here). A miss is a
825
- // no-op remove (best-effort; worst case = a transient duplicate row until the leg settles, old behavior).
826
432
  if (!m.suppressedTwin && e.progressTaskId && (!e.progressParentTaskId || e.progressParentTaskId === (m.parentTaskId ?? m.parentSessionLink))) {
827
- // The per-leg childId is `${run.runId} ${uuid}` and runId ≠ the core hostTaskId on the
828
- // sync-stream/resume paths (durableTaskId vs canonical sessionId), so a composite-key guess misses there.
829
- // Scan-by-suffix instead: the uuid tail uniquely names the child across every leg's runId prefix. One-shot
830
- // (suppressedTwin) so the O(rows) scan runs once per child.
831
433
  const tail = ` ${e.progressTaskId}`;
832
434
  for (const row of bus.snapshot().tasks) {
833
435
  if (row.id.endsWith(tail))
@@ -835,13 +437,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
835
437
  }
836
438
  m.suppressedTwin = true;
837
439
  }
838
- // codex R8:普通(非 fork)bg producer 的 spawn 不带 sessionId,uuid 别名要等 terminal 才现——嵌套孙
839
- // 在父运行期解析全 miss。但 child 本人的 tick 恒带 progressTaskId=其 uuid(与双生抑制同一判别形),
840
- // 首 tick 即学别名;孙侧 retry-until-success 语义随后一拍自然接上(spawn 路径已带 sessionId 的 fork 形
841
- // 不受影响,??= 只补缺)。
842
- // codex R9:别名习得**即回填**——快孙可能已无后续帧(spawn+tick+terminal 全在父首 tick 前),等孙自己
843
- // retry 就永远孤儿。习得此刻主动扫在场未解析孤儿,替它们把行升级(rowTags 内解析+冻结)。彻底闭窗
844
- // (孙整个生命周期都在别名前走完的瞬态孤儿)在 core:普通形 spawn emit 补 sessionId(fork 形已带)。
845
440
  if (m.aliasUuid === undefined && e.progressTaskId && (!e.progressParentTaskId || e.progressParentTaskId === (m.parentTaskId ?? m.parentSessionLink))) {
846
441
  m.aliasUuid = e.progressTaskId;
847
442
  for (const [cid, cm] of meta) {
@@ -852,33 +447,18 @@ export function fleetBackgroundChildPublisher(bus, log) {
852
447
  }
853
448
  const tokens = sumUsage(e.usage);
854
449
  const name = e.name ? redactSecrets(e.name) : undefined;
855
- // [1364]③ K-7 换位:TYPE 列取 tick 帧的行级 `agentType`(core 1.350 定谳)。[2354] 兼容面全清:
856
- // 「缺省回落 name」旧 core 兼容臂已删——floor ^4.0.0 下 agentType 恒在;缺席=诚实不发(行键 optional)。
857
450
  const agentType = e.agentType ? redactSecrets(e.agentType) : undefined;
858
- // The sink tick carries core's currentAction (its only attachment point today) —
859
- // UNTRUSTED (tool argument head) → redact, same discipline as name. Additive; absent until a tool runs.
860
451
  const currentAction = e.currentAction ? redactSecrets(e.currentAction) : undefined;
861
- // [1827]②/core 1.429:结构化同源体。字段 PICKED(不整对象 spread——future core 键不得裸骑);
862
- // toolName verbatim,target 与 currentAction 同边界(redact + 300 bound,brainStatus.detail 同惯例)。
863
452
  const currentTool = e.currentTool
864
453
  ? { toolName: e.currentTool.toolName, ...(e.currentTool.target !== undefined ? { target: redactSecrets(e.currentTool.target).slice(0, 300) } : {}) }
865
454
  : undefined;
866
- // cli [1748]:两个 additive 位。`toolUses` 是**累计值**( turn_end 的每轮增量相反) 直接上行不累加;
867
- // 缺席时**不发这个键**(消费方不得把缺席当 0)。`transcriptId` 是委派 prompt 的**映射** ——
868
- // prompt 本身不在 `BackgroundChildEvent` 上,server 收不到就不造。
869
- bus.publishTask({ id: e.taskId, ...rowTags(m), status: "running", ...(tokens !== undefined ? { tokens } : {}), ...(name ? { name } : {}), ...(agentType ? { agentType } : {}), ...(currentAction ? { currentAction } : {}), ...(currentTool ? { currentTool } : {}), ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.usage?.toolUses !== undefined ? { toolUses: e.usage.toolUses } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) }); // #261 seq→cycleSeq 缺席不铸
455
+ bus.publishTask({ id: e.taskId, ...rowTags(m), status: "running", ...(tokens !== undefined ? { tokens } : {}), ...(name ? { name } : {}), ...(agentType ? { agentType } : {}), ...(currentAction ? { currentAction } : {}), ...(currentTool ? { currentTool } : {}), ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.usage?.toolUses !== undefined ? { toolUses: e.usage.toolUses } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
870
456
  };
871
457
  const handleTerminal = (e, m) => {
872
- // terminal: flip the row so subscribers SEE the terminal frame, then leave the fleet — and push the
873
- // contract bg_notification on the always-open lane (缺口②: idle delivery is immediate; the durable
874
- // inbox park stays as the offline/replay belt, shell dedups by (taskId,status)).
875
458
  const status = e.status === "completed" ? "completed" : e.status === "killed" ? "killed" : "failed";
876
- // [1839]①/(a) 案:终态**行帧**同带残局键——此前只有 {id,status},看行卡(不订通知帧)的 idle 观察者
877
- // 拿不到终局。与下方 bg_notification 同源同姿势:键逐挑不 spread、editedFiles path 过 redactSecrets
878
- // + 100 界、缺席不铸。(recentSteps 不进行帧——通知卡语义,行卡不渲染步骤列表。)
879
459
  bus.publishTask({
880
460
  id: e.taskId, ...rowTags(m), status,
881
- ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), // #261 §2:终帧带**被结算那一代**的代际号(缺席不铸)
461
+ ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}),
882
462
  ...(e.stoppedBy ? { stoppedBy: e.stoppedBy } : {}),
883
463
  ...(e.resumable !== undefined ? { resumable: e.resumable } : {}),
884
464
  ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}),
@@ -897,12 +477,8 @@ export function fleetBackgroundChildPublisher(bus, log) {
897
477
  : {}),
898
478
  });
899
479
  bus.removeTask(e.taskId);
900
- // [2070]① 让位解除:本子代已终态离场,同 uuid 的迟到帧回到旧行为(claim 只在活期有效)。
901
480
  if (m.aliasUuid)
902
481
  bus.releaseBackgroundChildLane(m.tenantScope, m.aliasUuid);
903
- // codex 1.240-R1(M2):1.356 的回调序=bceTick 先于 host forward——tick 处的双生抑制扫在 uuid 双生
904
- // 行发布**之前**,one-shot 空扫后置旗,双生行永存。terminal 补扫一次(aliasUuid=tick 学到的 child
905
- // uuid),把 run-leg 的复合键行清场。
906
482
  if (m.aliasUuid) {
907
483
  const tail = ` ${m.aliasUuid}`;
908
484
  for (const row of bus.snapshot().tasks) {
@@ -910,32 +486,19 @@ export function fleetBackgroundChildPublisher(bus, log) {
910
486
  bus.removeTask(row.id);
911
487
  }
912
488
  }
913
- // codex 1.240-R1(H1):wa\*(workflowRunId 在)不走 bg_notification 契约——该帧族=后台任务完成通知
914
- // (壳注入模型),workflow 的完成通知已有 workflow_complete 专链;且 1.356 wa\* 帧 sessionScoped:false
915
- // 无 host owner → 通知无 ownerSessionId=同租户**全会话**可见(streamFleet 的 absent-放行是 bg 兼容位,
916
- // 不是 wa\* 的授权)。per-agent 完成本就不该打断模型(CC 亦无此形)。
917
489
  if (m.workflowRunId !== undefined) {
918
- m.done = true; // codex 1.240-R2(M):早退分支必须先落墓碑——迟到/重放 tick 不得复活已离场的 wa* 行
490
+ m.done = true;
919
491
  dlog("bg_child_event", { kind: "terminal-wa-no-notification", taskId: e.taskId, workflowRunId: m.workflowRunId });
920
492
  return;
921
493
  }
922
494
  bus.publishBgNotification({
923
495
  taskId: e.taskId,
924
496
  ...(e.sessionId ? { sessionId: e.sessionId } : {}),
925
- // [1392]①(PAIR-REVIEW F-1@cli 的 wire 前置):终帧 seq(core 1.352 BackgroundChildEvent.terminal 补位,
926
- // tolerate-absent——旧 core 无此位=不发,cli 键折 seq 的修依赖它区分 revive 周期)。
927
497
  ...(typeof e.seq === "number" ? { seq: e.seq } : {}),
928
498
  status: e.status ?? "failed",
929
499
  ...(e.summary ? { summary: redactSecrets(e.summary) } : {}),
930
500
  scope: (m.sessionScoped || e.sessionScoped) ? "session" : "task",
931
- // codex(1.350 二轮):wire 的 parentTaskId 位只出真任务域;fallback 形出解析成功的 run 行 id
932
- // (=durable run id,消费者拿它绑父行),解析未成=省略,绝不让会话 id 冒充任务 id 出 wire。
933
501
  ...(m.parentTaskId ? { parentTaskId: m.parentTaskId } : m.resolvedParentId ? { parentTaskId: m.resolvedParentId } : {}),
934
- // The residual fields must ride THIS lane too (the "kill across turns" main line renders
935
- // off it). Same posture as project.ts/inbox; fields picked, arrays defensively bounded.
936
- // P1-3(core 1.452,[1925] 一轮批):completionId=子代完成的幂等锚,消费端(cli)按它对同一完成的
937
- // 多路通知(bg_notification/task_notification/inbox 重放)去重。verbatim 透传,仅通知帧带
938
- // (行帧不带——行的身份是 taskId,幂等锚是通知语义)。
939
502
  ...(e.completionId ? { completionId: e.completionId } : {}),
940
503
  ...(e.stoppedBy ? { stoppedBy: e.stoppedBy } : {}),
941
504
  ...(e.resumable !== undefined ? { resumable: e.resumable } : {}),
@@ -943,7 +506,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
943
506
  ? { recentSteps: e.recentSteps.slice(0, 10).map((s) => ({ tool: s.tool, target: redactSecrets(s.target), outcome: redactSecrets(s.outcome) })) }
944
507
  : {}),
945
508
  ...(e.editedFiles !== undefined ? { editedFiles: e.editedFiles.slice(0, 100).map((f) => ({ path: redactSecrets(f.path), edits: f.edits })) } : {}),
946
- // [1543]§二 LOW:usage/transcriptId 补齐(fields picked——usage 六数值键逐一挑,不整对象 spread)。
947
509
  ...(e.usage
948
510
  ? {
949
511
  usage: {
@@ -957,14 +519,13 @@ export function fleetBackgroundChildPublisher(bus, log) {
957
519
  }
958
520
  : {}),
959
521
  ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}),
960
- ...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}), // P1-2(core 1.432)——通知帧同带,cli「全部生命周期事件在场」
522
+ ...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}),
961
523
  ...(m.tenantScope ? { ownerScope: m.tenantScope } : {}),
962
524
  ...(m.hostSessionId ? { ownerSessionId: m.hostSessionId } : {}),
963
525
  ...(m.rootSessionId ? { rootSessionId: m.rootSessionId } : {}),
964
526
  });
965
- // One info line per terminal so the emit-vs-filter bisect is a log-grep, not a rerun.
966
527
  dlog("bg_child_event", { kind: "terminal", taskId: e.taskId, status, scope: m.tenantScope ?? null, hostSessionId: m.hostSessionId ?? null, childSessionId: e.sessionId ?? null, bgNotificationPublished: true });
967
- m.done = true; // tombstone (kept in the bounded map) — see Meta.done
528
+ m.done = true;
968
529
  m.suppressedTwin = true;
969
530
  };
970
531
  return (e) => {
@@ -972,22 +533,18 @@ export function fleetBackgroundChildPublisher(bus, log) {
972
533
  handleSpawn(e);
973
534
  return;
974
535
  }
975
- // [2354] 补审 F2:lazy-mint 不再铸 spawnedAt: Date.now() 假锚(与 spawn 臂同口径——缺 launch 锚=
976
- // 诚实缺席,行键 optional;tick/terminal 先到=restart/丢 spawn 边缘,收帧时刻不是 launch 时刻)。
977
- const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: e.scope ?? "default" }; // scope domain map holds on the lazily-minted path too([1338]④ 首见起表);F2(2026-07-30 复审):lazy-mint 曾硬编码 "default",scoped 部署下 claim 落错租户键 ⇒ 让位失效退回双生行
536
+ const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: e.scope ?? "default" };
978
537
  if (!meta.has(e.taskId)) {
979
- remember(e.taskId, m); // tick/terminal without a seen spawn (restart/eviction edge) — row minted lazily
538
+ remember(e.taskId, m);
980
539
  dlog("bg_child_event", { kind: `${e.kind}-lazy-mint`, taskId: e.taskId, sessionScoped: e.sessionScoped });
981
540
  }
982
541
  else {
983
- remember(e.taskId, m); // codex:活儿每拍刷新近度——LRU 兜底永远先挤最久没动静的,不挤在跑的
542
+ remember(e.taskId, m);
984
543
  }
985
544
  if (m.done) {
986
- // tombstone: this child already settled — a late tick must not resurrect a running row (settle-freeze 同族).
987
545
  dlog("bg_child_event", { kind: `${e.kind}-after-terminal-ignored`, taskId: e.taskId });
988
546
  return;
989
547
  }
990
- // P1-2(core 1.432):lazy-mint/spawn 前旧 core 帧路径——parentToolCallId「出生就在场」,首见即补
991
548
  if (m.parentToolCallId === undefined && e.parentToolCallId)
992
549
  m.parentToolCallId = e.parentToolCallId;
993
550
  if (e.kind === "tick") {
@@ -997,32 +554,15 @@ export function fleetBackgroundChildPublisher(bus, log) {
997
554
  handleTerminal(e, m);
998
555
  };
999
556
  }
1000
- /** BC-1 (clay-decided 2026-06-27): a top-level run's fleet-row `name` = a SHORT, single-line **objective preview**
1001
- * (the readable "what is this task" label) — NOT the full objective dumped in, and NOT duplicated into `description`.
1002
- * `description` is left to the publisher's LIVE-ACTIVITY update (`onEvent` tool_start → the current tool), so the row
1003
- * shows `name`=objective + `description`=current activity (two DISTINCT things, killing the earlier "objective shown
1004
- * twice" dup). Whitespace-collapsed + capped ≤48. (A subagent CHILD row's name = its agent-type — BC-2.)
1005
- * SECRET-SAFE: redact secret shapes (redactSecrets) BEFORE truncating — a secret in the objective must not leak in
1006
- * the row name. The durable objective is already redacted; the name preview was asymmetric (raw) until this. */
1007
557
  export function fleetRunLabels(objective) {
1008
558
  const oneLine = redactSecrets((objective ?? "").replace(/\s+/g, " ")).trim();
1009
559
  if (!oneLine)
1010
560
  return { name: "task" };
1011
- // [1414]④:控制帧形 objective(注入 turn 的 `<task-notification>…` 等 XML 头)不得当行名——用户底部
1012
- // 会看到「<task-notifica…」尖括号伪影。以 `<` 开头且形如 XML tag 的 objective 给中性名;真实用户
1013
- // objective 以 `<` 开头的形(粘贴 HTML 片段)极罕且中性名无害。
1014
561
  if (/^<[a-z][a-z0-9_-]*[\s>/]/i.test(oneLine))
1015
562
  return { name: "(notification turn)" };
1016
563
  const name = oneLine.length > 48 ? `${oneLine.slice(0, 47)}…` : oneLine;
1017
564
  return { name };
1018
565
  }
1019
- /**
1020
- * fleet 行的**离场**判据(`onTerminal` 的 removal 集)—— `FleetTaskStatus` 的穷举 switch。
1021
- *
1022
- * 与 {@link runStatusToFleet} 同族(A-032 P1-④ 证据段点名):此前是消费点手抄的
1023
- * `=== "completed" || === "failed" || === "killed"` 三词字面比对,fleet 词表加成员时同样没有编译期钉。
1024
- * park 态(`waiting` / `awaiting approval`)判 false 是承重的:行还活着,必须留在 fleet 上。
1025
- */
1026
566
  export function isTerminalFleetStatus(status) {
1027
567
  switch (status) {
1028
568
  case "completed":
@@ -1037,16 +577,12 @@ export function isTerminalFleetStatus(status) {
1037
577
  case "idle":
1038
578
  return false;
1039
579
  default: {
1040
- const unhandled = status; // FleetTaskStatus 加成员 ⇒ tsc 红,加成员的人当场回答「这个态算不算离场」
580
+ const unhandled = status;
1041
581
  void unhandled;
1042
- // 运行期保守:未知 ⇒ 不摘行(误摘 = 把一条还在跑的任务从面板上抹掉;漏摘只是多一行)。
1043
- // 这条臂与 runStatusToFleet 的 miss 臂**不同源**:`FleetTaskStatus` 是本仓自有词表,只会被本仓
1044
- // 自己加成员,而那是编译期就红的事 —— 所以这里不接留痕(没有"运行期冒出个新词"的来路)。
1045
582
  return false;
1046
583
  }
1047
584
  }
1048
585
  }
1049
- /** Map a service run status onto the neutral fleet vocabulary the shell renders. */
1050
586
  export function runStatusToFleet(status, opts) {
1051
587
  if (opts?.awaitingPlanApproval)
1052
588
  return "awaiting approval";
@@ -1059,26 +595,17 @@ export function runStatusToFleet(status, opts) {
1059
595
  return "completed";
1060
596
  case "failed":
1061
597
  case "error":
1062
- case "blocked": // report_blocked = a non-success TERMINAL outcome (agent gave up) → the "failed" bucket (the
1063
- // neutral fleet vocab has no "blocked"). CRUCIAL: this also lets onTerminal REMOVE the row — the removal set is
1064
- // {completed,failed,killed}, so without mapping "blocked" here it fell through to the "idle" default and a
1065
- // terminal blocked run LEAKED a permanent stale "idle" row on the fleet (caught by on-box e2e, 2026-06-28).
598
+ case "blocked":
1066
599
  return "failed";
1067
600
  case "suspended":
1068
- return "waiting"; // a durable suspend = parked, awaiting resume
601
+ return "waiting";
1069
602
  case "needs_review":
1070
603
  return "awaiting approval";
1071
604
  case "killed":
1072
605
  case "cancelled":
1073
606
  return "killed";
1074
607
  default: {
1075
- // 🔴 编译期执法点:词表加成员 ⇒ 这里收不敛到 `never` ⇒ tsc 红,加成员的人必须当场回答
1076
- // 「这个新态在 fleet 上长什么样、行该不该离场」。
1077
608
  const unhandled = status;
1078
- // 运行期防线(#157:runtime miss 臂必须自陈)。这条臂**运行期仍可达**:`rowToRun` 把无约束的
1079
- // SQL 文本列裸 cast 成联合,滚动升级里更新的副本写入的新状态词会被旧副本读到。方向照旧折 `idle`
1080
- // (不改行为,改的是「静默」),但每次 miss 必须留痕 —— 否则「fleet 上为什么有一行永远不走的 idle」
1081
- // 在遥测里没有任何痕迹,只能靠人眼在面板上发现。
1082
609
  recordFailOpen("server.fleet.run-status-unmapped", String(unhandled));
1083
610
  return "idle";
1084
611
  }