@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,9 +1,6 @@
1
1
  import { resolveStreamApprovalGate } from "../../tool-approval.js";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
4
- /** 纯数据产物(`build*`):三个门的当前读数。`streamApproval` 走**单一谓词**
5
- * `resolveStreamApprovalGate` —— 与 `/v1/capabilities` 的 `streamApproval` 位、与回决口的 501 门同一个
6
- * 符号,所以诊断页读到的与消费端撞到的行为不可能不同。 */
7
4
  export function buildServerWiringGates(input) {
8
5
  const gate = resolveStreamApprovalGate({
9
6
  toolApprovalEnabled: input.toolApprovalEnabled,
@@ -17,25 +14,6 @@ export function buildServerWiringGates(input) {
17
14
  checkpointStore: input.checkpointStore !== undefined,
18
15
  };
19
16
  }
20
- /**
21
- * 三项对表(设计稿件二)。判据全是**两个独立谓词对同一件事的答案**,矛盾即装配谎言 —— 装配谎言不是
22
- * 「配置不理想」,是「服务端对自己接了什么线的两套说法互斥」,继续启动只会把矛盾带进运行期的每一条腿。
23
- *
24
- * ① park 设施:core 从 `spec.checkpointStore ?? deps.checkpointStore` 判 `parkLane.capable`,我方从
25
- * `backend.checkpoint ∧ DURABLE_APPROVAL` 判 `parkFacility` 并据此决定流内审批协议上不上场。两者
26
- * 不等 ⇒ 协议按「窗到期能 park」上场而引擎侧根本不 park(结局是 core fail-closed deny),或反之
27
- * 白白关掉一个可用的协议。`capable` 真而 `effective` 不真同样致命:能力在、这条部署形上却不会真的
28
- * park,窗到期依旧无降级目的地。
29
- * ② 会话店持久性:manifest 声明 `declared_durable` 而后端是 local / 根本没有后端 ⇒ 「重启存活」的声明
30
- * 没有介质兑现。这条与 memory+durable 两条既有拒启同族(声明与介质必须对得上)。
31
- * ③ interaction posture 声明 `interactive` 却没有任何 question 投递面 ⇒ 只 **warn**:core 在 prepare 时
32
- * 会按腿拒,boot 先一步把话说成人话。不拒启,因为部署形本身可能只是暂时没接活体面,而 core 侧已有
33
- * 兜底门 —— 拒启会把一个可运行的部署挡在门外。
34
- * ④ park 车道的持久性读数不可信时 ⇒ **warn**(理由与这条判据为什么不是拒启,见其行内注)。
35
- *
36
- * 判决分两级是有判据的:**拒启**留给「两个谓词对同一件事互斥」(其中一个必错,继续跑就是带着谎言服务);
37
- * **警告**留给「读数不可信/声明不完整」(信号本身还不足以断定谁错,据此拒启会挡住正常部署)。
38
- */
39
17
  export function buildStaticWiringAudit(facts) {
40
18
  const refusals = [];
41
19
  const warnings = [];
@@ -49,23 +27,10 @@ export function buildStaticWiringAudit(facts) {
49
27
  refusals.push(`park facility contradiction: this server assembled its approval gates with parkFacility=true, but the engine's static wiring resolves parkLane.effective=${JSON.stringify(park.effective)} (reasons: ${park.reasons.join(", ") || "none"}). ` +
50
28
  `A window expiry would then have no degradation target and adjudicate as a fail-closed deny — refusing to start.`);
51
29
  }
52
- // 判据写成「不在持久集合里」而不是「是 local」是有方向的:将来多一种 backend kind 时,它会**拒启**
53
- // 直到有人显式把它判进持久集,而不是静默通过 —— 声明与介质的对表,失败方向必须朝拒绝那边。
54
30
  if (facts.manifest.session.store === "declared_durable" && facts.backendKind !== "mysql" && facts.backendKind !== "pg") {
55
31
  refusals.push(`session store durability contradiction: the engine's static wiring reports session.store="declared_durable" while the store backend is ${facts.backendKind === undefined ? "absent (env-only worker)" : `"${facts.backendKind}"`}. ` +
56
32
  `A durable declaration with no durable medium is a claim nothing can honor — refusing to start. Wire DB_BACKEND=mysql|pg, or fix the store's durability declaration.`);
57
33
  }
58
- // park 车道的**持久性读数**(codex 交叉复审 2026-08-07 R2-F2,验真后部分采纳)。`capable` 说的是
59
- // 「有没有 checkpoint 店」、`effective` 说的是「这条部署形会不会真 park」—— 两者都为真时仍可能是一个
60
- // 重启即丢的店,那样窗到期后被 park 的审批在重启后无法赎回,而流内审批协议正是拿 park 当降级目的地。
61
- // ✅ **读数已转可信**(#168 件5,2026-08-07):树上三条 checkpoint 店(SQL 双生 + local 文件形)现在都
62
- // 如实声明了 `durability`,所以这条读数不再对真持久部署恒假 —— 它现在说的就是字面意思。
63
- //
64
- // 🔴 仍然是 **warn 而不是拒启**,判据与 session 那条**刻意不同**,理由写在这里免得下一个人以为是漏改:
65
- // session 那条对表的是「声明 durable × 介质不 durable」——两个已知量互相矛盾,拒启是查出了谎。这一条
66
- // 对表的是「有没有人声明」,而 core 把**缺席**折成 `process-local`;一个合法的第三方/新增 checkpoint
67
- // 店没表态时,拒启会挡住一个本来跑得好好的部署,而**没有任何旋钮**能让它继续启动(补偿为零 ⇒ 不升格)。
68
- // 升格的前置件 = core 把「未声明」与「声明为进程内」分成两个可区分读数,或本仓给出一个显式承认旋钮。
69
34
  if (facts.parkFacility && facts.manifest.parkLane.checkpointDurability !== "declared_durable") {
70
35
  warnings.push(`park facility is enabled but the checkpoint store does not declare durable storage ` +
71
36
  `(parkLane.checkpointDurability=${JSON.stringify(facts.manifest.parkLane.checkpointDurability ?? null)}). ` +
@@ -78,10 +43,6 @@ export function buildStaticWiringAudit(facts) {
78
43
  }
79
44
  return { refusals, warnings };
80
45
  }
81
- /**
82
- * boot 消费口:`refusals` 非空即抛(拒启,与 memory+durable 两条既有拒启同族的 fail-loud 形),
83
- * `warnings` 逐条 warn。返回判决本体,调用方可以继续把读数打进启动 info 行。
84
- */
85
46
  export function assertStaticWiringConsistent(facts, logger) {
86
47
  const audit = buildStaticWiringAudit(facts);
87
48
  for (const w of audit.warnings)
@@ -91,24 +52,10 @@ export function assertStaticWiringConsistent(facts, logger) {
91
52
  }
92
53
  return audit;
93
54
  }
94
- /**
95
- * [3399]③ clay 裁 (b)(2026-08-12,core [3648]④ + cli [3649]① 双背书):**零凭证 turnkey 形**的
96
- * loopback 来源门判据(单一属主,端点与测试同吃)。
97
- *
98
- * 只在「operator 名单空 ∧ 非多租户 ∧ 全局 service-credential 门也不在场(没配 SERVICE_AUTH_TOKEN /
99
- * AUTH_TOKENS)」的部署形上判——那正是全局门整个不设、诊断端点此前无认证可读的形。诊断面泄的是装配
100
- * 拓扑(store 接线/能力位/治理姿态),对误暴露公网端口的 turnkey 机器构成侦察面。(b) 对本机自用零摩擦
101
- * (壳缺省钉 BIND_HOST=127.0.0.1,local curl 照常),对远程侦察 fail-closed;配了 token 的部署远程读
102
- * 走认证(全局门),不进本臂。
103
- *
104
- * 判源=TCP 对端地址(`req.socket.remoteAddress`),**非任何头**——XFF 类头在无反代的 turnkey 形上本就
105
- * 是伪造面;有反代的部署必然配 token(反代后 remoteAddress 恒 loopback,门自然放行,语义仍对:该形的
106
- * 边界由反代+token 承担)。`remoteAddress` 缺席(socket 已断等边缘)⇒ 判拒(fail-closed,安全轴方向)。
107
- */
108
55
  export function zeroCredentialLoopbackDenied(remoteAddress, config) {
109
56
  const anyServiceAuth = Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
110
57
  if (anyServiceAuth)
111
- return false; // 凭证在场 ⇒ 边界归全局 service-credential 门,本判不参与
58
+ return false;
112
59
  const addr = remoteAddress ?? "";
113
60
  const isLoopback = addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
114
61
  return !isLoopback;
@@ -120,22 +67,6 @@ export async function handleDiagnostics(req, res, url, ctx) {
120
67
  }
121
68
  async function handleDiagnosticsBody(req, res, url, ctx, miss) {
122
69
  const { deps } = ctx;
123
- // 件三:「这台 worker 到底接了什么线」一屏看清 = core 的静态半场(含 `governance` 段 —— 本面**就是**
124
- // 那个段的 operator 受众)+ server 自己三个装配谓词的读数。
125
- //
126
- // 门 = `explicitOperatorOk`(**不是** `isOperator`)。执法条件是**名单在场 ∨ 多租户**,不是只看
127
- // `REQUIRE_PRINCIPAL`:本件的 SSE 腿对同一段 governance 恒判 `explicitOperatorOk`,若这里只在
128
- // REQUIRE_PRINCIPAL 时判,「列了 operator 名单却没开 REQUIRE_PRINCIPAL」的部署上就成了流里关着、
129
- // 端点上开着 —— 一个秘密两套门,严的那道白严(codex 交叉复审 2026-08-07 R2-F1,验真后采纳)。
130
- // 名单为空 ∧ 非多租户 = 单租户 turnkey:该部署没有 operator 这个概念,唯一的用户就是 operator,
131
- // 边界由全局 service-credential 门把守(与 `GET /v1/outcomes` 同裁)。
132
- // 401 由更上游的全局门给出,本域只判 operator 与否。
133
- //
134
- // ⚠️ 与 SSE 腿的**残余不对称**(显式裁定,不是疏忽):turnkey 形上流里仍然谁都拿不到治理段,而本端点
135
- // 给。理由=流是多订阅面且身份来自连接、端点是一次显式的 operator 请求;失败方向因此在流那侧更严。
136
- //
137
- // **不含** per-leg effective 历史:治理段只存在于 live 帧的 operator 投影里,durable 账本恒存剥后形
138
- // (账本写时不知道未来读者是谁)。历史考古是另一件事,不在本面伪装成有。
139
70
  if (req.method === "GET" && url === "/v1/diagnostics/wiring") {
140
71
  if (deps.config.operatorPrincipals.length > 0 || deps.config.requirePrincipal) {
141
72
  const principal = gatedPrincipal(req, deps.config);
@@ -148,25 +79,14 @@ async function handleDiagnosticsBody(req, res, url, ctx, miss) {
148
79
  sendError(res, 403, "auth.loopback_only", "wiring diagnostics on a zero-credential deployment are readable from loopback only — configure SERVICE_AUTH_TOKEN to read remotely");
149
80
  return;
150
81
  }
151
- // 静态半场是 **composition root 在 boot 算的**(与拒启自检同一份产物,不可能与它不同)。缺席 ⇒ 这个
152
- // 进程不是由 main.ts 装起来的(测试夹具形),此面诚实地不存在 —— 落到全局 404,而不是回一个空壳 200。
153
82
  if (!deps.staticWiring) {
154
83
  miss.fell = true;
155
84
  return;
156
85
  }
157
86
  sendJson(res, 200, {
158
87
  static: deps.staticWiring,
159
- // #252 件2:记忆面姿态(backend / vectorMode / 点亮态 / dark 成因 / embedder 身份)。此前这些事实
160
- // **只**活在启动日志里,而日志不可查询、跨副本不可聚合 —— cli 的记忆 UX 与运维都只能读日志或按 env
161
- // 自己推(推不出来:多租户 file 底座上 `MEMORY_ENGINE=on` 其实是暗的)。additive 键,与 `serverGates`
162
- // 同受众(operator-only,门在上方,本段不自带更松的门)。
163
- // 缺席 ⇒ 显式 `null`(「这个进程说不出来」),不铸一个看起来像真答案的空壳对象。
164
88
  memoryPosture: deps.memoryPosture ?? null,
165
- // #322 世代账的 operator 面:按组的 appliedVersion / deferredKeys+铸因 / lastRejected(消毒形——
166
- // 只有键名与判据文案,值从不进读面)。/health 那三键是同一份读数的窄投影,两面不可能各说各话。
167
- // 缺席 ⇒ 显式 `null`(与 memoryPosture 同姿势:「这个进程说不出来」,不铸空壳)。
168
89
  configApply: deps.configApplyState?.() ?? null,
169
- // #324:mid-turn MCP 撤销名单(design/338)。null=本进程没有配置管道(说不出来),与上两键同姿势。
170
90
  mcpRevocations: deps.mcpRevocationState?.() ?? null,
171
91
  serverGates: buildServerWiringGates({
172
92
  toolApprovalEnabled: deps.toolApproval !== undefined,
@@ -7,106 +7,42 @@ export async function handleFleet(req, res, url, ctx) {
7
7
  await handleFleetBody(req, res, url, ctx, miss);
8
8
  return !miss.fell;
9
9
  }
10
- /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
11
- * = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
12
10
  async function handleFleetBody(req, res, url, ctx, miss) {
13
11
  const { deps } = ctx;
14
12
  const { sessionOwnerScope } = ctx.helpers;
15
- void url; // 本域按 `req.url` 的完整 pathname 判路(带 query),`url` 只为域入口签名一致
16
- // MF-Fleet (shell-host data contract): GET /v1/fleet/stream — the LIVE multi-row task/workflow fleet view as
17
- // an SSE PUSH stream (status/elapsed/tokens/doneCount transitions over time). Owner-gated by scope (the
18
- // caller's own active runs) UNLESS fleet-wide (a deployment trace/auth token sees every tenant's rows, like
19
- // the trace API). Before the service-credential gate (self-gates by principal), parity with the workflow routes.
13
+ void url;
20
14
  if (req.method === "GET" && new URL(req.url ?? "", "http://x").pathname === "/v1/fleet/stream") {
21
15
  if (!deps.fleetBus) {
22
16
  sendError(res, 501, "capability.self_orchestration_required", "fleet stream requires self-orchestration / fleet wiring");
23
17
  return;
24
18
  }
25
- const { fleetWide } = sessionOwnerScope(req); // trace-token / system authToken ⇒ fleet-wide (all tenants)
26
- const principal = gatedPrincipal(req, deps.config); // direct-door-safe identity (NOT the spoofable header)
19
+ const { fleetWide } = sessionOwnerScope(req);
20
+ const principal = gatedPrincipal(req, deps.config);
27
21
  if (deps.config.requirePrincipal && !principal && !fleetWide) {
28
22
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
29
23
  return;
30
24
  }
31
- // #3 fleet per-session scope (clay 2026-06-30): the shell sends its current sessionId as `?session=` to see ONLY
32
- // this session's rows (not other sessions of the same principal — the "他会话 token" leak). Absent ⇒
33
- // unscoped (all of the principal's rows — back-compat).
34
- // 🔴 cli[1524] HIGH-1(V5c 回归锁首跑抓获):此前 fleetWide 把 callerSession 也强制 null——无 auth
35
- // 部署(isFleetWide 第一臂:无 token/无 traceToken/无 requirePrincipal=恒 fleetWide,TOC loopback
36
- // 默认形全中)下会话过滤/通知 fail-closed 门整体旁路成全量广播,而 meta 的 bgNotifyFailClosed 照报
37
- // true=标说谎;1.245 的注入 fail-closed 与 1.247 的代际标在默认部署从未真正生效(一直是壳侧台账
38
- // 在兜)。修:fleetWide 是**能力上限**(可看全量=豁免 scope 授权轴),不是「强制 unscoped」——
39
- // 显式带 `?session=` 的请求无论 fleetWide 与否都按会话过滤(观察者要全量=不带 session;要跟单
40
- // 会话且不烧 durable 副本=带 session+observe=1,既有防烧口)。meta 的 sessionScoped 由此自动如实,
41
- // bgNotifyFailClosed 对 session-bound 连接从此真实。
42
25
  const fleetParams = new URL(req.url ?? "", "http://x").searchParams;
43
26
  const callerSession = fleetParams.get("session");
44
- // `?observe=1` = a session-bound lane that must NOT serve-fence the durable copy
45
- // (a non-shell observer following one session). Default (absent) keeps the frame-即-投递 semantics —
46
- // today's only session-bound consumer IS the model-injecting shell.
47
27
  const observeOnly = fleetParams.get("observe") === "1";
48
- // #189:终态行窗的 durable 读交给 streamFleet 在**订阅注册之后**做(见那里的缓冲段)——放在这里
49
- // (开流之前)会让"帧已扇出而本连接尚未订阅"的窗口凭空多出一个读的时长,codex R1-H1 红先复现过。
50
- // [3156] 轮2 真红修:终态窗的查询键必须与可见性判据**同源**——fleetWide 连接(callerScope=null,
51
- // visW 放行全 scope)此前窗却按 caller principal("default")查,而行的 scope 是提交方自报
52
- // principal 的透传值(cli 壳本地单用户模式自发 "anon:shell-live";引擎不发明该字面量,[3225]
53
- // 后续侦察定谳)⇒ durable pull 恒 miss。fleetWide ⇒ null(窗走进程内 seed 缓存,全 scope)。
54
- // 读侧 `?? "default"` 三处与 core 写侧铸链同源(core prepare-task `spec.principal ?? "default"`,
55
- // 其 CHANGELOG 成文「Single-tenant spelling is an explicit scope:"default"」)——在 core 撤其
56
- // 写侧铸点前,server 单方面撤读侧会把无主部署的 durable pull 面弄瞎,故保持同源不动。
57
28
  await streamFleet(req, res, deps.fleetBus, fleetWide ? null : (principal ?? "default"), callerSession || null, observeOnly ? undefined : deps.workflowCompletionInbox, { store: deps.workflowRunStore, scope: fleetWide ? null : (principal ?? "default"), pullScope: principal ?? "default", ready: deps.fleetRecoveryDone });
58
29
  return;
59
30
  }
60
31
  miss.fell = true;
61
32
  }
62
- /** MF-Fleet: SSE the live fleet (active task + workflow rows) — a SNAPSHOT on connect, then every per-row delta
63
- * the {@link FleetEventBus} fans out. `callerScope === null` ⇒ fleet-wide (a trace/auth token sees all tenants);
64
- * else owner-scoped (only rows whose `scope` matches). The tenant-internal `scope` field is STRIPPED from every
65
- * emitted row (it's a gate input, never rendered). A removal is forwarded ONLY for a row this connection actually
66
- * saw (no id-existence oracle for an unseen tenant's run). Heartbeat + dual-close cleanup unsubscribe the bus. */
67
33
  function streamFleet(req, res, bus, callerScope, callerSession = null, completionInbox, terminal) {
68
34
  return new Promise((resolve) => {
69
35
  sseHeaders(res);
70
- // [1508] 1.247 `bgNotifyFailClosed` — the GENERATION marker cli asked for ([1500]📌/[1502] F2): declares
71
- // this server's bg_notification injection path fails CLOSED for scoped subscribers (1.245+ [1493] fix), so
72
- // a new-generation shell can drop its frame-level own/foreign checks entirely (incl. the cross-shell
73
- // restart edge) while keeping them against older servers whose meta lacks the field. Additive (version
74
- // stays 1): older shells ignore unknown fields.
75
- // `type: "meta"` 与 `event: meta` 同名双发(tasks/trace-usage/approvals 三流同款):部分代理只转发
76
- // `data:` 行,按 data.type 分派的消费端否则认不出首帧。additive,老消费端忽略未知键。
77
- // [4119] P-31 Q2(cli design/285,additive):session-bound 连接回显**本连接实际生效**的 session id。
78
- // 客户端 `server_fail_closed` 可信臂零内容自校——它信的是「server 按会话过滤了」这句话本身;把过滤
79
- // 判据说出来,客户端才能与本 ledger 会话锚**比对相等**后解封最强档,而不是在 keyed 多会话宿主上
80
- // 永远诚实封顶。键与 `sessionScoped` 对偶:unscoped 连接缺席(不发空串);值=`callerSession` 逐字
81
- // (即 streamFleet 真用于行过滤的那一个量,不另铸第二读点)。
82
36
  res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, scoped: callerScope !== null, sessionScoped: callerSession !== null, ...(callerSession !== null ? { session: callerSession } : {}), bgNotifyFailClosed: true })}\n\n`);
83
37
  const seenTasks = new Set();
84
38
  const seenWf = new Set();
85
- // #3 fleet per-session scope (clay 2026-06-30): a row is visible iff it matches the caller's principal scope AND
86
- // — when the caller sent `?session=` — its `sessionId`. A row WITHOUT a sessionId stays visible (back-compat for
87
- // unscoped rows); the leak this closes is a row whose sessionId is ANOTHER session of the same principal.
88
39
  const sessionOk = (rowSession) => callerSession === null || rowSession === undefined || rowSession === callerSession;
89
40
  const visT = (r) => (callerScope === null || r.scope === callerScope) && sessionOk(r.sessionId);
90
41
  const visW = (r) => (callerScope === null || r.scope === callerScope) && sessionOk(r.sessionId);
91
- // [1493] fable5 R1 (display plane): an UNATTRIBUTED row (no sessionId — restart lazy-mint / core version
92
- // skew edges; post-Part-A every normal spawn is tagged) stays fail-open VISIBLE to a scoped subscriber
93
- // (liveness back-compat: your own row after a restart still shows as running), but its content fields are
94
- // conversation-derived text — `name` is the objective preview, `description`/`agentType`/`agentName` label
95
- // the ask, `currentAction` is a live tool line. None of that may cross the session boundary, so a SCOPED
96
- // subscriber gets a content-scrubbed projection (ids/status/progress/timing kept); a fleet-wide observer
97
- // (trace/dashboard, callerSession === null) keeps the full row. Rows tagged with the caller's own session
98
- // pass sessionOk and are never scrubbed (r.sessionId present ⇒ it equals callerSession here).
99
- // codex R6:FleetTaskRow 逐键处置表——`satisfies Record<keyof FleetTaskRow,…>` 使**任何**行新键先在
100
- // 编译期红(TS2741),强迫对跨会话可见性表态;"keep" 键必须同步进下方白名单构造,配套钉在
101
- // test/fleet-bg-child.test.ts(R6 格:kept 键集 ⊆ 表内 keep∪rewrite 集)。
102
42
  const SCOPED_TASK_KEY_DISPOSITION = {
103
43
  id: "keep", parentId: "keep", parentToolCallId: "keep", workflowRunId: "keep", status: "keep",
104
44
  startedAt: "keep", elapsedMs: "keep", tokens: "keep", queuedCount: "keep",
105
45
  awaitingPlanApproval: "keep", toolUses: "keep", usage: "keep", resumable: "keep", sourceLane: "keep",
106
- // #261 §2:两键都是**结构事实**,零会话内容 —— `cycleSeq` 是 core registry 行的代际号
107
- // (数值,消费端判「复活 vs 前代迟到帧」的唯一钥匙),`retiredBy` 说的是「这条终态是对账腿从
108
- // durable 投影的、不是发布方亲报的」。降权投影里丢掉它们 = 未署名会话的行**判不了代**,而复活
109
- // 与迟到帧在无代际号时不可区分(正是本 issue 要消灭的那类静默错判)。故 keep。
110
46
  cycleSeq: "keep", retiredBy: "keep",
111
47
  name: "rewrite", scope: "strip", sessionId: "strip",
112
48
  description: "scrub", agentType: "scrub", agentName: "scrub", currentAction: "scrub",
@@ -116,9 +52,6 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
116
52
  const stripT = (r) => {
117
53
  const { scope: _s, sessionId: _sid, ...rest } = r;
118
54
  if (callerSession !== null && r.sessionId === undefined) {
119
- // [3136]②→codex R6(确认轮):黑名单形被两次击穿(currentTool、editedFiles)——同一信任边界的键
120
- // 靠逐个想起来列,漏一个就是跨会话内容泄漏。改**白名单构造**:kept 对象逐键显式拼,未列键默认
121
- // 不过;新键先过上方处置表的编译期门。
122
55
  return {
123
56
  id: rest.id,
124
57
  status: rest.status,
@@ -135,14 +68,12 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
135
68
  ...(rest.usage !== undefined ? { usage: rest.usage } : {}),
136
69
  ...(rest.resumable !== undefined ? { resumable: rest.resumable } : {}),
137
70
  ...(rest.sourceLane !== undefined ? { sourceLane: rest.sourceLane } : {}),
138
- ...(rest.cycleSeq !== undefined ? { cycleSeq: rest.cycleSeq } : {}), // #261:代际号是结构事实(见处置表)
71
+ ...(rest.cycleSeq !== undefined ? { cycleSeq: rest.cycleSeq } : {}),
139
72
  ...(rest.retiredBy !== undefined ? { retiredBy: rest.retiredBy } : {}),
140
73
  };
141
74
  }
142
75
  return rest;
143
76
  };
144
- // A-002.5(#194 病族 F2):workflow 行与 task 行同一条降权跨会话边界——stripT 的黑名单三次被
145
- // 击穿后改了白名单,stripW 当时漏改(同函数体孪生)。处置表同姿势:行加新键先编译红强迫表态。
146
77
  const SCOPED_WORKFLOW_KEY_DISPOSITION = {
147
78
  id: "keep", status: "keep", doneCount: "keep", totalCount: "keep", failedCount: "keep",
148
79
  startedCount: "keep", elapsedMs: "keep", tokens: "keep",
@@ -153,7 +84,6 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
153
84
  const stripW = (r) => {
154
85
  const { scope: _s, sessionId: _sid, ...rest } = r;
155
86
  if (callerSession !== null && r.sessionId === undefined) {
156
- // 白名单构造:kept 逐键显式拼,未列键默认不过(与 stripT 同形;"keep" 键必须同步进处置表)。
157
87
  return {
158
88
  id: rest.id,
159
89
  status: rest.status,
@@ -168,22 +98,11 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
168
98
  }
169
99
  return rest;
170
100
  };
171
- // [2373]C-12:data 里补 `type`(=event 名)双发——代理剥 `event:` 行/onmessage 忽略具名事件时,
172
- // 按 data.type 分派的客户端在本流拿到的是 undefined(approvals 流注释里的真实约束,同理适用)。
173
- // 单点注入:调用方已带 type 的帧(hook_notice/bg_notification)保持原值。
174
101
  const send = (event, data) => { if (!res.writableEnded)
175
102
  res.write(`event: ${event}\ndata: ${JSON.stringify({ type: event, ...data })}\n\n`); };
176
- /** #189:快照里那些**按窗保留**的 workflow 行 id(来自 durable 终态窗,或握手期间才翻终态的行)
177
- * —— 见 workflow / workflow_remove 两臂。 */
178
103
  const retainedTerminalWf = new Set();
179
- /** 现在是否正在重放握手期缓冲(只在这段里,终态行帧才升级为"保留行")。 */
180
104
  let replayingBuffer = false;
181
- /** [3193] codex R4:首见即终态的历史行保留**预算**(每连接)。retainedTerminalWf 本身不积累
182
- * (每个 id 的撤行帧即刻消费),真正会无界的是「被豁免撤行、留在面板与 seenWf 里的历史行数」。
183
- * 预算=终态窗容量同阶(×2);耗尽后退回旧「终帧→撤行」契约(行瞬态可见)——有界降级,不吞帧。
184
- * 快照径的保留行不占预算(已由窗的行数+窗龄双界)。 */
185
105
  let retainedHistoryBudget = FLEET_SNAPSHOT_TERMINAL_MAX_ROWS * 2;
186
- /** 一条增量帧的处理(**先注册订阅、后组装快照**,故与快照解耦成具名函数——见下方缓冲段)。 */
187
106
  const handleFrame = (frame) => {
188
107
  switch (frame.type) {
189
108
  case "task":
@@ -194,15 +113,6 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
194
113
  break;
195
114
  case "workflow":
196
115
  if (visW(frame.row)) {
197
- // #189 / codex R4-M3 → [3193] ①a 泛化:**本连接首见即终态**的行(重放期或活径皆然)与终态窗
198
- // 里的行是同一件事——它对这条连接从来不是"活跃集成员",是历史;它的撤行帧描述的活跃集离场
199
- // 对本连接无意义,照发只会让"看不看得见刚结束的 workflow"取决于 recover 落在握手窗哪一侧
200
- // (cli [3193] 插桩实证:落在重放窗内=保留可见,落在重放窗后=终帧+撤行背靠背、行瞬现即消)。
201
- // ⇒ 判据=「重放期 ∪ 首见即终态」并集(#189 原判据的严格超集):重放期臂保住"快照见过
202
- // running、缓冲里才翻终态"的行(R4-M3);首见臂保住活径上 recover 晚到的行(①a)。见过
203
- // running 的行在活径仍按既有契约走(终帧→撤行),在线消费端语义不变。豁免仍一次性,集合有界如旧。
204
- // [3193] codex R5:已在保留集里的 id(快照径收的)不重复扣预算——否则握手重放把同一批行
205
- // 按"快照+重放"记两次账,40 预算被快照行白耗光,其后真正的首见历史行立刻失去豁免。
206
116
  if ((replayingBuffer || !seenWf.has(frame.row.id)) && isTerminalWorkflowRowStatus(frame.row.status) && !retainedTerminalWf.has(frame.row.id) && retainedHistoryBudget > 0) {
207
117
  retainedTerminalWf.add(frame.row.id);
208
118
  retainedHistoryBudget--;
@@ -212,9 +122,6 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
212
122
  }
213
123
  break;
214
124
  case "task_remove":
215
- // #261 §2③/§5 Q-d:两个 additive 键随帧透传。撤行帧本就**只投给见过这一行的连接**
216
- // (`seenTasks.delete` 的返回值即判据)—— 这正是 Q-d 那条边界钉的服务端半场:不持行的
217
- // 订阅方连撤行帧都收不到,自然没有"结算"义务。`removeReason` 缺席 = 终态之后的常规清场。
218
125
  if (seenTasks.delete(frame.id)) {
219
126
  send("task_remove", {
220
127
  id: frame.id,
@@ -225,96 +132,41 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
225
132
  }
226
133
  break;
227
134
  case "workflow_remove":
228
- // 🔴 #189 / codex R2-M1:按窗**刻意保留**的历史行不受本连接的撤行帧影响。行在 durable 读
229
- // 期间翻终态时,活写路径的「终帧 + 撤行」会落进缓冲,重放到这里就会把快照刚保住的那一行
230
- // 又撤掉 —— 于是「看不看得见刚结束的 workflow」重新取决于连接时机(晚 1ms 连上的人看得见,
231
- // 撞上这一拍的人看不见),正是本 issue 要消灭的竞态。撤行帧描述的是**活跃集**离场,而这些
232
- // 行进快照时就已经是终态历史,不在活跃集语义里。id 不复用(runId 每次新铸,终态不回滚),
233
- // 故这层豁免既有界又不会误伤后来的真活行。
234
- // 豁免**一次性**:配对的那一帧撤行到达即消费(codex R5-M3)。否则这个 id 被永久噤声——
235
- // 同 id 若之后又出现活行(帧乱序/未来的行复用),它的撤行帧也会被吞,面板留一条撤不掉的死行;
236
- // 顺带,每条长连接的保留集不再随握手期完成数无界增长。
237
135
  if (retainedTerminalWf.delete(frame.id))
238
136
  break;
239
137
  if (seenWf.delete(frame.id))
240
138
  send("workflow_remove", { id: frame.id, ts: frame.ts });
241
139
  break;
242
140
  case "hook_notice": {
243
- // cli [1786]:一次 hook 判定**未能完成**的观测帧。可见性与 bg_notification **同为 fail-CLOSED** ——
244
- // 一条关于**别人会话**的「这轮没看住」落到这个壳上,会让用户以为是自己这轮出了问题
245
- // (而且 `/goal enforced` 面板会据此说一句不属于这个会话的话)。
246
- // ⇒ scoped 订阅方只收能证明是自己的:`ownerSessionId === callerSession`;`undefined` ⇒ **丢弃**。
247
- // fleet-wide 观察者(callerSession === null:trace/dashboard)照收。
248
141
  const hn = frame.notice;
249
142
  const hScopeOk = callerScope === null || hn.ownerScope === callerScope;
250
143
  const hSessionOk = callerSession === null || hn.ownerSessionId === callerSession;
251
144
  if (hScopeOk && hSessionOk) {
252
145
  const { ownerScope: _hs, ownerSessionId: _hsid, ...wire } = hn;
253
- // 与 bg_notification 同一帧契约:`type` 也放进 payload(壳的帧桥按 data.type 分派)。
254
146
  send("hook_notice", { type: "hook_notice", ...wire, ts: frame.ts });
255
147
  }
256
148
  break;
257
149
  }
258
150
  case "bg_notification": {
259
- // design/129-B 缺口②(帧契约): a bg child's completion pushed on THIS always-open lane — the
260
- // idle shell renders the receipt immediately (no more one-input-cycle durable-drain lag). Visibility:
261
- // tenant filter on ownerScope (same rule as rows; a notification without one is operator/fleet-wide
262
- // visible only — fail-closed for scoped callers) + `?session=` on ownerSessionId (the HOST session;
263
- // the payload sessionId is the CHILD's uuid — the contract's stable anchor — and never matches the caller's).
264
151
  const n = frame.notification;
265
152
  const scopeOk = callerScope === null || n.ownerScope === callerScope;
266
- // [1493] fail-CLOSED on the MODEL-INJECTION path. bg_notification is auto-injected into the idle
267
- // shell's model (the receipt drives the LLM's next turn). Unlike the display rows — whose absent-
268
- // session `sessionOk` stays fail-OPEN for back-compat (an untagged row is at worst visible, never
269
- // actionable) — an untagged completion delivered to a SCOPED subscriber would inject a DIFFERENT
270
- // session's result into this shell's model (clay's dual-shell repro: two shells sharing one engine +
271
- // principal). So a scoped caller (callerSession !== null) receives a completion ONLY when it is
272
- // provably its own: ownerSessionId === callerSession. undefined ⇒ NOT proven ⇒ dropped. A fleet-wide
273
- // observer (callerSession === null: trace/dashboard) still sees everything. Part A (fleet-bus) is what
274
- // populates ownerSessionId for the legit owner so this gate delivers to it and drops only strangers.
275
153
  const sessionInjectOk = callerSession === null || n.ownerSessionId === callerSession;
276
154
  if (scopeOk && sessionInjectOk) {
277
155
  const { ownerScope: _os, ownerSessionId: _osid, ...wire } = n;
278
- // The frame contract puts `type:'bg_notification'` INSIDE the object (the shell's frame
279
- // bridge dispatches on data.type, not only the SSE event name) — carry it in the payload too.
280
156
  send("bg_notification", { type: "bg_notification", ...wire, ts: frame.ts });
281
- // The frame IS the delivery when it reaches the OWNING shell's session-bound lane —
282
- // serve-fence the durable park (both orders hold: an already-parked entry is acked; a park landing
283
- // AFTER this is refused by the serve fence) so the next stream-open drain can't re-inject the same
284
- // completion into the model (the engine-side steer-inject double the shell cannot dedup — the
285
- // 129-bg variant of that double-injection class, proven from real session logs). ONLY a `?session=`-bound subscriber counts: a fleet-wide
286
- // dashboard consuming the frame must NOT burn the owning shell's durable copy — the park keeps its
287
- // offline/no-active-lane semantics. Keys line up by contract: frame taskId = BackgroundChildEvent
288
- // .taskId = the a*-domain registry handle (stable across kinds) = the parked entry's
289
- // runId (leg onTaskNotification's n.task_id, same domain); ownerSessionId = HOST session = entry key.
290
- // design/144 键升级:park 键=域:taskId:status[#seq],fleet 帧不带 seq → 传家族前缀
291
- // (taskNotificationFoldKey 无 seq 形),markTerminalServed 家族 ack 清同族 seq 键成员。
292
- // PAIR-REVIEW F-2:mark 延至 **write flush 回调** + socket 存活双检——「res.write 已调用」≠投递
293
- // (对端已断本端未感知的窗口里,帧写进死 socket 而 durable 副本被烧=通知永丢,1.334 教训)。
294
- // flush 回调=数据出到内核缓冲(尽力而为的最强本端信号;真端到端 ack 需协议位,另议),回调时
295
- // 再验 socket.destroyed——断连时 Node 会先触发 destroy,此时不 ack,park 副本留给下次开流 drain。
296
- // fence 的防双注入语义不变(已 ack 的重复帧仍被拒),只推迟 ack 时点。
297
157
  if (completionInbox && n.ownerSessionId && callerSession !== null && callerSession === n.ownerSessionId && !res.writableEnded) {
298
158
  const ownerSid = n.ownerSessionId;
299
- // codex R1-H1(修车批):帧带 seq(1.239/core 1.352)→ **精确键** ack(servedAt 只 fence 该
300
- // 周期;revive 周期 N+1 的 park/enqueue 不被连坐)。legacy 无 seq 帧保持家族键 ack(原语义)。
301
159
  const family = taskNotificationFoldKey({ task_id: n.taskId, status: n.status, ...(n.seq !== undefined ? { seq: n.seq } : {}) });
302
- // codex R2-H1(server belt):revive 周期的 park 键在 **uuid 域**(core ≤1.353 的 revive 通知
303
- // task_id=childSessionId,subagent.js:598/655——identity 对齐转 core 在单),帧键在 a\* 域——
304
- // 单键 ack 必 miss=开流二次注入。帧带 sessionId(uuid 别名)时**双域各 ack 一次**,两种 core
305
- // 版本的 park 键都被 fence 住;core 修齐后 uuid 臂自然空转(无同键 park,无害)。
306
160
  const familyUuid = n.sessionId !== undefined && n.sessionId !== n.taskId
307
161
  ? taskNotificationFoldKey({ task_id: n.sessionId, status: n.status, ...(n.seq !== undefined ? { seq: n.seq } : {}) })
308
162
  : undefined;
309
163
  const ackIfAlive = () => {
310
164
  if (res.writableEnded || res.socket?.destroyed)
311
- return; // 死连接:不烧 durable 副本
165
+ return;
312
166
  void completionInbox.markTerminalServed(ownerSid, family).catch(() => undefined);
313
167
  if (familyUuid !== undefined)
314
168
  void completionInbox.markTerminalServed(ownerSid, familyUuid).catch(() => undefined);
315
169
  };
316
- // res.write 的 flush 回调在上面 send() 内不可达(send 不透传 cb)——补一个零字节注释帧承载回调:
317
- // SSE 注释行(`: ack\n\n`)对消费者不可见(SDK 吞注释),flush 语义等同。
318
170
  res.write(`: ack
319
171
 
320
172
  `, ackIfAlive);
@@ -323,17 +175,9 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
323
175
  break;
324
176
  }
325
177
  default:
326
- break; // snapshot frames are connect-time only, never fanned out
178
+ break;
327
179
  }
328
180
  };
329
- // 🔴 #189 / codex R1-H1:订阅**先于**快照注册,快照组装期间到达的帧先进缓冲、发完快照再按序重放。
330
- // 快照第一次有了一段 await(durable 终态窗读),而 fleet bus 只扇给**当前**订阅者、不重放 —— 先读后
331
- // 订阅就等于开了一个"这段时间的帧对本连接不存在"的窗口(bg_notification 是模型注入面,丢一帧=这次
332
- // 完成回执要等到下次开流才由 durable inbox 补;task_remove/hook_notice 同理)。
333
- // 顺序安全:`bus.snapshot()` 取在**读之后**,缓冲里的行帧描述的都是快照那一刻或更早的状态,重放
334
- // 只会把同一状态再叠一次(upsert 幂等);撤行帧仍按 seen 集判(没见过的行不发撤除)。
335
- // 缓冲无显式上限:窗口被读预算硬界在 2s,且缓冲存的是**帧对象引用**(不复制行);设一个会丢帧的
336
- // 上限反而正好丢掉这段代码要保住的东西。
337
181
  let buffered = [];
338
182
  const unsubscribe = bus.subscribe((frame) => {
339
183
  if (buffered !== null)
@@ -341,7 +185,6 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
341
185
  else
342
186
  handleFrame(frame);
343
187
  });
344
- // Real frame (not an SSE comment) — a per-frame-parsing BFF drops comments (zero-frame window downstream)
345
188
  const hb = setInterval(() => { if (!res.writableEnded)
346
189
  res.write(`event: heartbeat\ndata: {}\n\n`); }, 15_000);
347
190
  if (typeof hb.unref === "function")
@@ -357,18 +200,10 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
357
200
  res.end();
358
201
  resolve();
359
202
  };
360
- // 关闭钩子在 await **之前**挂上:快照还在组装时对端就断了,也照常收摊(不然那段时间的断连没人处理)。
361
203
  req.on("close", shutdown);
362
204
  res.on("close", shutdown);
363
205
  void (async () => {
364
- // [3193] 病理1:boot recover(判死前世 running 行)与快照组装赛跑——SIGKILL 后壳重连常抢在
365
- // recover 之前,此刻 seed/durable 两径皆空,快照永远缺那一行。屏障=recovery 完成 promise
366
- // (main.ts 铸,memory 后端/无 gate 部署=已 resolve),**有界**等待(帧订阅已挂上,等待期帧全
367
- // 进缓冲不丢):超时=recover 真挂了,按旧行为出快照(可用性路径,行还有 first-sight-terminal
368
- // 豁免与下次重连兜底),不无限拖住连接。
369
206
  if (terminal?.ready !== undefined) {
370
- // A-067 C5 补遗(merge-rescan 车证伪「唯二反例」的第三处):竞速一结束 finally 清
371
- // (A-002.10 同款)——unref 只是不吊进程退,不回收定时器本身。
372
207
  let readyTimer;
373
208
  await Promise.race([terminal.ready, new Promise((r) => { readyTimer = setTimeout(r, 3000); if (typeof readyTimer.unref === "function")
374
209
  readyTimer.unref(); })]).finally(() => {
@@ -378,19 +213,12 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
378
213
  if (done || res.writableEnded)
379
214
  return;
380
215
  }
381
- // #189:durable 近期终态行窗(有界 + 永不抛;详见 fleet-terminal-window.ts)。
382
216
  const terminalWindow = terminal === undefined ? [] : await recentTerminalWorkflowRows(terminal.store, terminal.scope, terminal.pullScope);
383
217
  if (done || res.writableEnded)
384
- return; // 组装期间断连:shutdown 已 unsubscribe,不再发任何东西
385
- // connect-time snapshot (filtered + scope-stripped) — the visible rows become this connection's "seen" set.
218
+ return;
386
219
  const snap = bus.snapshot();
387
220
  const tasks = snap.tasks.filter(visT);
388
221
  const live = snap.workflows.filter(visW);
389
- // #189:活行(bus 的活跃集)+ durable 终态窗。
390
- // · 一行一 id:同 id 两侧都有(行刚翻转的那一拍)时 **活行优先** —— 活行是唯一写者写的当前态,
391
- // 终态窗是读时快照,后者覆盖前者会让面板倒退。
392
- // · 并入行同样过 `visW`(scope + `?session=`),与活行**同一套判据**:终态行泄跨租户是事故。
393
- // · bus 的 Map **不被触碰** —— 单写者不变量原样:活跃集里永远没有终态行,增量帧语义逐字不变。
394
222
  const liveIds = new Set(live.map((r) => r.id));
395
223
  const retained = terminalWindow.filter((r) => !liveIds.has(r.id) && visW(r));
396
224
  retained.forEach((r) => retainedTerminalWf.add(r.id));
@@ -399,20 +227,16 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
399
227
  workflows.forEach((r) => seenWf.add(r.id));
400
228
  send("snapshot", { tasks: tasks.map(stripT), workflows: workflows.map(stripW), ts: snap.ts });
401
229
  const pending = buffered ?? [];
402
- buffered = null; // 自此直投(先置空再重放:重放期间新到的帧走直投,不会插到旧帧前面)
230
+ buffered = null;
403
231
  replayingBuffer = true;
404
232
  try {
405
233
  for (const frame of pending)
406
234
  handleFrame(frame);
407
235
  }
408
236
  finally {
409
- replayingBuffer = false; // 重放一结束立刻恢复常态语义(哪怕上面抛了)
237
+ replayingBuffer = false;
410
238
  }
411
239
  })().catch(() => shutdown());
412
- // ⚠️ 上面这个 catch 不是"静默降级":快照组装本身抛错(写向已销毁的 socket 那一族)时**没有**可以继续
413
- // 的语义——这条连接已经没救了,干净收摊即正解。它真正防的是把原先同步执行器里的抛错(会直接 reject
414
- // 出去)变成一条 **unhandled rejection**(Node 默认带走整个进程)。订阅回调自身的抛错另有隔离层
415
- // (fleet bus 的 subscribe,记 fail-open),不经这里。
416
240
  });
417
241
  }
418
242
  //# sourceMappingURL=fleet.js.map