@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,93 +1,15 @@
1
- /**
2
- * #189 修方向 1(黑板 [3112] 定谳)—— `/v1/fleet/stream` 连接时快照要并入的**近期终态 workflow 行窗**。
3
- *
4
- * ## 它修什么
5
- * 引擎重启后 boot 扫描判死前世 `running` run,`publishWorkflow`(终帧)与 `removeWorkflow` 在**同一同步
6
- * 调用栈内**背靠背执行;fleet bus 是纯内存 EventEmitter、无重放,`snapshot()` 只读活跃 Map ⇒ **重启之后
7
- * 才连上的客户端连增量带快照都结构性看不见那一行**(cli 七轮正控实证)。durable 真源一直是对的
8
- * (`GET /v1/workflows` 可达),缺的只是把它接到面板这一路上——所以这里是 **pull**:读同一份 durable
9
- * store,**不碰 bus 的活跃 Map**(活行的单写者不变量、增量帧语义逐字不动)。
10
- *
11
- * ## 五条界(任何一条失手都只是少几行历史 ⇒ 整体 F 类 fail-open,登记 tag,不静默)
12
- * · 窗长 {@link FLEET_SNAPSHOT_TERMINAL_WINDOW_MS} · 行数 {@link FLEET_SNAPSHOT_TERMINAL_MAX_ROWS}
13
- * · 扫描面 {@link FLEET_SNAPSHOT_TERMINAL_SCAN_LIMIT} · 读预算 {@link FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS}
14
- * · 熔断冷却 {@link FLEET_SNAPSHOT_TERMINAL_COOLDOWN_MS}
15
- *
16
- * ## 为什么本模块有进程级可变状态(路由域刻意保持零状态,故不放在 `routes/fleet.ts`)
17
- * 三件都长在同一个事实上——**`WorkflowRunStore` 契约没有取消面**,`Promise.race` 只结束等待、撤不回
18
- * 已发出的查询:合流(重连风暴不按连接数放大,R1-H2)、合流窗有限(不把陈旧结果发给后来者、也不让
19
- * 一条永不 settle 的读把后续连接全钉死,R2-H1/R3-M2)、熔断冷却(挂死的后端不被每周期继续加压,
20
- * R3-H1)。真解是店侧查询超时/取消 —— 要动契约与四个后端,已作移交项。
21
- *
22
- * ## scope
23
- * `WorkflowRunStore` 契约**没有跨 scope 枚举**(见 `orchestration/workflow-notify-journal.ts` 头注),
24
- * 所以窗恒按**调用者自己的 principal scope** 读:fleet-wide 观察者看到的是「全租户活行 + 自己 scope 的
25
- * 终态窗」。这是诚实的不对称(读不到的东西不编),方向安全(绝不多给);真行的租户/会话可见性仍由
26
- * 路由的 `visW` 统一执法,本模块不自行放行。
27
- */
28
1
  import { isTerminalWorkflowStatus } from "@sema-agent/core";
29
2
  import { recordFailOpen } from "../observability/fail-open.js";
30
3
  import { buildFleetWorkflowRow } from "./fleet-bus.js";
31
- /**
32
- * 窗长 —— 10 分钟,**不做旋钮**。
33
- *
34
- * 它覆盖的是一段具体的人机时长:「引擎重启 → 客户端重连 → 人看一眼面板上刚才那条 workflow 怎么了」。
35
- * 比它短会漏掉重连慢一步的壳(即本 issue 的病灶);比它长就变成「拿 SSE 快照当历史列表」——而历史面
36
- * 本来就有真源(`GET /v1/workflows`:durable、可分页、带 scope/session 过滤),不该在这里长出第二个。
37
- * 两侧边界都由语义定死,旋钮只会让部署方去调一个没有正确取值的数;fleet 面现有零旋钮,从之。
38
- */
39
4
  export const FLEET_SNAPSHOT_TERMINAL_WINDOW_MS = 10 * 60_000;
40
- /** 同一快照里终态行的行数上限(窗长之外的第二道界)。快照是面板首屏,不是历史列表——超出的部分归
41
- * `GET /v1/workflows`。 */
42
5
  export const FLEET_SNAPSHOT_TERMINAL_MAX_ROWS = 20;
43
- /**
44
- * 每个终态**扫描面**的行数(店侧下推的 limit)。它比 {@link FLEET_SNAPSHOT_TERMINAL_MAX_ROWS} 大是有
45
- * 具体病灶的(codex R1-M3,红先复现):店侧分页按 `createdAt DESC` 排(core 契约 + 两个 SQL 实现皆然),
46
- * 而本窗的判据是**结束时刻**——一条跑了三天、刚刚才结束的 workflow(正是用户此刻要看的那条)会被 20 条
47
- * 更晚创建的行挤出首页。留这段余量是在现有契约内能给的最好答案;彻底解法是店侧加一个 `ended_at_ms DESC`
48
- * 的窄查询(要过真双库门,已作移交项记在发车说明里)。
49
- *
50
- * 残余(如实记档):同 scope 同一终态下,若有超过本值条**更晚创建**的行,那条"早创建、刚结束"的行仍会
51
- * 被挤掉。此时用户仍可从 `GET /v1/workflows` 看到真相——丢的只是面板首屏的一行。
52
- */
53
6
  export const FLEET_SNAPSHOT_TERMINAL_SCAN_LIMIT = 100;
