@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,45 +1,10 @@
1
- /**
2
- * design/158 A10:composition root 分段 —— 后台 reaper / 维护 tick。
3
- *
4
- * 纯搬运:函数体逐字来自 `main.ts`(原 2387-2599 行),缩进不变;新增的只有 import 与
5
- * `startReapers(ctx)` 包壳。
6
- *
7
- * ⚠️ **位置即契约**:
8
- * 1. 本段必须在 `createHttpServer` **之前**调用 —— D-D SLA-timer 的 deny-sweep 是 server 造出来的,
9
- * 故经 `getRunDenySweep()` 晚绑(`let runDenySweep` 仍留在 main.ts,赋值点仍在 server 之后)。
10
- * 这是本次搬运里**唯一**一行改写(`runDenySweep?.(…)` → `getRunDenySweep()?.(…)`)。
11
- * 2. `reaper.unref?.()` 必须紧跟 `setInterval`(定时器绝不可持住进程),故一并搬进本函数、
12
- * 返回值只交出 handle 给收尾段 `clearInterval`。
13
- * 3. tick 内各腿的**相对次序**逐字保留:probeClockSkew(时钟指纹)先于任何按本地时钟分桶的清算。
14
- */
15
1
  import { Runner, defaultTaskRegistry } from "@sema-agent/core";
16
2
  import { sweepStaleScratchpads } from "../env-facts.js";
17
3
  import { CostQuota } from "../observability/cost-quota.js";
18
4
  import { isAdoptionQuiescing, trackMaintenanceLeg } from "../adoption/quiesce.js";
19
5
  import { createApprovalReconciler } from "../approval-reconciler.js";
20
6
  import { createE2bOrphanSandboxProvider, reclaimOrphanE2bSandboxes, E2B_ORPHAN_GRACE_MS } from "../plugins/e2b-orphan-reclaim.js";
21
- /** #157 / [2762]§五(sema-comms `audits/failopen-governance-176.md` §7 "Z 类 1 条"):default consecutive-failure
22
- * count before a reaper leg's swallowed error gets a `warn` line. The ~15 `.catch(() => undefined)` sites
23
- * below are correct in DIRECTION (a hiccuping periodic sweep must never crash the whole tick loop — one bad
24
- * leg cannot be allowed to starve its ~15 siblings), but were zero-observability: an operator watching only
25
- * `warn`/`error` would never learn a leg had been silently broken for days. `5` (not `1`) is deliberate —
26
- * a transient blip (one DB reconnect, one timeout) is exactly what fail-open exists to absorb without noise;
27
- * only a STREAK crosses into "diagnosable, not transient". Exported so a future per-leg override is a
28
- * constructor-arg change, not a magic-number hunt. */
29
7
  export const REAPER_FAILURE_WARN_THRESHOLD = 5;