54
- /** durable 读的时间预算。超时=**放弃这段增益**(记 F 类 fail-open),绝不让一次慢查询把整条 SSE 握手
55
- * 拖住——面板少一条历史行是难看,连不上流是坏掉。 */
56
7
  export const FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS = 2_000;
57
- /**
58
- * (原 `FLEET_SNAPSHOT_TERMINAL_COALESCE_MS` 已删)⚖️ 「同时只有一支探针」与「结果别太陈旧」的取舍
59
- * —— codex R3-M2 与 R4-H1 是两轮方向相反的 finding,这里成文定案。
60
- *
61
- * 后来者**加入**在飞的那支读,只要它还在自己的预算之内({@link FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS})。
62
- * · 曾短暂改成 250ms 的窄窗(想压小"采到"与"交付"的时差):实测判据下更坏 —— 慢而健康的店(比如
63
- * 1.9s)配错峰重连,会变成每 250ms 起一批新读(2 次 list + ≤20 点读),而熔断要等某个调用方 2s
64
- * 超时才开;单飞的意义被自己抵消(R4-H1)。
65
- * · 保留的代价:刚好在探针尾巴上加入的调用方,可能拿到一份最多约"两倍预算"之前采到的行集(R3-M2)
66
- * —— 后果是这条连接首屏少一条**刚刚**结束的行(且只有跨副本翻转才补不上帧),与本模块整体的 F 类
67
- * 姿态同级;而 R4-H1 的后果是给一个已经很慢的库继续加压。取小害。
68
- * 过了预算的在飞读 = 已被放弃的一代(等待方全走光了),不再加入:那一代由熔断收尾,新调用方在冷却期
69
- * 外开新一代。
70
- */
71
- /**
72
- * 熔断冷却期:一次读失败/超预算之后,同 (store, scope) 在这段时间内**不再发起新读**(直接放弃这段增益,
73
- * 记 F 类 fail-open)。
74
- *
75
- * 为什么必须有(codex R3-H1,红先复现 4→2):`WorkflowRunStore` 契约没有取消面 —— 发出去的查询撤不回。
76
- * 后端挂死时,只按"复用窗过期就再起一次"办,等于每个周期给那个已经挂死的后端再加两笔永不回来的活,
77
- * 连接不断则永远堆下去(池/内存/驱动队列迟早耗尽),这正是"故障期被自己的重试放大"那一族。冷却期把
78
- * 新增速率钉在「每 scope 每 10s 至多一次探针」,并且**自愈**:冷却一过,下一条连接就是一次真实探测。
79
- * 残余(如实记档):挂死那几笔仍在后端占着;真解=店侧查询超时/取消(要动契约与四个后端,移交项)。
80
- */
81
8
  export const FLEET_SNAPSHOT_TERMINAL_COOLDOWN_MS = 10_000;
82
- /** 终态词表:从 {@link WorkflowRunStatus} 闭集**穷举**(缺一个键或多一个键都是编译期错误)后按 core 的
83
- * {@link isTerminalWorkflowStatus} 判定——集合的闭合由类型执法、终态的**语义**仍是 core 单源。core 若
84
- * 加一个状态,这里编译期就红,而不是悄悄漏进/漏出终态窗。 */
85
9
  const TERMINAL_WORKFLOW_STATUSES = (() => {
86
10
  const all = { running: true, completed: true, failed: true };
87
11
  return Object.keys(all).filter((s) => isTerminalWorkflowStatus(s));
88
12
  })();
89
- /** fleet 行上的 `status` 是自由字符串(wire 面容忍未知词),而"是不是终态"必须与上面那张**同一张**
90
- * 闭集表说同一句话 —— 供路由判断一条 workflow 行帧是否终态(不做裸 cast:词表就是判据)。 */
91
13
  export function isTerminalWorkflowRowStatus(status) {
92
14
  return TERMINAL_WORKFLOW_STATUSES.some((s) => s === status);
93
15
  }
@@ -105,28 +27,20 @@ function scopeState(store, scope) {
105
27
  }
106
28
  return st;
107
29
  }
108
- /** 开熔断,并挂一个 unref 的到期回调自清 + 销号 —— 否则「读超时 2.0s、查询 2.1s 才落地」这条常见路径会
109
- * 留下一条永久驻留的 scope 记录(冷却记号没人清、`evictIfIdle` 又因为冷却未过而不动手,codex R5-M2)。
110
- * 代际校验(`coolingUntil === until`)保证到期回调只清自己开的那一次。 */
111
30
  function openCooldown(store, scope, st) {
112
- // codex R6-M:同一支探针失败的 N 个等待者各自到点都会走到这里——冷却语义是 per-probe-failure,不是
113
- // per-waiter,首开者赢:已在冷却期内就不续期、不再铸第二支定时器(风暴+挂库时 O(连接数) 定时器与
114
- // 闭包正好压在依赖已经退化的时刻)。
115
31
  if (st.coolingUntil !== undefined && st.coolingUntil > Date.now())
116
32
  return;
117
33
  const until = Date.now() + FLEET_SNAPSHOT_TERMINAL_COOLDOWN_MS;
118
34
  st.coolingUntil = until;
119
35
  const t = setTimeout(() => {
120
36
  if (st.coolingUntil !== until)
121
- return; // 期间又开过新的一次冷却:那次自己管自己
37
+ return;
122
38
  st.coolingUntil = undefined;
123
39
  evictIfIdle(store, scope, st);
124
40
  }, FLEET_SNAPSHOT_TERMINAL_COOLDOWN_MS);
125
41
  if (typeof t.unref === "function")
126
- t.unref(); // 观测性的收尾绝不该拖住进程退出
42
+ t.unref();
127
43
  }