30
- /**
31
- * Builds a NAMED, stateful throttle for one reaper leg: attach as `.then(() => guard.onSuccess(), guard.onError)`
32
- * in place of a bare `.catch(() => undefined)` (the two-arg `.then` form still catches a rejection from any
33
- * earlier link in the chain — same coverage as the `.catch()` it replaces — while also observing the FULFILLED
34
- * path, which a plain `.catch()` cannot). Behavior this closure owns, unchanged reaper scheduling/cleanup:
35
- * - a successful run resets the consecutive-failure streak to 0 (one hiccup mid-outage doesn't inflate the count);
36
- * - a failure increments the streak; only once the streak reaches `threshold` (default
37
- * {@link REAPER_FAILURE_WARN_THRESHOLD}) does ONE `warn` line fire (reaper name + streak length + the most
38
- * recent error's message) — then the streak resets to 0 so a STILL-broken leg reports again after another
39
- * full `threshold` run, rather than going silent forever after its first report.
40
- * One instance must be created ONCE per named leg (outside the tick's `setInterval` callback) so the streak
41
- * survives across ticks — a fresh instance per tick would never accumulate past 1.
42
- */
43
8
  export function createThrottledReaperCatch(name, logger, threshold = REAPER_FAILURE_WARN_THRESHOLD) {
44
9
  let streak = 0;
45
10
  return {
@@ -51,14 +16,6 @@ export function createThrottledReaperCatch(name, logger, threshold = REAPER_FAIL
51
16
  if (streak < threshold)
52
17
  return;
53
18
  try {
54
- // #157 补痕交叉复审真发现(codex,2026-08-05):`String(err)` on a pathological rejection reason
55
- // (a throwing `Symbol.toPrimitive`/`toString`) or a throwing `logger.warn` transport would otherwise
56
- // make THIS handler throw — and since onError is wired as a `.then(fulfilled, onError)` REJECTED
57
- // branch with no further `.catch` downstream (every reapers.ts call site), that throw becomes a
58
- // floating rejected promise (an unhandled rejection under Node's default handling can crash the
59
- // process). The ORIGINAL `.catch(() => undefined)` this replaces could never do that — a failure
60
- // in the REPORTING of a swallowed leg failure must not be worse than the silence it replaces, so
61
- // this whole block is wrapped total/non-throwing.
62
19
  logger.warn("reaper_leg_repeated_failure", {
63
20
  reaper: name,
64
21
  consecutiveFailures: streak,
@@ -66,38 +23,13 @@ export function createThrottledReaperCatch(name, logger, threshold = REAPER_FAIL
66
23
  });
67
24
  }
68
25
  catch {
69
- // observability must never itself become the outage — see comment above.
70
26
  }
71
27
  finally {
72
- streak = 0; // re-open the window — a leg that stays broken reports again every `threshold` failures, not once ever.
28
+ streak = 0;
73
29
  }
74
30
  },
75
31
  };
76
32
  }
77
- /**
78
- * 🔴 #292 C18(core 5.48.0 design/323,[4743] 提货单)——**分区腿武装**的判据本体。
79
- *
80
- * core 5.48 起 `reapDurableAgents` 的 `deps.sessions` 多一只可选 `listPlaced`:在场即启用**分区收割
81
- * 腿** —— 一条 placed 转录的属主行已经不在(put 从未落地 / 上一轮条件删赢了但 release 失败)时,行驱动
82
- * 的那条臂**结构上看不见**它(它从行出发枚举),只有从会话侧枚举才找得到。
83
- *
84
- * 两只面必须落在**host durable 店**上,而 release 这一格是承重的:
85
- * · `listPlaced` = host 店的分区枚举(SQL 孪生实装;local/File 后端无此面 ⇒ 键**不设**,core 那条腿
86
- * 如实不可用 —— 绝不给一个恒返回 `[]` 的假面,那会把「枚不了」读成「没有孤儿」);
87
- * · `release` 对 **placed id** 必须走 host 店:路由店(`ForkRoutingSessionStore`)对一个重启后不认识
88
- * 的 id 会回落 transient(进程内 no-op),那会让 core 数出一次**从未发生**的 release
89
- * (`orphanPlacedReleased` 当场变成谎)而字节还在库里。故先按**持久化元组**判(一次 PK 探针),
90
- * placed 走 host 店的真删除;其余 id 原样保留 [1522] 裁定的双店语义。
91
- * · 🔴 探针本身失败 ⇒ **响亮抛**(codex 对抗复审 R1-[medium],验真后修:本函数的初版把探针错误
92
- * 改写成 `undefined` 再落路由店 —— 而重启后路由店不认识 placed id ⇒ transient no-op,core 却因为
93
- * release 正常 resolve 而 `orphanPlacedReleased++`。间歇性 DB 故障于是被**伪装成 GC 成功**:监控
94
- * 报着「收掉了 N 条孤儿」,durable 字节一条没动。这正是本函数顶注要防的那个形,初版自己踩了。
95
- * 抛的后果是安全的:core 两条臂的 release 都在 try 内、且计数在 release **之后**递增 ⇒ 抛 = 不计数
96
- * + 下一 tick 重新枚举重试(失败经济学与 core 的成文一致:泄漏给下一轮,而不是谎报已清)。
97
- *
98
- * 抽成具名工厂而不是就地闭包的理由与 `createEngineNoticeSeat` 逐字同一条:判据在**值**上
99
- * (哪只店被调),源码文本门看不出对错,只有行为测得出来。
100
- */
101
33
  export function createPlacementAwareReapSessions(deps) {
102
34
  const { host, routed } = deps;
103
35
  const listPlaced = typeof host?.listPlaced === "function" ? host.listPlaced.bind(host) : undefined;
@@ -111,12 +43,10 @@ export function createPlacementAwareReapSessions(deps) {
111
43
  placed = await placementOf(sessionId);
112
44
  }
113
45
  catch (err) {
114
- // 探针失败 = 「这条 id 是不是 placed」这件事**不知道**。不知道就不许当成「不是」——那会把它
115
- // 送去路由店拿一次 no-op 冒充成功释放(见上方顶注)。响亮抛,计数不涨,下一 tick 重来。
116
46
  throw new Error(`placement probe failed for session ${sessionId} — refusing to release it blindly (a routed no-op would be counted as a real GC): ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? { cause: err } : undefined);
117
47
  }
118
48
  if (placed !== undefined) {
119
- await host.release(sessionId); // placed ⇒ host 店的 release = 真删除(义务 4)
49
+ await host.release(sessionId);
120
50
  return;
121
51
  }
122
52
  }
@@ -125,20 +55,8 @@ export function createPlacementAwareReapSessions(deps) {
125
55
  ...(listPlaced !== undefined ? { listPlaced } : {}),
126
56
  };
127
57
  }
128
- /** 起后台维护 tick,返回定时器 handle(收尾段 clearInterval 用)。 */
129
58
  export function startReapers(ctx) {
130
59
  const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, rateLimiter, costQuota, toolResultStore, fileSnapshotStore, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, getRunDenySweep, } = ctx;
131
- // S7 (SILENT-FALLBACK P0-d): the sweeps' return counts were discarded — an instance death that batch-fails
132
- // N orphans was indistinguishable from organic failures. Count + log ONLY when a sweep flipped rows (>0),
133
- // so healthy ticks stay silent. Tolerant of void-returning stores (typeof guard).
134
- /**
135
- * 一条维护腿的**起飞登记**(codex 对抗复审 R1 [high] 的修复面)。
136
- *
137
- * 代替裸 `void <promise>`:语义与 `void` 完全一样(不 await、错误由各腿自己的 guard 接住),
138
- * 只多一件事 —— 把这一份 promise 登记进收编闸的在飞集,好让一条正要动数据的收编弧**等它落地**。
139
- * 少一处没换,那条腿就是闸的漏网者(而漏网是静默的),所以本文件里 tick 内**不该再出现裸 `void`**。
140
- * 入参允许 `undefined`(可选链的腿:`void store?.reap()` 在店缺席时求值成 undefined)。
141
- */
142
60
  const leg = (p) => {
143
61
  if (p !== undefined)
144
62
  trackMaintenanceLeg(p);
@@ -149,26 +67,18 @@ export function startReapers(ctx) {
149
67
  logger.info("reaper_swept", { metric, ...labels, count: n });
150
68
  }
151
69
  };
152
- // #242:E2B 孤儿回收腿的三件座——provider(仅 e2b 部署形 + 旋钮 ON 时铸,一次)、时间节流锚
153
- // (初始 -GRACE ⇒ 首 tick 即跑,承担 boot 半场)、重入守卫(list+逐杀可能跨多个 tick;provider RPC
154
- // 带 SDK 级 requestTimeoutMs,旗不会因一次挂起永挂)。节流锚刻意用墙钟(codex R3 登记不改):判据
155
- // 侧的 GRACE 比对是 E2B startedAt(墙钟)vs Date.now(墙钟)同钟系,节流锚换单调钟只护住「回拨多跑
156
- // 一轮 list」这种无害面,却让本文件唯一一处异质钟——不值。锚在**发起时**推进(非成功时):失败轮也
157
- // 压满 GRACE 间隔=对 provider 的天然退避;错误可见性由 e2bOrphanGuard 的连败阈值 warn 承担。
158
70
  const e2bOrphanProvider = config.remoteExec?.provider === "e2b" && config.remoteExec.orphanReclaim !== false
159
71
  ? createE2bOrphanSandboxProvider({ apiKey: config.remoteExec.apiKey })
160
72
  : undefined;
161
73
  let e2bOrphanLastRunMs = Date.now() - E2B_ORPHAN_GRACE_MS;
162
74
  let e2bOrphanInFlight = false;
163
- let wfRunReapInFlight = false; // 1.108 review (lens③): serialize the all-scopes retention sweep across ticks
164
- let attachmentSweepInFlight = false; // 复审 F1:附件孤儿 sweep 的重入守卫(同上)
165
- let bgAgentReapInFlight = false; // core 1.364: same serialization for the durable bg-agent joint reap (per-scope serial loop)
166
- let worktreeReapInFlight = false; // A10 留档发现①(2026-07-29):worktreeReap 重入守卫(同上三个邻居——见下方调用点注释)
167
- // #157 / [2762]§五 Z 类补痕:one NAMED throttled-catch guard per periodic leg below (created ONCE here, not
168
- // per-tick, so each leg's consecutive-failure streak survives across ticks — see createThrottledReaperCatch).
75
+ let wfRunReapInFlight = false;
76
+ let attachmentSweepInFlight = false;
77
+ let bgAgentReapInFlight = false;
78
+ let worktreeReapInFlight = false;
169
79
  const probeClockSkewGuard = createThrottledReaperCatch("probe_clock_skew", logger);
170
80
  const runsReapStaleGuard = createThrottledReaperCatch("runs_reap_stale", logger);
171
- const e2bOrphanGuard = createThrottledReaperCatch("e2b_orphan_reclaim", logger); // #242
81
+ const e2bOrphanGuard = createThrottledReaperCatch("e2b_orphan_reclaim", logger);
172
82
  const checkpointsReapExpiredGuard = createThrottledReaperCatch("checkpoints_reap_expired", logger);
173
83
  const runsFailSuspendedExpiredCheckpointGuard = createThrottledReaperCatch("runs_fail_suspended_expired_checkpoint", logger);
174
84
  const runDenySweepGuard = createThrottledReaperCatch("run_deny_sweep", logger);
@@ -186,36 +96,20 @@ export function startReapers(ctx) {
186
96
  const workflowNotifyJournalReapAckedGuard = createThrottledReaperCatch("workflow_notify_journal_reap_acked", logger);
187
97
  const bgAgentsReapGuard = createThrottledReaperCatch("bg_agents_reap", logger);
188
98
  const rosterReapOlderThanGuard = createThrottledReaperCatch("roster_reap_older_than", logger);
189
- const leaderRunStaleGuard = createThrottledReaperCatch("leader_run_reap_stale", logger); // #193 车4 件1
190
- // #193 车4 件1:陈旧 running 清算腿的店。合取 `config.leaderEnabled` —— 默认关的部署不注册这条腿
191
- // (取不到店也一样,`?.` 让整条腿成为 no-op)。
99
+ const leaderRunStaleGuard = createThrottledReaperCatch("leader_run_reap_stale", logger);
192
100
  const leaderRunStore = config.leaderEnabled ? backend?.leaderRun?.() : undefined;
193
101
  const scratchpadSweepStaleGuard = createThrottledReaperCatch("scratchpad_sweep_stale", logger);
194
102
  const approvalReconcileGuard = createThrottledReaperCatch("approval_reconcile", logger);
195
103
  const permissionRuleReapGuard = createThrottledReaperCatch("permission_rule_reap_expired", logger);
196
- let permissionRuleReapInFlight = false; // A-010.17 / codex R2:重入守卫(邻居四腿同形,理由见调用点)
197
- let approvalReconcileInFlight = false; // 车5 §8 C-8:重入守卫(邻居四先例同形——本腿时长随 PARKING 行数增长)
198
- // #151 车5(design/172 §3.0 对账收敛器 + 崩溃恢复扫描):建腿条件 = 开关开 ∧ **有 `StoreBackend`**
199
- // (见下方判据行 —— 是**任意** backend,不限 SQL twins:`local` 车道也算,它的 `approvalAsk()` 是进程内
200
- // `InMemoryApprovalAskStore`,扫的是内存不是库)。无 backend 或显式关 ⇒ 本腿根本不注册,零扫描。
201
- // ⚠️ 运维读数已反转:总开关默认**在树上已是 ON**(发布线上落在 7.4.0 之后的下一发布;版本坐标与
202
- // 「别按 /health 版本号推默认」的告诫见属主 config.ts 的 `streamApprovalConfig()` 头注),
203
- // 所以「零新增扫描」从**默认态**变成了 **opt-out 态** —— 任何配了 backend 的部署(含单用户 local)
204
- // 默认都会注册本腿并每 tick 扫两段(PARKING + 孤儿 STREAM_PENDING,每段
205
- // `STREAM_APPROVAL_RECONCILE_BATCH` 行,默认 200);SQL twins 上这就是**真新增的周期性查询**,local 上
206
- // 只是内存遍历。存量部署升级后要回到零扫描,需显式把开关关掉。
207
- // 判据 1/2 的两个读口按能力面接:checkpoint 店的窄读口只有 SQL twins 有(local 车道的店没有这个面),
208
- // 缺席 ⇒ 判据 1 恒不命中(诚实降级,行落 ②③④⑤),不是「假装没 checkpoint 就该 DENY」。
104
+ let permissionRuleReapInFlight = false;
105
+ let approvalReconcileInFlight = false;
209
106
  const approvalReconciler = (() => {
210
107
  if (!config.streamApproval.enabled || !backend)
211
108
  return undefined;
212
- // duck probe(邻居同形:本文件 `sweepStagingSessions` / `reapAllScopes` 都是这个姿势)——
213
- // `CheckpointStoreFull` 的 local 成员没有这个窄读口,它是 SQL twins 的扩展面。
214
109
  const cpPort = checkpointStore;
215
110
  return createApprovalReconciler({
216
111
  askStore: backend.approvalAsk(),
217
112
  checkpoints: typeof cpPort?.findCheckpointCandidatesForAsk === "function" ? cpPort : undefined,
218
- // RunStore 三形(TiDB/Pg/File)都带 `getRun`,故直接接、无 probe。
219
113
  runs: runStore,
220
114
  logger,
221
115
  metrics,
@@ -226,21 +120,6 @@ export function startReapers(ctx) {
226
120
  ...(toolApproval ? { emitRevoke: (frame, target) => void toolApproval.emitRevoke(frame, target) } : {}),
227
121
  });
228
122
  })();
229
- // core 1.364([1503] 提货单③):retention **只走** reapDurableAgents(联合 reap:条件删赢了才 release
230
- // 转录 session;裸 store.reap 会 strand 转录)。staleRunning 翻转在 core 编排内先行(store.reap 一步)。
231
- // scope 枚举:core 1.368 `BackgroundAgentStore.listScopes?()`([1516]② 交付,SQL twins/File 双实现
232
- // 都带)——1.248 拍的「file 腿只扫 default」假设已撤;无该方法的第三方 store 仍回落 "default"(可选
233
- // 接口成员,诚实回落)。
234
- // sessions=subRunner.sessions —— [1522] 裁定件(③ 显式裁定形,双店真相考据后拍):bg 子代转录会话
235
- // 是**双店**(ForkRoutingSessionStore):①非 fork 子代铸在 transient 店(TtlSessionStore,进程内
236
- // 1h TTL)——release=内存删=**真正终结可寻址性**(转录本就不 durable;跨实例读面读的是行内
237
- // finalOutput,非转录);②fork 子代铸在 host durable 店——release=lease 释放,durable 转录行**有意
238
- // 保留**,留存归 session 留存策略/E21 purge 生命周期管(行先删可接受:行是执行记录,转录是会话资产,
239
- // 两者生命周期本就不同)。core [1522] 复审只见 durable 腿判 HIGH——按其契约句「做不到终结可寻址性
240
- // 宁可不传」,transient 类做得到、fork 类是显式裁定,维持传入。sessionsReleased 计数=「store 侧
241
- // release 被调次数」(transient=真删,durable=lease 释放),非「durable 转录处置数」——如实注。
242
- // 复审 F4 如实注:条件删的赢者若非写者副本,fork-routing 对未知 id 回落 transient=本地 no-op,写者
243
- // 进程内 pinned 转录滞留到自身 TTL/进程退出——sessionsReleased 计数偏低是诚实读数,非缺陷。
244
123
  const reapSessions = createPlacementAwareReapSessions({ host: backend?.session(), routed: subRunner.sessions });
245
124
  const reapBgAgents = backgroundAgentStore
246
125
  ? async () => {
@@ -248,21 +127,7 @@ export function startReapers(ctx) {
248
127
  const scopes = await (store.listScopes?.() ?? Promise.resolve(["default"]));
249
128
  let rows = 0, sessions = 0, skipped = 0, failedScopes = 0, orphanPlaced = 0;
250
129
  for (const scope of scopes) {
251
- // [1522] LOW3:per-scope 错误隔离——一个 scope 抛(坏 record_json/权限)不饿死后续 scope;
252
- // 失败响亮(warn+计数),下 tick 重试。
253
130
  try {
254
- // core 1.382([1561] 提货单④,design/153 parked 状态机对账):先对账 parked 行(checkpoint
255
- // expired/missing → 行诚实翻 failed;stale claim → 回滚 parked),再让下面既有的
256
- // reapDurableAgents 按自然节奏处置(刚翻 failed 的行本 tick 不会立刻被 maxAge 删——它是
257
- // "刚失败"的新行,不是"失败很久"的老行)。**走 registry 面**(裂脑 fence 内建于
258
- // TaskRegistry.reconcileParkedAgents 内部,不裸调 store 面——那是无活实例场景专用,core
259
- // [1561]④ 措辞)。仅在 checkpointStore 真在场时跑(同 [1561]①②「同车必接」判据——没有
260
- // checkpointStore 就不可能有真正 parked 的行,调用本身没有意义)。
261
- // 🔴 [1575] F1 修(cli 复查,红先行确认):`opts.staleClaimMaxAgeMs` 必须显式传——core 把
262
- // 整个 stale-claim 清算块(claimer 崩死回滚 + resolved-未-finalize 诚实翻 failed)门在
263
- // `staleMs !== undefined` 上,缺席不是"跳过 stale-claim 这一小步",是**整块永不运行**
264
- // (此前漏传,`bg_agents_park_reconciled_rolledback_total` 恒 0,人已做出的审批决定会被
265
- // 静默丢弃——见 config.ts backgroundAgentParkClaimStaleMs 顶注)。
266
131
  if (checkpointStore) {
267
132
  const pr = await defaultTaskRegistry.reconcileParkedAgents({ agentStore: store, checkpointStore }, scope, Date.now(), { staleClaimMaxAgeMs: config.backgroundAgentParkClaimStaleMs });
268
133
  if (pr.failed > 0 || pr.rolledBack > 0) {
@@ -271,8 +136,6 @@ export function startReapers(ctx) {
271
136
  logger.info("reaper_swept", { metric: "bg_agents_park_reconciled", scope, failed: pr.failed, rolledBack: pr.rolledBack });
272
137
  }
273
138
  }
274
- // deps.mailbox(core 1.374,[1533]②):行删联动 F-13——agent 行 reap 时同 drop 其信箱
275
- // (盒生命周期随行终结;不挂=信箱行无 retention 无界涨)。
276
139
  const r = await defaultTaskRegistry.reapDurableAgents(scope, { store, sessions: reapSessions, ...(mailboxStore ? { mailbox: mailboxStore } : {}) }, {
277
140
  maxAgeMs: config.backgroundAgentRetentionMs,
278
141
  staleRunningMaxAgeMs: config.backgroundAgentStaleRunningMs,
@@ -284,25 +147,15 @@ export function startReapers(ctx) {
284
147
  }
285
148
  catch (err) {
286
149
  failedScopes++;
287
- // codex 交叉复审第三轮真发现(2026-08-05):a poison `err` (throwing `Symbol.toPrimitive`/`toString`)
288
- // or a throwing `logger.warn` must not escape THIS catch — the outer `bgAgentsReapGuard` (this
289
- // car's own wiring) already stops it from becoming an unhandled rejection, but an uncaught throw
290
- // HERE would abort the `for` loop early and skip every remaining scope, defeating the per-scope
291
- // isolation this block exists for ("[1522] LOW3" above — one bad scope must not starve the rest).
292
150
  try {
293
151
  logger.warn("bg_agent_reap_scope_failed", { scope, err: err instanceof Error ? err.message : String(err) });
294
152
  }
295
153
  catch {
296
- // observability must never break the per-scope loop — see comment above.
297
154
  }
298
155
  }
299
156
  }
300
157
  if (failedScopes > 0)
301
158
  metrics.inc("bg_agent_reap_scope_failures_total", {}, failedScopes);
302
- // #292:`orphanPlacedReleased` 是**独立的读数**(分区腿的产出),不折进 `sessionsReleased`
303
- // ——两者回答的是不同的问题(「行驱动地释放了几条」vs「行已不在、从分区侧收掉了几条」),
304
- // 相加会让「行与会话同亡的联合 reap 是否健康」这条判读永久失去分辨率。行数为 0 但分区腿有
305
- // 收成时也要出线(那正是 reap 失败/漏跑留下的历史欠账被追平的时刻)。
306
159
  if (orphanPlaced > 0)
307
160
  metrics.inc("bg_agents_orphan_placed_released_total", {}, orphanPlaced);
308
161
  if (rows > 0 || sessions > 0 || orphanPlaced > 0) {
@@ -311,12 +164,7 @@ export function startReapers(ctx) {
311
164
  }
312
165
  }
313
166
  : undefined;
314
- // S10 (SILENT-FALLBACK P1): the write-behind counters bucket on LOCAL floor(now/windowMs) — replica clock
315
- // skew vs the DB splits a fleet window into disjoint buckets (soft-limit leak) with zero visibility. Probe
316
- // the DB clock each reaper tick; the gauge is the fleet-wide skew fingerprint (can be negative).
317
167
  const probeClockSkew = async () => {
318
- // Probe-ok gauge: a failing probe froze the skew gauge at its last value with zero signal —
319
- // "probe ran, skew=N" vs "probe failing for 30 min" were indistinguishable.
320
168
  try {
321
169
  const dbMs = await backend?.dbNowMs?.();
322
170
  if (typeof dbMs === "number" && Number.isFinite(dbMs)) {
@@ -330,43 +178,22 @@ export function startReapers(ctx) {
330
178
  };
331
179
  let quiesceLogged = false;
332
180
  const reaper = setInterval(() => {
333
- // 🔴 A-010.18(验真后修):**收编弧跑动期间整轮让路**。design/183 I1「引擎先停」此前在进程内
334
- // 零执法 —— 弧在逐表 UPDATE 的同时,本 tick 的好几条腿写的正是被迁的那些表。最锋利的是审批收敛器:
335
- // 它按 `owner` 读出一批行再 CAS 结算,而收编正在把 `approval_ask.owner` 从 A 改成 B ⇒ 一只本该
336
- // PARKED 的 ask 可能被写成 **DENIED**,而 DENIED 是终局(收编回滚不了它,重跑也追认不回来)。
337
- // 为什么让开**整轮**而不是挑腿避让、以及「只挡本副本」这条射程边界,逐字见 `adoption/quiesce.ts`。
338
- // 让路是零代价的:每条腿都幂等、都能等下一轮,而弧是毫秒级的几条 UPDATE。
339
181
  if (isAdoptionQuiescing()) {
340
182
  if (!quiesceLogged) {
341
- quiesceLogged = true; // 一次性 —— 弧毫秒级,逐 tick 刷屏没有信息量
183
+ quiesceLogged = true;
342
184
  logger.info("reaper_tick_yielded_to_adoption", {
343
- // 文案只说**兑得出来**的话(codex R1 [high] 的教训):本行保证的是「不再**起飞**新腿」。
344
- // 已经在飞的那些由弧侧的有界排空负责等,等不到会另打一条 `adoption_maintenance_drain_timeout`
345
- // —— 两条读数合起来才是全貌,所以这一行不许自称「没有任何腿在写被迁的表」。
346
185
  note: "an adoption arc is running in this process; the maintenance tick launches NO new legs for its duration. Legs already airborne when the arc began are waited on separately (bounded) — see adoption_maintenance_drain_timeout if that wait ran out (design/183 I1, in-process half)",
347
186
  });
348
187
  }
349
188
  return;
350
189
  }
351
190
  quiesceLogged = false;
352
- leg(probeClockSkew().then(() => probeClockSkewGuard.onSuccess(), probeClockSkewGuard.onError)); // S10
191
+ leg(probeClockSkew().then(() => probeClockSkewGuard.onSuccess(), probeClockSkewGuard.onError));
353
192
  leg(runStore
354
193
  ?.reapStale(config.runStaleSec * 1000)
355
194
  .then(reapCount("runs_reaped_total", { kind: "stale" }))
356
195
  .then(() => runsReapStaleGuard.onSuccess(), runsReapStaleGuard.onError));
357
- // Durable F4 (design/45) + design/80 §3 inv#3 crash-safe backstop: CAS-expire checkpoints past their
358
- // deadline OR their absolute terminal_at_ms backstop (≈ deny), then fail the suspended run rows whose
359
- // checkpoint was thereby expired (release task_active = unlock the session). These run EVERY tick,
360
- // REGARDLESS of APPROVAL_TIMEOUT_SEC — terminal_at_ms (stamped at put, never before an explicit deadline)
361
- // bounds even a NULL-deadline pending checkpoint, so the backstop is the always-on safety net (it was
362
- // inert when nested under the approvalTimeoutSec>0 guard — adversarial finding). The run-row half is
363
- // checkpoint-STATE-driven (not a uniform timer) so it aligns with the per-row terminal_at_ms. Global +
364
- // idempotent across replicas, no election.
365
196
  if (checkpointStore) {
366
- // 轴A #5 注释落档(1.254):core CheckpointStore.reap 契约把弃置臂的 unpin 义务派给部署 reaper——
367
- // 真 pin 只存在于 in-memory TtlSessionStore;本部署 durable checkpoint 恒配 durable session 后端
368
- // (pin=no-op),deny-sweep 走 resumeCheckpoint=core 内部 unpin ✓。SESSION_BACKEND=memory+durable
369
- // checkpoint 的 niche dev 组合下 abort-expire 臂会把被钉会话泄到进程终——显式接受,不为 dev 形加腿。
370
197
  leg(checkpointStore
371
198
  .reapExpired(Date.now())
372
199
  .then(reapCount("checkpoints_reaped_total", {}))
@@ -375,49 +202,31 @@ export function startReapers(ctx) {
375
202
  ?.failSuspendedWithExpiredCheckpoint()
376
203
  .then(reapCount("runs_reaped_total", { kind: "expired_checkpoint" }))
377
204
  .then(() => runsFailSuspendedExpiredCheckpointGuard.onSuccess(), runsFailSuspendedExpiredCheckpointGuard.onError));
378
- // D-D SLA-timer: resolve-DENY human/irreversible_ask gates past their deadline (graceful — the model
379
- // continues with the denial), vs the abort reapExpired gives the other kinds. Bounded per tick.
380
205
  leg(getRunDenySweep()?.(Date.now())
381
- .then(() => runDenySweepGuard.onSuccess(), runDenySweepGuard.onError)); // A10 搬运改写:晚绑取值(原 `runDenySweep?.`)
206
+ .then(() => runDenySweepGuard.onSuccess(), runDenySweepGuard.onError));
382
207
  }
383
- // The finer, OPT-IN time-based suspended-run reaper (APPROVAL_TIMEOUT_SEC). Stays gated — reapSuspended
384
- // with a 0 TTL would nuke ALL suspended rows (cutoff = now). The absolute backstop above is the safety
385
- // floor; this is the operator-chosen deadline. ([2354] A-5:F4 轮询店的 expireStale 腿随车道退役。)
386
208
  if (config.approvalTimeoutSec > 0) {
387
209
  leg(runStore
388
210
  ?.reapSuspended(config.approvalTimeoutSec * 1000)
389
211
  .then(reapCount("runs_reaped_total", { kind: "suspended" }))
390
212
  .then(() => runsReapSuspendedGuard.onSuccess(), runsReapSuspendedGuard.onError));
391
213
  }
392
- // GC checkpoint_ctx rows whose checkpoint is gone (bound to the checkpoint lifecycle).
393
214
  leg(checkpointStore
394
215
  ?.reapCtx(Date.now() - config.runStaleSec * 1000)
395
216
  .then(() => checkpointReapCtxGuard.onSuccess(), checkpointReapCtxGuard.onError));
396
217
  rateLimiter?.sweep();
397
218
  if (costQuota instanceof CostQuota)
398
- costQuota.reap(); // TiDB variant self-reaps in its flush loop
219
+ costQuota.reap();
399
220
  leg(toolResultStore
400
221
  ?.reapOlderThan?.(Date.now() - config.toolResultTtlSec * 1000)
401
- ?.then(() => toolResultReapOlderThanGuard.onSuccess(), toolResultReapOlderThanGuard.onError)); // optional extra: SQL twins only (the local FileToolResultStore persists like transcripts)
402
- // 2c session-sync: GLOBALLY GC grace-window orphan blobs (standalone /sync/blobs PUTs that were never
403
- // imported are otherwise collected only on reap()/deleteBySession(), which never fire for a never-imported scope).
404
- // Bounds the standalone-PUT orphan-blob exhaustion (review finding) to the grace window. Durable twins only (local
405
- // omits the seam → optional-chained no-op).
222
+ ?.then(() => toolResultReapOlderThanGuard.onSuccess(), toolResultReapOlderThanGuard.onError));
406
223
  leg(fileSnapshotStore
407
224
  ?.sweepOrphanBlobs?.()
408
225
  .then(() => fileSnapshotSweepOrphanBlobsGuard.onSuccess(), fileSnapshotSweepOrphanBlobsGuard.onError));
409
- // D-1 附件 TTL:上传后从未被任何 task 引用(session_id NULL)且超过 attachmentUnboundTtlMs 的行收割
410
- // (绑定行不在此收——随会话 E21 级联删)。best-effort,与其余 reaper 腿同姿。
411
226
  leg(taskAttachmentStore
412
227
  ?.reapUnbound(Date.now() - config.attachmentUnboundTtlMs)
413
228
  .then(reapCount("attachments_reaped_total", {}))
414
229
  .then(() => attachmentReapUnboundGuard.onSuccess(), attachmentReapUnboundGuard.onError));
415
- // D-1 孤儿**对象**彻底 GC(clay 拍 2026-07-28):对象存储 × meta 行对账,grace 默认 1h(上传先行窗
416
- // 保护;`ATTACHMENT_ORPHAN_GRACE_MS=0` 关掉本腿——同 tick 邻居都有旋钮,复审 F10)。
417
- // 列举失败=本轮 warn 跳过(「列不出来」绝不当「没有孤儿」),下轮再试。
418
- // 🔴 复审 F1:必须 in-flight 守卫——本腿时长随对象总数增长(20 万对象实测秒级、百万级可达 30s),
419
- // 无守卫时超过 tick 间隔即逐 tick 叠加(实测叠 6 层),同时压 LIST/SQL/DELETE 三面。邻居
420
- // wfRunReap/bgAgentReap 都有同款守卫。
421
230
  if (config.attachmentOrphanGraceMs > 0 && !attachmentSweepInFlight && taskAttachmentStore?.sweepOrphanObjects) {
422
231
  attachmentSweepInFlight = true;
423
232
  leg(taskAttachmentStore
@@ -427,65 +236,32 @@ export function startReapers(ctx) {
427
236
  logger.info("attachment_orphan_objects_swept", { removed: n });
428
237
  } })
429
238
  .catch((err) => {
430
- // codex 交叉复审第二轮真发现(2026-08-05):`String(err)` on a pathological rejection reason or a
431
- // throwing `logger.warn` transport must not escape THIS handler — this `.catch` sits ahead of a
432
- // `.finally()` with no further `.catch` downstream, so an uncaught throw here becomes a floating
433
- // rejected promise (unhandled rejection under Node's default handling). Same invariant as
434
- // createThrottledReaperCatch.onError above: a reaper leg's failure REPORTING must never itself
435
- // crash the tick loop.
436
239
  try {
437
240
  logger.warn("attachment_orphan_sweep_failed", { err: err instanceof Error ? err.message : String(err) });
438
241
  }
439
242
  catch {
440
- // observability must never crash the tick — see comment above.
441
243
  }
442
244
  })
443
245
  .finally(() => { attachmentSweepInFlight = false; }));
444
246
  }
445
- // P1d-β 2c session-sync: GC ABANDONED staged imports — staging-id session_event rows (`%#stg-%`) with NO
446
- // session_meta whose oldest row is older than the grace window (an in-flight stream stays fresh → never reaped).
447
- // Bounds the orphan-staging-row growth (a Phase B that opened a staging then never committed). Durable session
448
- // stores only (the local backend stages in memory → no durable rows; the seam is absent → optional-chained no-op).
449
247
  leg(backend?.session()?.sweepStagingSessions?.()
450
248
  .then(() => sessionSweepStagingGuard.onSuccess(), sessionSweepStagingGuard.onError));
451
- // Bake-runner backstop (§P2.7): fail any `running` bake whose lease went stale (the runner crashed mid-build),
452
- // append a synthetic terminal `done{failed}` so SSE readers settle, and force-release the single-flight lease.
453
- // staleMs (≈3× the 30s heartbeat) bounds a healthy slow build so it is never wrongly reaped.
454
249
  leg(imageBakes
455
250
  ?.reapStaleBakes(config.imageBakes.staleMs)
456
251
  .then(reapCount("bakes_reaped_total", {}))
457
252
  .then(() => imageBakesReapStaleGuard.onSuccess(), imageBakesReapStaleGuard.onError));
458
- // SVC-3 worktree isolation: deregister worktrees orphaned by a process crash (the Runner never reached
459
- // destroy → `git worktree remove` never ran). `git worktree prune` cleans registrations whose dirs are
460
- // already gone. Best-effort, userland (core ships no post-kill Runner hook); never throws. Unset = no-op.
461
- // 🔴 A10 留档发现①(review 2026-07-29):必须 in-flight 守卫——`git worktree prune` 随仓内 worktree
462
- // 总数/子代并发度增长可变慢,无守卫时超过 tick 间隔即在下一 tick 被重入叠跑(邻居
463
- // attachmentSweep/wfRunReap/bgAgentReap 三个 sweep 都已有同款守卫,此腿此前是唯一漏配的)。
464
- // 照抄 bgAgentReap 的 in-flight + finally 释放形。
465
253
  if (worktreeReap && !worktreeReapInFlight) {
466
254
  worktreeReapInFlight = true;
467
255
  leg(worktreeReap()
468
256
  .then(() => worktreeReapGuard.onSuccess(), worktreeReapGuard.onError)
469
257
  .finally(() => (worktreeReapInFlight = false)));
470
258
  }
471
- // SVC-1 (adversarial-review HIGH): PERIODIC notify-recovery sweep (not just at boot) — re-delivers a terminal
472
- // run whose in-process notify was lost, AND finalizes-as-abandoned a `running` run orphaned past the grace
473
- // window (core never resumes/reaps a prior `running` row, so nothing else would). Idempotent; best-effort.
474
259
  leg(workflowNotifyGate
475
260
  ?.recover(workflowRecoverOpts)
476
261
  .then(() => workflowNotifyRecoverGuard.onSuccess(), workflowNotifyRecoverGuard.onError));
477
- // SVC-2 (adversarial-review HIGH): time-based GC of the workflow_journal table (the heaviest, TaskResult-bearing
478
- // one) — the per-run deleteByRun has no run-store reap hook, so this bounded sweep is what stops unbounded
479
- // growth. A resume of a journal older than the retention window re-runs live (resume is an optimization).
480
262
  leg(workflowJournalStore
481
263
  ?.reapExpired?.(Date.now(), config.workflowJournalRetentionMs)
482
264
  .then(() => workflowJournalReapExpiredGuard.onSuccess(), workflowJournalReapExpiredGuard.onError));
483
- // Retention (WorkflowRunStore never auto-purges, reap is explicit):
484
- // age out TERMINAL workflow_run rows across ALL scopes. SQL twins only (reapAllScopes is their DISTINCT-scope
485
- // extension; the contract itself has no cross-scope enumeration, and the File store keeps the transcripts-like
486
- // keep-everything posture). Running rows are untouched — the orphan-grace sweep above owns those.
487
- // In-flight guard (1.108 review, lens③): the sweep enumerates EVERY scope serially — under a short
488
- // REAP_INTERVAL_SEC + many scopes, overlapping sweeps would pile up on the pool. One at a time.
489
265
  if (!wfRunReapInFlight) {
490
266
  const sweep = sqlWorkflowRunStore?.reapAllScopes?.(Date.now(), { maxAgeMs: config.workflowRunRetentionMs });
491
267
  if (sweep) {
@@ -494,43 +270,24 @@ export function startReapers(ctx) {
494
270
  .then(() => workflowRunReapAllScopesGuard.onSuccess(), workflowRunReapAllScopesGuard.onError)
495
271
  .finally(() => (wfRunReapInFlight = false)));
496
272
  }
497
- // 1.108: SQL notify-journal retention rides the same knob — ACKED rows are pure history (pending rows are
498
- // the recovery backlog and are NEVER reaped; the orphan-grace sweep retires a stuck pending run).
499
273
  leg(workflowNotifyJournal
500
274
  ?.reapAcked?.(Date.now() - config.workflowRunRetentionMs)
501
275
  .then(() => workflowNotifyJournalReapAckedGuard.onSuccess(), workflowNotifyJournalReapAckedGuard.onError));
502
276
  }
503
- // core 1.364 durable bg-agent joint reap(定义在 interval 上方,契约注释在彼)。in-flight 守卫同
504
- // wfRun sweep(per-scope 串行循环,短 tick + 多 scope 下不叠罗汉)。
505
277
  if (reapBgAgents && !bgAgentReapInFlight) {
506
278
  bgAgentReapInFlight = true;
507
279
  leg(reapBgAgents()
508
280
  .then(() => bgAgentsReapGuard.onSuccess(), bgAgentsReapGuard.onError)
509
281
  .finally(() => (bgAgentReapInFlight = false)));
510
282
  }
511
- // [1522] MED2:agent_roster TTL 清理(core RB-23②③ 派给部署的半场)——SQL twins 扩展面
512
- // (reapOlderThan,duck probe;File/Memory 店 core 自带 maxAgeMs,无此面=no-op)。
513
283
  leg(rosterStore
514
284
  ?.reapOlderThan?.(Date.now() - config.rosterRetentionMs)
515
285
  .then(reapCount("roster_rows_reaped_total", {}))
516
286
  .then(() => rosterReapOlderThanGuard.onSuccess(), rosterReapOlderThanGuard.onError));
517
- // #193 车4 件1(codex 对抗复审 R1-high②):陈旧 `running` leader run 的清算 —— 驱动它的副本死了,
518
- // 行会永远停在 `running`(跨副本读面显示「在跑」而没有任何东西在跑)。到期迁 `failed` 并写清 WHY。
519
- // **不是接管**(理由见 leader-run-store-sql.ts 的 reapStaleRunning 头注)。腿只在开了 leader 的
520
- // 部署上注册:`leaderRunStore` 是 `config.leaderEnabled && backend?.leaderRun?.()` 的合取(见上面
521
- // 的取值处),默认关的部署零扫描。
522
287
  leg(leaderRunStore
523
288
  ?.reapStaleRunning(config.leaderRunStaleMs, Date.now())
524
289
  .then(reapCount("leader_runs_stale_reaped_total", {}))
525
290
  .then(() => leaderRunStaleGuard.onSuccess(), leaderRunStaleGuard.onError));
526
- // 修8(三路复审 absorb-2,接线 (b)):periodic scratchpad sweep — 无 E21 purge 兜到的孤儿目录(session 从未
527
- // DELETE、purge 当次失败、local 后端无 purge coordinator)按 mtime 过期回收。SCRATCHPAD_SWEEP_TTL_MS
528
- // (default 7d,0=禁用)。activeSessionIds 不传:活跃判据的诚实边界在 env-facts.ts 的 sweep 文档——TTL 7d
529
- // 远大于任何在飞任务;一个 7 天零写入的 scratchpad 被回收是可接受的(它本就是临时区,fact 文案即如此宣示)。
530
- // #151 车5:流内审批的对账收敛器 + 崩溃恢复扫描(两段都在 `runOnce` 内,见 approval-reconciler.ts
531
- // 判据表 v2)。**不新起独立定时器**——复用本 tick(收敛延迟上界 = 一个 reaper tick;壳侧的体感由
532
- // 车4 的 425 `Retry-After` 覆盖)。in-flight 守卫同邻居四腿:本腿时长随 PARKING 行数与两个读口的
533
- // 库延迟增长,短 REAP_INTERVAL_SEC 下无守卫会逐 tick 叠罗汉压同一批行。
534
291
  if (approvalReconciler && !approvalReconcileInFlight) {
535
292
  approvalReconcileInFlight = true;
536
293
  leg(approvalReconciler
@@ -538,16 +295,6 @@ export function startReapers(ctx) {
538
295
  .then(() => approvalReconcileGuard.onSuccess(), approvalReconcileGuard.onError)
539
296
  .finally(() => (approvalReconcileInFlight = false)));
540
297
  }
541
- // A-010.17:规则店的保留期腿。**只删可证已死的行**(过期导入票 + 超期孤儿 pending 记录)——
542
- // 谓词、每轮上界、以及「为什么这条腿不需要旋钮」逐字见 `plugins/permission-rule-store-sql.ts`
543
- // 的 `reapExpired` / `RULE_REAP_BATCH`。
544
- // `?.` 的缺席臂 = File 车道没有这一面(如实登记在 `rules-consent.ts` 的接口注里,不是漏接)。
545
- //
546
- // 🔴 重入守卫(codex 对抗复审 R2 [high],验真后修):第一版按「两条等值 DELETE,时长不随在飞量增长」
547
- // 判定无需守卫 —— 那句话**不成立**。`permission_rule_approval` 按设计永久留审计事实(只增不减),
548
- // 而 MySQL 存量库在删库重建之前拿不到新加的清扫索引 ⇒ 这条腿的时长会随**审计史**增长。一旦越过
549
- // tick 间隔,下一轮就叠在同一批行上。判据与邻居四腿(attachmentSweep / wfRunReap / bgAgentReap /
550
- // worktreeReap)同形。
551
298
  if (!permissionRuleReapInFlight) {
552
299
  permissionRuleReapInFlight = true;
553
300
  leg(permissionRuleStores
@@ -555,8 +302,6 @@ export function startReapers(ctx) {
555
302
  .then(reapCount("permission_rules_reaped_total", {}))
556
303
  .then(() => permissionRuleReapGuard.onSuccess(), permissionRuleReapGuard.onError)
557
304
  .finally(() => (permissionRuleReapInFlight = false)));
558
- // 店缺席(File 车道 / 关旋钮)⇒ 上面整条求值成 undefined、`finally` 永不跑 ⇒ 守卫要当场归位,
559
- // 否则第一轮之后这条腿就被自己的守卫永久锁死(而它本来就该零调用,锁死是静默的)。
560
305
  if (permissionRuleStores?.reapExpired === undefined)
561
306
  permissionRuleReapInFlight = false;
562
307
  }
@@ -568,9 +313,6 @@ export function startReapers(ctx) {
568
313
  })
569
314
  .then(() => scratchpadSweepStaleGuard.onSuccess(), scratchpadSweepStaleGuard.onError));
570
315
  }
571
- // #242(design/242 v1):E2B 孤儿沙盒回收。tick 内**时间节流**到 GRACE 周期(每 tick 打一次
572
- // provider list API 是浪费——孤儿的时间尺度是分钟级);首轮在进程起后第一个 tick 就跑(boot 半场,
573
- // 不单开 boot 调用点)。判据/边界全在 e2b-orphan-reclaim.ts;这里只管节流+重入+throttled catch。
574
316
  if (e2bOrphanProvider && runStore && Date.now() - e2bOrphanLastRunMs >= E2B_ORPHAN_GRACE_MS && !e2bOrphanInFlight) {
575
317
  e2bOrphanInFlight = true;
576
318
  e2bOrphanLastRunMs = Date.now();