128
- /** 无事可记的 scope 就地销号(codex R4-M2):否则每一个**曾经**开过流的 principal 都会在这张强引用表里
129
- * 永久留一条——多租户长跑进程下,租户流动/高基数身份会把它变成一条只增不减的内存线。 */
130
44
  function evictIfIdle(store, scope, st) {
131
45
  if (st.inFlight !== undefined)
132
46
  return;
@@ -136,34 +50,17 @@ function evictIfIdle(store, scope, st) {
136
50
  if (byScope?.get(scope) === st)
137
51
  byScope.delete(scope);
138
52
  }
139
- /**
140
- * 取该 scope 的近期终态行窗。**永不抛**:任何失败/超预算都收成"少几行历史"并记 F 类 fail-open。
141
- *
142
- * ⚠️ 调用方仍须对每一行施加与活行**同一套**可见性判据(`visW`:scope + `?session=`)——本函数按 scope
143
- * 读,但会话轴刻意留给调用方,好与活行"无 sessionId 的行 fail-open 可见"的既有语义逐字一致。
144
- */
145
- /** [3156] 轮2 真红的修:进程内终态行 seed 缓存 —— **写路径顺手喂**(publishFleet 的 terminal 臂),
146
- * 读侧零库查询零时序竞态。存在理由:durable pull 的查询键是 caller 的 scope,而 fleetWide 连接
147
- * (无 auth 部署恒是)的可见性判据放行**全部** scope —— 查询键与可见性判据不同源。行 scope 的值
148
- * = 提交方自报的 principal 经引擎透传("anon:shell-live" 是 cli 壳在本地单用户模式**自发的 header
149
- * 值**,引擎只做 scope=principal 赋值,不发明该字面量;[3225] 后续侦察定谳,勿再写「引擎铸」),
150
- * 它永远不等于 caller 读侧的 "default",pull 恒 miss。seed 缓存按「发生过什么」
151
- * 记(与订阅方无关),读侧再过各连接自己的 visW。不是活跃集:bus 的 Map 零触碰,单写者不变量原样。
152
- * 有界:行数 ≤ MAX_ROWS×2、窗长同 WINDOW_MS,超界丢最旧。 */
153
53
  const seededTerminalRows = [];
154
54
  export function seedTerminalWorkflowRow(row) {
155
55
  const now = Date.now();
156
56
  seededTerminalRows.push({ row, at: now });
157
- // 双界修剪:窗长 + 行数(常数级,每次 seed 顺手)
158
57
  while (seededTerminalRows.length > 0 && (now - seededTerminalRows[0].at > FLEET_SNAPSHOT_TERMINAL_WINDOW_MS || seededTerminalRows.length > FLEET_SNAPSHOT_TERMINAL_MAX_ROWS * 2)) {
159
58
  seededTerminalRows.shift();
160
59
  }
161
60
  }
162
- /** 测试缝:清 seed 缓存(与 clearTerminalWindowCooldownForTest 同姿势,生产零调用)。 */
163
61
  export function clearSeededTerminalRowsForTest() {
164
62
  seededTerminalRows.length = 0;
165
63
  }
166
- /** 窗内 seed 行(同 id 取最新一条;窗外的顺手剪掉)。 */
167
64
  function seededRowsInWindow() {
168
65
  const now = Date.now();
169
66
  const byId = new Map();
@@ -174,29 +71,19 @@ function seededRowsInWindow() {
174
71
  return [...byId.values()];
175
72
  }
176
73
  export async function recentTerminalWorkflowRows(store, scope, pullScope) {
177
- // [3156]:seed 缓存 = 本进程生命周期内全部终态(含 boot recover 判死)的同源可达径——fleetWide 连接
178
- // (scope=null,可见性判据放行全 scope)取 seed **全量**,并以 `pullScope`(caller principal,尽力形)
179
- // 做 durable pull 兜底:pull 的查询键与行 scope(提交方自报 principal 的透传值)可能不同源
180
- // (cli 轮2 实测 "default" vs 壳自发的 "anon:shell-live" 恒 miss),中了是增益、中不了还有 seed
181
- // ——两径并集,谁都不当唯一真源。
182
- // scoped 连接:seed 按本 scope 滤 + durable pull 本 scope(跨进程兜底)。
183
- // [3193] 病理3:seed 侧不再按 caller scope 预滤——那是第二份可见性判据(F1 病族),与行 scope
184
- // (壳自发 "anon:shell-live" vs caller "default")恒 miss。本函数只产**候选集**;可见/擦除由路由的
185
- // visW/stripW 同一套判据裁(与快照活行、增量帧同源),窗与 MAX_ROWS 双界仍在。
186
74
  const seeded = seededRowsInWindow();
187
75
  const effectiveScope = scope ?? pullScope;
188
76
  if (effectiveScope === undefined)
189
77
  return seeded;
190
78
  const scopeForPull = effectiveScope;
191
79
  if (!store)
192
- return seeded; // 无 durable store 的部署(memory 后端/未开自编排):seed 缓存仍工作
80
+ return seeded;
193
81
  const cooling = scopeState(store, scopeForPull);
194
82
  if (cooling.coolingUntil !== undefined && Date.now() < cooling.coolingUntil) {
195
- // 熔断中:上一代读超预算(后端卡住),这段时间内连探都不探(见 FLEET_SNAPSHOT_TERMINAL_COOLDOWN_MS)。
196
83
  recordFailOpen("server.fleet.snapshot-terminal-window-unavailable", "cooling down after an over-budget read");
197
- return seeded; // 熔断只熔 durable 读;seed 是进程内的,照常(scope=null 的 fleetWide 形=全量,勿再按 null 过滤)
84
+ return seeded;
198
85
  }
199
- cooling.coolingUntil = undefined; // 冷却已到期:销掉这个记号,好让空闲 scope 能被回收
86
+ cooling.coolingUntil = undefined;
200
87
  evictIfIdle(store, scopeForPull, cooling);
201
88
  let timer;
202
89
  const probe = sharedRead(store, scopeForPull);
@@ -207,67 +94,33 @@ export async function recentTerminalWorkflowRows(store, scope, pullScope) {
207
94
  timer.unref();
208
95
  });
209
96
  const pulled = await Promise.race([probe.rows, budget]);
210
- // scoped 腿 = durable pull ∪ seed(同 id durable 优先 —— 库行是 CAS 后真源,seed 是投影快照)
211
97
  const pulledIds = new Set(pulled.map((r) => r.id));
212
98
  return [...pulled, ...seeded.filter((r) => !pulledIds.has(r.id))];
213
99
  }
214
100
  catch (err) {
215
- // 熔断**只由自己这一代开**(codex R4-H1):判据是「我等的那一代到现在还挂在飞」——那才是"后端卡住、
216
- // 再发就是加压"。两个反例都被这条判据排除:① 我等的那代其实已经完成/失败并销号(快失败的店不累积
217
- // 在飞的活,没有加压问题,不该罚 10 秒);② 我超时时表上挂的已经是**别人**开的新一代(我这代早settle
218
- // 了),拿它当证据会让一个已经健康的 scope 被上一代的迟到超时按住。
219
- // 🔴 只**查表**、不铸造(2026-08-12 合并码重扫 confirmed):快失败路径上 probe 的 .finally 先跑
220
- // (清 inFlight + evictIfIdle 销号),这里若用 scopeState() 会**重建**一个新空条目——它 inFlight
221
- // 恒 undefined ⇒ 冷却不开、又没人再销,每个快失败的 scope 就永久留一条(R4-M2 那条只增内存线
222
- // 换了个入口)。观测口 terminalWindowJoinsInFlightForTest 的头注同判据:检查者不许创建它要检查的状态。
223
101
  const st = stateByStore.get(store)?.get(scopeForPull);
224
102
  if (st !== undefined && st.inFlight === probe)
225
103
  openCooldown(store, scopeForPull, st);
226
- // detail 用 `err.message` 而不是 `String(err)`:后者会调用抛出值自己的 toString,而那本身可能再抛
227
- // (逃出本兜底,把降级变成故障)。非 Error 抛出值就如实说"不是 Error",不去问它任何问题。
228
104
  recordFailOpen("server.fleet.snapshot-terminal-window-unavailable", err instanceof Error ? err.message : "terminal-window read threw a non-Error value");
229
- return seeded; // durable 读失败只失去跨进程兜底;本进程 seed 照常(读它不碰后端)
105
+ return seeded;
230
106
  }
231
107
  finally {
232
108
  if (timer !== undefined)
233
109
  clearTimeout(timer);
234
110
  }
235
111
  }
236
- /**
237
- * test-only:把熔断冷却**提前**到现在(等价于"冷却期已过"),**不动**在飞读的登记。
238
- *
239
- * 为什么只清冷却:冷却期是墙钟(10s),测试不该真等;而"在飞读的复用是否会把毒 promise 传给后来者"
240
- * 恰恰是要被测的行为,清掉它就等于把待测对象删了。生产路径不引用本函数(与
241
- * `resetFailOpenRecorderForTest` 同一姿势:测试面显式,不给生产留旁路)。
242
- */
243
112
  export function clearTerminalWindowCooldownForTest(store, scope) {
244
113
  scopeState(store, scope).coolingUntil = undefined;
245
114
  }
246
- /** test-only:这个 store 目前还记着几个 scope(codex R4-M2 的空闲销号有没有真的销)。 */
247
115
  export function trackedTerminalWindowScopesForTest(store) {
248
116
  return stateByStore.get(store)?.size ?? 0;
249
117
  }
250
- /**
251
- * test-only:下一次开流会不会**合流到**当前这代在飞的读(与 {@link sharedRead} 同一份判据,不另抄常数)。
252
- *
253
- * 为什么需要它:挂死的读永不 settle,那一代会一直留在表上、只被复用窗按龄挡掉。测试要断言"后端恢复后
254
- * 的下一次开流读得到行",前置条件恰恰是"毒代已过复用窗"——那条前置条件必须**等出来**(轮询本谓词),
255
- * 不能靠"上一条连接自己吃满预算 + 一次客户端往返"凑出的那几毫秒墙钟余量(实测 8~28ms,既无因果保证
256
- * 也无下界 ⇒ 负载敏感偶发红)。读到的是快照:表里没有这一 scope 就是"不会合流"。
257
- *
258
- * ⚠️ 刻意**不走** `scopeState`(它会顺手建表项)——观测口不许改被观测的状态(`trackedTerminalWindow
259
- * ScopesForTest` 钉的空闲销号计数会被它污染)。
260
- */
261
118
  export function terminalWindowJoinsInFlightForTest(store, scope) {
262
119
  return joinsInFlight(stateByStore.get(store)?.get(scope)?.inFlight, Date.now());
263
120
  }
264
- /** 后来者是否加入这一代在飞的读:仍在自己的预算之内才加入(见上方取舍段);更老的那代已被放弃,不加入。
265
- * 抽成具名谓词的唯一理由是**单一属主**——测试的观测口({@link terminalWindowJoinsInFlightForTest})与
266
- * 生产的合流决策必须由同一行代码回答,否则测试里那份常数复制件会在实现改判据的那天悄悄失效。 */
267
121
  function joinsInFlight(probe, now) {
268
122
  return probe !== undefined && now - probe.startedAt < FLEET_SNAPSHOT_TERMINAL_READ_BUDGET_MS;
269
123
  }
270
- /** 合流层:同 (store, scope) 上**同时只有一代**在飞的读;后来者加入仍在预算内的那一代,settle 即销号。 */
271
124
  function sharedRead(store, scope) {
272
125
  const st = scopeState(store, scope);
273
126
  const existing = st.inFlight;
@@ -276,65 +129,44 @@ function sharedRead(store, scope) {
276
129
  const entry = {
277
130
  startedAt: Date.now(),
278
131
  rows: readRecentTerminalWorkflowRows(store, scope).finally(() => {
279
- // 只销自己那一号:一次慢读期间可能已经开始了下一代(本次先 settle 的竞态下,清空会误删新号)。
280
132
  if (st.inFlight === entry) {
281
133
  st.inFlight = undefined;
282
134
  evictIfIdle(store, scope, st);
283
135
  }
284
136
  }),
285
137
  };
286
- // 所有等待方都可能因为超预算先走(race),没有 catch 的 promise 会变成 unhandledRejection 把进程带下去。
287
138
  entry.rows.catch(() => undefined);
288
139
  st.inFlight = entry;
289
140
  return entry;
290
141
  }
291
- /**
292
- * `Promise.all` 的**等齐**版:等**每一支**都落地,再决定成败(有失败就抛第一个失败原因)。
293
- *
294
- * 为什么不能用 `Promise.all`(codex R5-H1):它在**第一支**失败时立刻 reject,而兄弟支还挂在后端上。
295
- * 于是这一代探针提前销号,超时的调用方看到"我这代已经不在表上了"就不开熔断(那条判据本意是排除
296
- * 快失败),下一条连接照样再发一批 —— 正是这套机制要防的放大。等齐之后,"探针还在飞"与"后端还有
297
- * 我发出去的活"这两件事重新同义,代际熔断的判据才成立。
298
- */
299
142
  async function allOrFirstFailureAfterAllSettled(work) {
300
143
  const settled = await Promise.allSettled(work);
301
144
  const values = [];
302
145
  for (const r of settled) {
303
146
  if (r.status === "rejected")
304
- throw r.reason; // 第一支失败的原因原样上抛(调用方统一收成 fail-open)
147
+ throw r.reason;
305
148
  values.push(r.value);
306
149
  }
307
150
  return values;
308
151
  }
309
- /** 读本体(抛错/超时由 {@link recentTerminalWorkflowRows} 统一收成 fail-open)。 */
310
152
  async function readRecentTerminalWorkflowRows(store, scope) {
311
153
  const cutoff = Date.now() - FLEET_SNAPSHOT_TERMINAL_WINDOW_MS;
312
- /** 窗判据的时间轴 = 「这一行最后变成现在这样是什么时候」:终态行恒有 `endedAt`,回落 `createdAt`
313
- * (与 core `reap` 的 `endedAt ?? createdAt` 同一口径——两处对「这行多老」必须给同一个答案)。 */
314
154
  const settledAt = (r) => r.endedAt ?? r.createdAt;
315
- // 每个终态各扫一页(店侧下推 status + limit),再按结束时刻归并。不用"一次不带 status 的取页":那会被
316
- // 同 scope 里更新的 running 行挤掉真正要找的终态行。
317
155
  const pages = await allOrFirstFailureAfterAllSettled(TERMINAL_WORKFLOW_STATUSES.map((status) => store.listByScope(scope, { status, limit: FLEET_SNAPSHOT_TERMINAL_SCAN_LIMIT })));
318
156
  const windowed = pages
319
157
  .flat()
320
158
  .filter((s) => s.scope === scope && settledAt(s) >= cutoff)
321
- .sort((a, b) => settledAt(b) - settledAt(a) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) // 同刻结束按 id 定序:快照不许随机
159
+ .sort((a, b) => settledAt(b) - settledAt(a) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
322
160
  .slice(0, FLEET_SNAPSHOT_TERMINAL_MAX_ROWS);
323
- // summary 不带 doneCount/startedCount(core `WorkflowRunSummary` 没有这两位),而面板行的其余供数都在
324
- // 它上面——与其在这里另写一份"近似"投影(两份投影必漂,本仓在 startedCount 口径上已吃过一次),不如
325
- // 取回整行走**同一个** {@link buildFleetWorkflowRow}。代价=每行一次主键点读,受行数上限硬界(≤20),
326
- // 且只发生在开流那一拍、同 scope 并发还会合流。
327
161
  const runs = await allOrFirstFailureAfterAllSettled(windowed.map((s) => store.get(s.id)));
328
162
  const rows = [];
329
163
  for (const [i, run] of runs.entries()) {
330
164
  if (!run)
331
- continue; // list 与 get 之间被 reap 掉:诚实跳过,不为一个不存在的 run 造行
332
- // `store.get` 是**无 scope 门**的裸读(带门的是 core 的 `getWorkflowRun`)——id 虽然来自本 scope 的
333
- // 列表,仍按真行的 scope 复核一次:租户轴上宁可多判一次,也不靠"来源可信"。
165
+ continue;
334
166
  if (run.scope !== scope)
335
167
  continue;
336
168
  if (!isTerminalWorkflowStatus(run.status) || settledAt(run) < cutoff)
337
- continue; // 读回时已不在窗内/已不是终态
169
+ continue;
338
170
  rows.push(buildFleetWorkflowRow(windowed[i].id, run));
339
171
  }
340
172
  return rows;
@@ -1,30 +1,6 @@
1
- /**
2
- * SubagentTailBus(design/151 S2 server 半场,[1520]{core} 提货单)——per-agent **live tail** 的进程内
3
- * 分发总线。core 1.370 起后台子代 lane 向宿主 `onForwardEvent` 转发的每一帧带 `bgAgentId`(a* 句柄,
4
- * TaskEventIdentity 顶层平铺;sync/steer 子代无 tag,wa* 走自己的 observer lane)——两条 run 腿
5
- * (sync=http/server.ts、bg=runs.ts)的 fwdInternals 按 tag 存在性把帧 publish 进来,
6
- * `GET /v1/runs/:id/subagents/:handle/stream` 按句柄订阅。
7
- *
8
- * 语义边界(与 [1497] A2 的「replay+tail」故事各占一半):
9
- * - **嵌套归并**(core 复审发现 #5):core recordingForward 在每层 bg 边界覆写 bgAgentId——host wire 上
10
- * 整棵委托子树的帧一律带**最外层** a* 句柄;对嵌套 a* 行开 tail 流=只有 heartbeat(它的帧归并进外层
11
- * 句柄的流,按 parentToolCallId 区分)。core JSDoc 补句已 ask。
12
- * - **live-only**:从订阅时刻起,replica-local(帧只在宿主 run 所在副本产生)——历史/终报走 durable
13
- * replay 面(`GET .../subagents/:handle/output` + AgentTranscript),meta 帧如实声明。
14
- * - **有界背压**:每订阅者有界队列(TAIL_QUEUE_CAP),慢消费者丢**最旧**帧并在下一次 drain 前插入一枚
15
- * `{type:"lagged", dropped}` 诚实标记帧——绝不无界缓冲(死对端=内存增长),绝不静默丢。
16
- * - **投影在 publish 侧一次**(projectTailFrame):与 sync 主流的 forward 投影同 builder 同纪律
17
- * (§E1:tool 帧走 whitelist+redact builder,text verbatim=owner 自己的模型输出同信任类,
18
- * reasoning 过 redactSecrets)——每帧投影一次,N 个订阅者共享同一投影对象(只读约定)。
19
- *
20
- * 订阅形 = subscribeWorkflow 同姿势:push 迭代器,消费端显式驱动,断连调 `it.return()` 清订阅
21
- * (parked next() 不观察 closed 旗——不清理=死订阅者队列永驻)。
22
- */
23
1
  import { redactSecrets } from "../trace/redact.js";
24
2
  import { taskProgressEventData, toolStartEventData, toolEndEventData } from "../trace/project.js";
25
3
  const TAIL_QUEUE_CAP = 512;
26
- /** core forward 事件 → wire 投影(sync 主流五分支同源;不认识的帧类型=null 不发——tail 面只承诺
27
- * content 五类,新 core 帧类型先经这里显式收编再出 wire)。 */
28
4
  export function projectTailFrame(e) {
29
5
  const t = e.type;
30
6
  if (t === "task_progress") {
@@ -32,11 +8,11 @@ export function projectTailFrame(e) {
32
8
  }
33
9
  if (t === "tool_start") {
34
10
  const tsv = e;
35
- return { type: "tool_start", ...toolStartEventData(tsv) }; // label 现由 builder 自带(1.258),外拼冗余已清
11
+ return { type: "tool_start", ...toolStartEventData(tsv) };
36
12
  }
37
13
  if (t === "tool_end") {
38
14
  const te = e;
39
- return { type: "tool_end", ...toolEndEventData(te) }; // label 现由 builder 自带,外拼冗余已清(同 tool_start 1.258 先例)
15
+ return { type: "tool_end", ...toolEndEventData(te) };
40
16
  }
41
17
  if (t === "text_delta") {
42
18
  const td = e;
@@ -48,9 +24,8 @@ export function projectTailFrame(e) {
48
24
  }
49
25
  return null;
50
26
  }
51
- /** 有界 put(FIFO 逐最老)——`Map` 的迭代序即插入序,取首键即最老。 */
52
27
  function putBounded(map, key, value) {
53
- map.delete(key); // 重写=刷新它的插入序(值真变了的活句柄不该按最初那次登记的年龄被逐)
28
+ map.delete(key);
54
29
  map.set(key, value);
55
30
  if (map.size > HOST_MODE_CAP) {
56
31
  const oldest = map.keys().next();
@@ -58,59 +33,21 @@ function putBounded(map, key, value) {
58
33
  map.delete(oldest.value);
59
34
  }
60
35
  }
61
- /** 句柄→供给形登记的容量上限(FIFO 逐最老)。诊断料,不是账本——满了逐最老,读侧缺席退回 "unknown"
62
- * (诚实降级:把"我知道"退成"我不知道",不会退成一个错答案)。 */
63
36
  const HOST_MODE_CAP = 4096;
64
37
  export class SubagentTailBus {
65
38
  subs = new Map();
66
- /**
67
- * a\* 句柄 → 该句柄**自己的宿主腿**的供给形。
68
- *
69
- * 🔴 键必须是**句柄**,不是「查看时用的那条 run」(codex 复审 high,2026-08-14 采纳):
70
- * session-scoped 子代(`backgroundScope:"session"`)活得比派它的那条 run 久,而订阅方寻址用的是
71
- * 「派它的 run ?? 当前活跃 run」——同一会话里 A 腿(带 forwardSubagentEvents)派的子代,完全可能
72
- * 被经**后起的 B 腿**(没带)去看。按 run/会话键答题在这一档会**确定性谎报**(A=on 被答成
73
- * progress_only ⇒ 客户端提前放弃真内容;反向 ⇒ 永久空等)。按句柄登记则天然正确:登记发生在
74
- * **产出这一帧的那条闭包**里,帧上的 bgAgentId 与它的宿主腿是同一件事,不经任何寻址推断。
75
- *
76
- * 时间轴与降级:子代产出第一帧之前 = 无登记 ⇒ 路由出 `"unknown"`(诚实:还不知道);宿主腿在别的
77
- * 副本 ⇒ 本副本永远收不到它的帧 ⇒ 同样 `"unknown"`(而不是拿本地同会话的无关腿冒充);容量逐出
78
- * 同理只会把 `"on"/"progress_only"` 退成 `"unknown"`,不会退成错答案。
79
- * ⚠️ `progress_only` 形下 task_progress **照来**,所以那一档同样在子代首拍 tick 就登记上了——
80
- * 不需要"先有内容帧"才能学到"没有内容帧"。
81
- */
82
39
  modeByHandle = new Map();
83
- /** 三条 run 腿的 forward 回调里逐帧调(在 `hasSubscribers` 快门**之前**——没人在听时也要学,
84
- * 否则晚开的 tail 永远读不到供给形)。同值重复写是 no-op(热路径只付一次 Map.get)。 */
85
40
  noteHandleContentMode(bgAgentId, mode) {
86
41
  if (this.modeByHandle.get(bgAgentId) === mode)
87
42
  return;
88
43
  putBounded(this.modeByHandle, bgAgentId, mode);
89
44
  }
90
- /**
91
- * 句柄终态时**撤销**登记(三条腿的 `onTaskNotification` 观察者调,与 `task_settled` 帧同处)。
92
- *
93
- * 🔴 为什么必须撤(codex 复审 round2 high,2026-08-14 采纳):后台子代可以用**原 a\* 句柄**进入下一个
94
- * 复活周期(`POST /v1/runs/:id/subagents/:target/resume` → core `registerBackgroundAgent({ id:
95
- * reviveRow.handle })`),而新周期的宿主腿是**另一条 run**,它的 forwardSubagentEvents 完全可能相反。
96
- * 不撤的话,新周期首帧之前 meta 会把**上一周期**的供给形当成本周期的答案:上周期 on / 本周期
97
- * progress_only ⇒ 客户端等一个永远不来的内容;反向 ⇒ 提前放弃真内容。撤到 `"unknown"` 是唯一诚实的
98
- * 中间态(新周期首帧一到自然重新登记)。
99
- * 非 a\* 句柄(bash / monitor 那些从未登记过的句柄)进来是 no-op。
100
- *
101
- * 残余(如实记,不假装没有):撤销之后若还有**同周期的迟到帧**进来,会按那条腿的模式重新登记一次
102
- * ——值仍是「刚结束那个周期」的真话,只是万一紧接着发生复活、且新周期首帧未到,那一瞬读到的是旧周期
103
- * 的答案。core 今天把终态通知排在最后一条 forward 之后(fleet-bus 同注),复活又是人手动作,这条窗
104
- * 实际上不可达;真要关死需要给句柄加墓碑态,代价大于一位诊断标签的收益,故不做。
105
- */
106
45
  forgetHandleContentMode(bgAgentId) {
107
46
  this.modeByHandle.delete(bgAgentId);
108
47
  }
109
- /** 路由读(按 tail 的 target 句柄)。缺席 = 不知道,路由出 `"unknown"`。 */
110
48
  handleContentMode(bgAgentId) {
111
49
  return this.modeByHandle.get(bgAgentId);
112
50
  }
113
- /** 两条 run 腿的 fwdInternals 调用点:帧已投影(projectTailFrame),null 不进来。 */
114
51
  publish(bgAgentId, frame) {
115
52
  const set = this.subs.get(bgAgentId);
116
53
  if (!set)
@@ -126,7 +63,6 @@ export class SubagentTailBus {
126
63
  sub.wake?.();
127
64
  }
128
65
  }
129
- /** 有无订阅者(publish 侧的免投影快门:没人听就不投影)。 */
130
66
  hasSubscribers(bgAgentId) {
131
67
  return (this.subs.get(bgAgentId)?.size ?? 0) > 0;
132
68
  }
@@ -139,9 +75,6 @@ export class SubagentTailBus {
139
75
  }
140
76
  set.add(sub);
141
77
  const cleanup = () => {
142
- // 复审 #2(实证):路由对同一迭代器 return() 两次(close 钩+finally,设计如此)——非幂等 cleanup 在
143
- // 两次调用之间若有新请求 subscribe 同句柄(map 重建新集),第二次会把**新订阅者的集**从 map 删掉
144
- // (静默只剩 heartbeat)。幂等门+集同一性校验双保险。
145
78
  if (sub.closed)
146
79
  return;
147
80
  sub.closed = true;
@@ -180,7 +113,5 @@ export class SubagentTailBus {
180
113
  return it;
181
114
  }
182
115
  }
183
- /** 进程单例(defaultTaskRegistry 同款姿势):bus 语义本就是进程内 replica-local,两条 run 腿与路由
184
- * 直接 import,不穿长参数列表;测试自 new 独立实例。 */
185
116
  export const defaultSubagentTailBus = new SubagentTailBus();
186
117
  //# sourceMappingURL=subagent-tail-bus.js.map