@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,47 +1,11 @@
1
- /**
2
- * Fleet worker-side wiring(announce/heartbeat + usage 批报;契约 = registry-core 0.1.48+
3
- * `/fleet`,center 端点 = sema-registry `/api/fleet/*`)。设计要点:
4
- * - **可选分层(D3)**:仅当 center 已配置(sema-registry lane 的 baseUrl+token+worker)∧ `FLEET_ADVERTISE_ADDRESS`
5
- * 显式声明时才启动 —— 不配 center 的部署零行为变化。address 不自猜(k8s pod IP / docker alias / 127.0.0.1:port
6
- * 只有部署环境自己知道,故用显式 env)。
7
- * - **announce=注册=心跳同形状幂等 upsert**:boot 后 POST 一次 + 每 heartbeatMs 续;`ready = bootDone ∧ !draining`。
8
- * - **draining 翻转即刻再 announce**(第一时间自摘流,比任何观测都快);退出前 DELETE(显式注销,幂等)。
9
- * - **usage 批报**:tracer 的 `brain.call` 已带 ALS principal —— FleetUsageAccumulator 按 principal×model
10
- * 累计,每窗口(默认 60s,半开 [windowStartMs, windowEndMs))POST /api/fleet/usage。幂等键
11
- * (worker,instanceId,windowStartMs) 重发安全;**发送失败 → merge 回累计器**,并入下一窗(失败窗从未入账,
12
- * 向前合并不会双计 —— 只延迟,不丢量)。
13
- * - 全部 fire-and-forget + swallow-guarded:fleet 面是运维旁路,绝不影响任务面;网络错误只 warn 一次/连败类。
14
- */
15
1
  import { InstanceAnnouncement, UsageReport, DEFAULT_HEARTBEAT_MS } from "@sema-agent/registry-core/fleet";
16
- /** Bound on distinct principal×model keys per window — a runaway/multi-tenant burst collapses into
17
- * `__other__` instead of growing unbounded (same posture as budget.ts cardinalityGuard). */
18
2
  export const FLEET_USAGE_KEY_CAP = 1024;
19
- /** C1(2026-08-03):announce/usage/deregister 三站点的 fetch 上界(fleet-lease `LEASE_FETCH_TIMEOUT_MS`
20
- * 同款判据的普适形)。比 lease 的 2500 略宽——这三条不在提交热路径,5s 足以吃掉慢网 RTT,又远小于
21
- * undici 默认 ~300s headersTimeout(此前半开 center 把 stop()/心跳各钉住到那个量级)。 */
22
3
  export const FLEET_CLIENT_FETCH_TIMEOUT_MS = 5000;
23
- /**
24
- * Per-window principal×model usage accumulator, fed from the tracer's `brain.call` hook (which carries the
25
- * ALS principal — reaches council/team sub-task spend too, same attribution point costQuota uses). Sync,
26
- * never throws (TracerHook contract).
27
- */
28
4
  export class FleetUsageAccumulator {
29
- cells = new Map(); // key = principal <NUL> model
30
- runsSeen = new Set(); // taskId per window → runs count (first brain.call of a run in this window)
31
- /**
32
- * 🔴 RB-368(core 2.3.0;registry-core 0.11.0 收口,[2103]):`d.costMicroUsd` **可缺席 = 未知**
33
- * (unpriced 部署,budget.ts 如实不带键)。wire 自 0.11.0 起有缺席位(UsageEntry.costMicroUsd
34
- * optional)⇒ 本处**整键透传缺席**,不再折 0 低估。cell 级累加语义与 budget.ts ModelUsageTracker
35
- * 同款=**传染**(同为单 producer 累加器:已知+未知=未知);center 聚合面的「下界 + costUnknown
36
- * 标记」语义在 registry-core aggregateUsage(跨 worker 审计总账层,两层判据见 [2103])。
37
- * 配额轴(weightedTokens)不受影响,仍逐笔精确。
38
- *
39
- * core 3.0.0([2296] 装车单 S4c)语义注:`d.inputTokens` 在唯一调用点(budget.ts 的
40
- * `fleetUsage.record(...)`,S1 已迁)传的是 `e.totalInputTokens`(归一化总输入含 cache),不是
41
- * `e.promptTokens`(3.0.0 起恒 cache-MISS)——上游已切换口径,本函数只是照收不重算,值域/签名零改动。
42
- */
5
+ cells = new Map();
6
+ runsSeen = new Set();
43
7
  record(principal, model, taskId, d) {
44
- const p = principal ?? "__anonymous__"; // single-user turnkey: no principal — still meter, under a stable bucket
8
+ const p = principal ?? "__anonymous__";
45
9
  let key = `${p}\u0000${model}`;
46
10
  if (!this.cells.has(key) && this.cells.size >= FLEET_USAGE_KEY_CAP)
47
11
  key = `__other__\u0000__other__`;
@@ -52,11 +16,10 @@ export class FleetUsageAccumulator {
52
16
  if (cur) {
53
17
  cur.inputTokens += d.inputTokens;
54
18
  cur.outputTokens += d.outputTokens;
55
- // 缺席=未知 ⇒ 传染(见上方旁注);两边都已知才相加
56
19
  cur.costMicroUsd = cur.costMicroUsd === undefined || d.costMicroUsd === undefined ? undefined : cur.costMicroUsd + d.costMicroUsd;
57
20
  cur.weightedTokens += d.weightedTokens ?? 0;
58
21
  if (d.quotaWeightAtUse !== undefined)
59
- cur.quotaWeightAtUse = d.quotaWeightAtUse; // 审计尾迹=最新倍率(窗口内基本恒定)
22
+ cur.quotaWeightAtUse = d.quotaWeightAtUse;
60
23
  if (firstOfRun)
61
24
  cur.runs += 1;
62
25
  }
@@ -64,7 +27,6 @@ export class FleetUsageAccumulator {
64
27
  this.cells.set(key, { inputTokens: d.inputTokens, outputTokens: d.outputTokens, costMicroUsd: d.costMicroUsd, weightedTokens: d.weightedTokens ?? 0, quotaWeightAtUse: d.quotaWeightAtUse ?? 1, runs: firstOfRun ? 1 : 0 });
65
28
  }
66
29
  }
67
- /** Drain the window into UsageReport entries and RESET. Empty window → []. */
68
30
  drain() {
69
31
  const out = [];
70
32
  for (const [key, c] of this.cells) {
@@ -75,8 +37,6 @@ export class FleetUsageAccumulator {
75
37
  this.runsSeen.clear();
76
38
  return out;
77
39
  }
78
- /** Merge a FAILED batch back (delivery error → carry the amounts into the next window; never double-counts
79
- * because the failed window was never recorded center-side). */
80
40
  mergeBack(entries) {
81
41
  for (const e of entries) {
82
42
  let key = `${e.principal}\u0000${e.model}`;
@@ -88,7 +48,7 @@ export class FleetUsageAccumulator {
88
48
  if (cur) {
89
49
  cur.inputTokens += e.inputTokens;
90
50
  cur.outputTokens += e.outputTokens;
91
- cur.costMicroUsd = cur.costMicroUsd === undefined || e.costMicroUsd === undefined ? undefined : cur.costMicroUsd + e.costMicroUsd; // 回携未知同款传染
51
+ cur.costMicroUsd = cur.costMicroUsd === undefined || e.costMicroUsd === undefined ? undefined : cur.costMicroUsd + e.costMicroUsd;
92
52
  cur.weightedTokens += w;
93
53
  if (qw !== undefined)
94
54
  cur.quotaWeightAtUse = qw;
@@ -103,9 +63,6 @@ export class FleetUsageAccumulator {
103
63
  return this.cells.size;
104
64
  }
105
65
  }
106
- /** #123 F1 披露半场(§M):center 的拒绝响应体是操作员唯一的自诊线索(E2E #121:web 端明说了
107
- * 「address must be a parseable http(s) base URL」而 server 日志只有 `announce 400`)——摘要进错误
108
- * 信息(cap 200 字符);读体失败不改变结果面(照旧只有状态码)。 */
109
66
  async function rejectionSnippet(res) {
110
67
  try {
111
68
  const text = (await res.text()).slice(0, 200).trim();
@@ -115,10 +72,6 @@ async function rejectionSnippet(res) {
115
72
  return "";
116
73
  }
117
74
  }
118
- /**
119
- * Start the worker-side fleet loops. Caller has already gated on "center configured ∧ advertise address set"
120
- * (startFleetClientFromEnv below) — this function assumes a complete opts.
121
- */
122
75
  export function startFleetClient(opts) {
123
76
  const f = opts.fetchImpl ?? fetch;
124
77
  const heartbeatMs = opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
@@ -126,7 +79,7 @@ export function startFleetClient(opts) {
126
79
  const base = opts.center.baseUrl.replace(/\/+$/, "");
127
80
  const headers = { authorization: `Bearer ${opts.center.token}`, "content-type": "application/json" };
128
81
  let stopped = false;
129
- let failStreak = 0; // warn once per failure streak, not per tick (a down center must not spam logs)
82
+ let failStreak = 0;
130
83
  const announceNow = async () => {
131
84
  const body = {
132
85
  instanceId: opts.instanceId,
@@ -138,9 +91,6 @@ export function startFleetClient(opts) {
138
91
  heartbeatMs,
139
92
  };
140
93
  try {
141
- // 鲁棒性批1 C1(2026-08-03):三条对 center 的 fetch 全部界上 abort(fleet-lease O1 同款判据的
142
- // 普适形)——半开 center 下此前各挂 undici 默认 ~300s headersTimeout,stop() 的滚动更新被单个
143
- // 半开连接钉住;心跳侧每 tick 叠一个在飞请求。
144
94
  const res = await f(`${base}/api/fleet/instances`, { method: "POST", headers, body: JSON.stringify(body), signal: AbortSignal.timeout(FLEET_CLIENT_FETCH_TIMEOUT_MS) });
145
95
  if (!res.ok)
146
96
  throw new Error(`announce ${res.status}${await rejectionSnippet(res)}`);
@@ -161,9 +111,9 @@ export function startFleetClient(opts) {
161
111
  const windowEndMs = Date.now();
162
112
  const entries = opts.usage.drain();
163
113
  const start = windowStartMs;
164
- windowStartMs = windowEndMs; // half-open [start, end) — the next window starts exactly where this one ended
114
+ windowStartMs = windowEndMs;
165
115
  if (entries.length === 0)
166
- return; // nothing accrued — skip the POST entirely
116
+ return;
167
117
  const report = { worker: opts.center.worker, instanceId: opts.instanceId, windowStartMs: start, windowEndMs, entries };
168
118
  try {
169
119
  const res = await f(`${base}/api/fleet/usage`, { method: "POST", headers, body: JSON.stringify({ reports: [report] }), signal: AbortSignal.timeout(FLEET_CLIENT_FETCH_TIMEOUT_MS) });
@@ -171,12 +121,11 @@ export function startFleetClient(opts) {
171
121
  throw new Error(`usage ${res.status}${await rejectionSnippet(res)}`);
172
122
  }
173
123
  catch (err) {
174
- // Delivery failed → the window was never recorded center-side; carry the amounts forward (no loss, no double).
175
124
  opts.usage.mergeBack(entries);
176
125
  opts.logger?.warn("fleet_usage_report_failed", { error: err instanceof Error ? err.message : String(err), entries: entries.length });
177
126
  }
178
127
  };
179
- void announceNow(); // boot announce (registration = first heartbeat)
128
+ void announceNow();
180
129
  const hb = setInterval(() => void announceNow(), heartbeatMs);
181
130
  hb.unref?.();
182
131
  const uw = opts.usage ? setInterval(() => void flushUsage(), usageWindowMs) : undefined;
@@ -188,29 +137,20 @@ export function startFleetClient(opts) {
188
137
  clearInterval(hb);
189
138
  if (uw)
190
139
  clearInterval(uw);
191
- await flushUsage(); // final partial window — best-effort
140
+ await flushUsage();
192
141
  try {
193
142
  await f(`${base}/api/fleet/instances`, { method: "DELETE", headers, body: JSON.stringify({ instanceId: opts.instanceId, worker: opts.center.worker }), signal: AbortSignal.timeout(FLEET_CLIENT_FETCH_TIMEOUT_MS) });
194
143
  }
195
144
  catch {
196
- /* deregistration is best-effort — staleness (2× heartbeatMs) reaps us anyway */
197
145
  }
198
146
  };
199
147
  return { announceNow, stop };
200
148
  }
201
- /**
202
- * Env/config gate (D3 可选分层): start ONLY when the sema-registry lane is configured (baseUrl+token+worker)
203
- * AND the operator explicitly declared this instance's reachable address. Anything missing → undefined (zero
204
- * behavior change for non-fleet deployments); worker missing is warned when the rest is present (half-configured).
205
- */
206
149
  export function startFleetClientFromEnv(cfg, opts) {
207
150
  const cc = cfg.configCenter;
208
151
  const address = opts.advertiseAddress ?? process.env.FLEET_ADVERTISE_ADDRESS;
209
152
  if (!cc?.baseUrl || !cc.token || !address)
210
153
  return undefined;
211
- // #123 F1(E2E #121 立案):旋钮**设了但坏形** ≠ 没设——center 端要求可解析的 http(s) base URL,
212
- // 坏形(如缺 scheme 的 host:port)此前静默通过,启动后 fleet 腿每拍 announce 400、worker 永不注册。
213
- // env fail-loud 族判据([2443] A1 同族):这里前置响亮拒,部署在起点就失败可见,而不是带病运行。
214
154
  let parsed;
215
155
  try {
216
156
  parsed = new URL(address);
@@ -1,48 +1,17 @@
1
- /**
2
- * Fleet worker-side QUOTA-LEASE consumption(调度框架 D4=AP 模式的 worker 半场;
3
- * 契约 = registry-core `/fleet` QuotaLease + center `POST /api/fleet/leases`,center 侧已实装并经
4
- * chaos 测试验证)。
5
- *
6
- * ## 模型(roaming/CDN)
7
- * center 把某 principal 全局预算的一片(默认 $5 slice / 1h TTL)预授权给 ONE worker;worker 本地扣减
8
- * (`recordSpend`,零 per-call 往返),`shouldRenewLease`(任一维度 80% 或寿命 80%)提前后台续租;center 用
9
- * usage 批报事后单调对账(`/api/fleet/usage` 入库顺手 reconcile —— worker 不需要单独上报 consumed)。
10
- *
11
- * ## 协商式发现(谁走 lease?)
12
- * worker 不在本地解析 EntitlementBudget —— 首次 admit 向 center POST,center 用 resolveEntitlement 判:
13
- * - 200 {lease} → 该 principal 是 lease-enforced,进入本地扣减周期;
14
- * - 409 "principal is not lease-enforced" → 负缓存 {@link NOT_LEASE_TTL_MS},走既有本地 CostQuota 路径;
15
- * - 409 `code:"quota_exhausted"` → 提交拒绝(429,镜像 quotaExceeded 姿势),短缓存防打爆 center;
16
- * - 网络/5xx → **fail-open 放行**(AP 哲学:center 短暂不可达绝不停集群;对账兜底,
17
- * 过花敞口 ≤ slice 尺寸 —— 正是 D4 承诺的 trade)。
18
- * 单飞(single-flight):同 principal 并发首触/续租只发一个 RTT。
19
- *
20
- * ## 边界(documented,非疏漏)
21
- * - lease 状态 **in-memory**:worker 重启丢本地 consumed → center 续租时用 reports+monotone guard 补账,
22
- * 敞口同样 ≤ slice(AP 设计内)。
23
- * - 409 判别靠 center 返回的结构化 code(两端常量);center 侧变更时此处同步。
24
- * - 全部 swallow-guarded:lease 面绝不 fault 任务提交路径。
25
- */
26
1
  import { QuotaLease, leaseExhausted, shouldRenewLease } from "@sema-agent/registry-core/fleet";
27
- /** 负缓存 TTL:非 lease-enforced 的 principal 多久后再问一次 center(entitlement 升级为 lease 的收敛上界)。 */
28
2
  export const NOT_LEASE_TTL_MS = 10 * 60 * 1000;
29
- /** center 判 budget exhausted 后,多久内的重复提交直接拒绝而不再打 center(防 per-request 打爆)。 */
30
3
  export const EXHAUSTED_RETRY_MS = 30 * 1000;
31
- /** principal 状态表上界(镜像 FLEET_USAGE_KEY_CAP 的姿势):超界丢最旧 —— 被丢的 principal 下次 admit
32
- * 重新协商,一次多余 RTT,无正确性影响。 */
33
4
  export const LEASE_PRINCIPAL_CAP = 4096;
34
- /** 复审 O1:lease 协商 RTT 的硬上限——leaseDenied 在提交热路径同步 await 它,一个 accept-but-hang 的
35
- * center 必须迅速塌成 fail-open 而不是拖垮提交。取 2.5s(远小于任何续租余量,略紧于 registry 的 8s)。 */
36
5
  export const LEASE_FETCH_TIMEOUT_MS = 2500;
37
6
  export class FleetLeaseManager {
38
7
  opts;
39
8
  state = new Map();
40
- inflight = new Map(); // single-flight issue/renew per principal
9
+ inflight = new Map();
41
10
  f;
42
11
  base;
43
12
  headers;
44
13
  now;
45
- failStreak = 0; // warn once per outage streak (mirrors fleet-client)
14
+ failStreak = 0;
46
15
  constructor(opts) {
47
16
  this.opts = opts;
48
17
  this.f = opts.fetchImpl ?? fetch;
@@ -50,10 +19,6 @@ export class FleetLeaseManager {
50
19
  this.headers = { authorization: `Bearer ${opts.center.token}`, "content-type": "application/json" };
51
20
  this.now = opts.now ?? Date.now;
52
21
  }
53
- /**
54
- * Admission check for one billed submission. Fast path is synchronous-shaped (live lease / fresh negative
55
- * cache → no await beyond the microtask); only first-touch / expiry / exhaustion pays a center RTT.
56
- */
57
22
  async admit(principal) {
58
23
  const now = this.now();
59
24
  const st = this.state.get(principal);
@@ -63,24 +28,12 @@ export class FleetLeaseManager {
63
28
  return { ok: false, reason: "budget_exhausted", retryAfterSec: Math.ceil((st.until - now) / 1000), ...(st.detail ? { detail: st.detail } : {}) };
64
29
  }
65
30
  if (st?.kind === "lease" && !leaseExhausted(st.lease, now)) {
66
- // Renew EARLY in the background so the refresh RTT overlaps remaining headroom (never blocks a call).
67
31
  if (shouldRenewLease(st.lease, now))
68
32
  void this.issueOrRenew(principal).catch(() => undefined);
69
33
  return { ok: true };
70
34
  }
71
- // No usable state → negotiate with center (single-flight; blocks THIS submission only).
72
35
  return this.issueOrRenew(principal);
73
36
  }
74
- /** Local deduction — wired at the SAME tracer `brain.call` point as the usage accumulator (ALS principal),
75
- * sync + never throws (TracerHook contract). No-op unless the principal currently holds a lease.
76
- * registry-core 0.6.1 双轴:weightedTokens(加权 tokens 配额轴,weight-at-burn 已在调用方固化)
77
- * + costMicroUsd($ 轴,轴名沿用 0.5 既定)。任一轴耗尽=leaseExhausted 拒(deny-wins,registry 类型层定死)。
78
- *
79
- * 🔴 RB-368(core 2.3.0)已核 undefined 安全:两轴都是 `?? 0`,unpriced 部署下 budget.ts 不带
80
- * costMicroUsd 键调进来 ⇒ $ 轴不扣、不产 NaN。**这是有意的 fail-open**:租约是**执行面**,把"未知"
81
- * 当"扣满"会让没配价目表的部署一上来就被 leaseExhausted 拒掉所有请求(deny-wins),那是把可观测性
82
- * 缺口升级成可用性事故。补偿=配额轴(weightedTokens)不依赖价目表、逐笔精确,治理在 unpriced 部署下
83
- * 仍然咬得住;$ 轴对这类部署等于未启用,这一点在部署位 `capabilities.pricingConfigured` 上可读回。 */
84
37
  recordSpend(principal, d) {
85
38
  if (!principal)
86
39
  return;
@@ -90,16 +43,15 @@ export class FleetLeaseManager {
90
43
  st.lease.consumed.weightedTokens += d.weightedTokens ?? 0;
91
44
  st.lease.consumed.costMicroUsd += d.costMicroUsd ?? 0;
92
45
  }
93
- /** Test/observability hook: the current state kind for a principal (never exposes the token). */
94
46
  peek(principal) {
95
47
  return this.state.get(principal)?.kind;
96
48
  }
97
49
  set(principal, st) {
98
50
  if (!this.state.has(principal) && this.state.size >= LEASE_PRINCIPAL_CAP) {
99
- const oldest = this.state.keys().next().value; // insertion order ≈ LRU-enough for a cap backstop
51
+ const oldest = this.state.keys().next().value;
100
52
  this.state.delete(oldest);
101
53
  }
102
- this.state.delete(principal); // re-insert at tail
54
+ this.state.delete(principal);
103
55
  this.state.set(principal, st);
104
56
  }
105
57
  issueOrRenew(principal) {
@@ -112,11 +64,6 @@ export class FleetLeaseManager {
112
64
  }
113
65
  async issueOrRenewInner(principal) {
114
66
  try {
115
- // 🔴 复审 O1(Opus,3 镜头独立命中):BOUND the negotiation RTT. leaseDenied awaits this on the SUBMISSION
116
- // hot path (11 sites), so a center that ACCEPTS the TCP connection but never responds (brownout/half-open LB —
117
- // exactly the outage AP is meant to ride out) would otherwise hang up to undici's ~300s default headersTimeout
118
- // before the fail-open catch fires, stalling every first-touch/expired principal's submissions. A short abort
119
- // lands in the catch below → prompt fail-open. Matches the codebase's outbound-to-center timeout convention.
120
67
  const res = await this.f(`${this.base}/api/fleet/leases`, {
121
68
  method: "POST",
122
69
  headers: this.headers,
@@ -125,7 +72,7 @@ export class FleetLeaseManager {
125
72
  });
126
73
  if (res.status === 200) {
127
74
  const body = (await res.json());
128
- const lease = QuotaLease.parse(body.lease); // zod — a malformed center response fails into the catch (fail-open)
75
+ const lease = QuotaLease.parse(body.lease);
129
76
  this.set(principal, { kind: "lease", lease });
130
77
  if (this.failStreak > 0)
131
78
  this.opts.logger?.info?.("fleet_lease_recovered", { after: this.failStreak });
@@ -135,16 +82,8 @@ export class FleetLeaseManager {
135
82
  }
136
83
  if (res.status === 409) {
137
84
  const body = (await res.json().catch(() => ({})));
138
- // 🔴 复审 O3(Opus):discriminate on a POSITIVE exhaustion match; ANY OTHER 409 (incl. a reworded
139
- // not-lease-enforced, or an unforeseen refusal) defaults to the PASS-THROUGH direction — a center wording
140
- // drift must not flip legitimate traffic into repeated 429 denial. Failing OPEN on an ambiguous 409 keeps
141
- // the AP posture (the local CostQuota path still governs); denying is reserved for an explicit exhaustion.
142
- // typed 唯一语义:`code==="quota_exhausted"`(E4 定稿 typed code)。旧常量
143
- // `error:"budget exhausted"` 的过渡 fallback 已删——center 侧旧常量已撤,该 fallback 成死分支,已删净。
144
85
  if (body.code === "quota_exhausted") {
145
86
  const until = this.now() + EXHAUSTED_RETRY_MS;
146
- // E4 结构化字段(windowType/remaining/resetAt/isEstimate/pool)verbatim 缓存进 exhausted 态——
147
- // EXHAUSTED_RETRY_MS 窗内的重复提交拒绝也带同一 detail(不再打 center),429 body 透传归 HTTP 层。
148
87
  const detail = {
149
88
  ...(body.windowType !== undefined ? { windowType: body.windowType } : {}),
150
89
  ...(body.remaining !== undefined ? { remaining: body.remaining } : {}),
@@ -157,16 +96,9 @@ export class FleetLeaseManager {
157
96
  this.opts.metrics?.inc("fleet_lease_exhausted_total");
158
97
  return { ok: false, reason: "budget_exhausted", retryAfterSec: Math.ceil(EXHAUSTED_RETRY_MS / 1000), ...(hasDetail ? { detail } : {}) };
159
98
  }
160
- // not-lease-enforced (the common case) OR any other 409 → local CostQuota path governs this principal.
161
99
  this.set(principal, { kind: "not-lease", until: this.now() + NOT_LEASE_TTL_MS });
162
100
  return { ok: true };
163
101
  }
164
- // 🔴 复审 O2(Opus):an auth/config error (401/403/400) is operator MISCONFIG (expired service token, wrong
165
- // worker id, URL drift) — NOT an availability event. Failing open silently would disable budget enforcement
166
- // indefinitely (a stealth bypass) with only a once-per-streak warn. Fail open STILL (never lock a tenant out
167
- // on a token blip) but make it LOUD + observable: a dedicated metric + a warn EVERY occurrence (not deduped),
168
- // and DON'T cache — so a persistent misconfig re-warns on every admit and can't hide. No negotiated state is
169
- // recorded, so recovery is immediate once the config is fixed.
170
102
  if (res.status === 401 || res.status === 403 || res.status === 400) {
171
103
  this.opts.logger?.warn?.("fleet_lease_auth_error_fail_open", { status: res.status, principal, note: "center rejected the lease request — check the service token / worker id / center URL (budget NOT enforced until fixed)" });
172
104
  this.opts.metrics?.inc("fleet_lease_auth_error_total", { status: String(res.status) });
@@ -175,7 +107,6 @@ export class FleetLeaseManager {
175
107
  throw new Error(`lease ${res.status}`);
176
108
  }
177
109
  catch (err) {
178
- // center unreachable / 5xx / timeout / malformed → FAIL-OPEN (AP posture): admit, keep whatever state we had.
179
110
  this.failStreak += 1;
180
111
  if (this.failStreak === 1)
181
112
  this.opts.logger?.warn?.("fleet_lease_unavailable_fail_open", { error: err instanceof Error ? err.message : String(err) });
@@ -184,11 +115,6 @@ export class FleetLeaseManager {
184
115
  }
185
116
  }
186
117
  }
187
- /**
188
- * Env/config gate(与 startFleetClientFromEnv 同 D3 分层姿势,但 lease 不需要 FLEET_ADVERTISE_ADDRESS ——
189
- * 它是出站消费面,不是入站可达性声明):sema-registry lane 配齐(baseUrl+token+worker)即启;任一缺失 →
190
- * undefined,非 fleet 部署零行为变化。
191
- */
192
118
  export function createFleetLeaseFromEnv(cfg, opts) {
193
119
  const cc = cfg.configCenter;
194
120
  if (!cc?.baseUrl || !cc.token || !cc.worker)
@@ -1,6 +1,3 @@
1
- /** Git-host API dialect word table (P1-2). Leaf module — the single owner shared by the config layer
2
- * (env parse/validation) and `capabilities/repo-tools` (the two client implementations); config must not
3
- * value-import the capabilities layer (module-cycle gate §G), so the table lives below both. */
4
1
  export const GIT_API_KINDS = ["gitea", "github"];
5
2
  export function isGitApiKind(v) {
6
3
  return typeof v === "string" && GIT_API_KINDS.includes(v);
@@ -1,76 +1,6 @@
1
- /**
2
- * [2942]/[2943] `governanceForced` 的**判定缝** —— operator 治理层产的 ask 在 server 进程内的标记通道。
3
- *
4
- * 🔴 为什么需要一条 out-of-band 通道(侦察结论,亲验装树 core dist;5.16.x 首次落笔,5.23.0 复核仍成立):
5
- * `PermissionResult.decisionReason`(闭集,词表以 core 的 `DecisionReason` 为准 —— 本文**刻意不复述**那
6
- * 张表:复述出来的词表会随 core 加员静默过期,而这段话的论点与表里有几个词无关)**到不了帧铸点**。
7
- * core 在 `dist/core/runner/prepare-task.js` 的 ask 铸造点(`resolveAsk({...})`,三处继承臂 + 主臂)只把
8
- * `toolName / toolCallId / args / preview / message / askSourceIdentity() / riskAxesOf() / requiresRealApproval`
9
- * 装进 `AskRequest`;`decisionReason` 连同整个 `PermissionResult` 一起留在 core 内部。`AskRequest` 的类型面
10
- * (`dist/core/tool-policy.d.ts:78`)也确认没有这个键。所以「映射 decisionReason ⇒ governanceForced」这条
11
- * 缝在**当前 core 上不存在**——它不是没接线,是没有这个字段可读。
12
- * ([3372] 提货批复核)core 5.23.0 给这个闭集加了 `org_rule` / `org_unavailable` 两个**治理来源**的词
13
- * (design/182 org 层)。这看着像是本表终于有了上游判据,其实**不是**:①`AskRequest` 的类型面在 5.23.0
14
- * 上逐字未变,那两个词仍然到不了帧铸点(缝还是不存在);②即便够得着,它们说的是**组织**发布的快照,
15
- * 与本键的语义(**本部署运维治理层**产的 ask)不是一回事,直接映射会把两种来源混成一个标记。本仓无
16
- * org 层装配口(`ToolGateInput.orgRules` 无声明点),该臂当前不可达。
17
- *
18
- * 另一侧同样不可用:`riskAxes.irreversible` 在 shellGate 上场时对**每一次** shell 调用都为真(core
19
- * `prepare-task.js:1620-1631` 无条件 `irreversibilityTier.set("Bash"/"Monitor", …)`),与「这只 ask 是谁
20
- * 提的」无关;`requiresRealApproval` 的生产者是 core 自己的两条安全策略(unverifiable-delete /
21
- * transcript-integrity),与治理层无关。两者都不是判据。
22
- *
23
- * ⇒ 采用的方案 = **server 标记自家产物**:治理层策略(`runtime-governance.ts` 的
24
- * `applyRuntimeGovernance`)在合成时被包一层观察器,自己产 `ask` 时把该次调用的 `toolCallId` 记进本表;
25
- * 帧铸点(`tool-approval.ts` 的 `askBroadcast`)按 `toolCallId` 回查。
26
- * **禁把信号藏进 message 文本**——那是 in-band hack,消费端只能靠正则猜。
27
- *
28
- * ## 三条硬语义
29
- *
30
- * 1. **只做正向断言**。命中 = 有直接证据(治理策略本人产的 ask,或治理层把 shellGate 抬到 `"always"` 后
31
- * 的 shell 调用——该档下每一次 shell 调用都被治理层门住,推断是紧的)。**未命中 ≠ 「不是治理」**,
32
- * 只是「没有证据」⇒ wire 上键**缺席**,绝不编一个 `false`。宁可漏标,不可错标:一个错标会把运维支去
33
- * 翻一份根本没门住这只 ask 的治理配置。
34
- * 2. **peek,不 consume**。同一 `toolCallId` 可能被广播两次(重试 / failover / 闭包再入,见
35
- * `tool-approval.ts` 里 `pendingByAskId` 的顶注),消费式读会让第二帧莫名丢键。
36
- * 3. **有界**。写入点是每一次工具调用,读取点只在 ask 上——未被读到的标记(调用被放行 / 被别的策略 deny)
37
- * 必须能自己老化掉。按插入序淘汰最旧的(`Set` 的迭代序 = 插入序)。
38
- *
39
- * ## 作用域:按**审批 broker key** 分格,不是进程级一锅烩
40
- *
41
- * 🔴 codex 交叉复审 round3 [medium](验真):`toolCallId` **不是**全局唯一的 —— 它多数情况下是**提供方**
42
- * 逐字给的 id(core 的 brain 层不另铸),本仓自己的代码就是这么假设的:core 把 tool-result 引用命名成
43
- * `tr_<sessionId>~<toolCallId>~<contentSeg>`(core 5.26.0 #119 起的四段单射形;见 `plugins/tool-result-store-sql.ts`
44
- * 的 `deleteBySession` 顶注),
45
- * `deriveAskId` 也把 `sourceTaskId`/`runId`/`legKey` 一并入哈希。一张不分格的表会**跨任务/跨租户串味**:
46
- * 低熵 id(本地/自建模型服务常见的 `call_1` 之类)下,A 任务的治理标会被 B 任务的普通 ask 读走(假出处),
47
- * 或被 B 的一次治理放行反手清掉(丢真出处)。
48
- *
49
- * 分格用的 **scope = 审批协调器的 broker key**(`["session", owner, sessionId]` / `["adhoc", owner, taskId]`)。
50
- * 选它的理由是**两侧都够得到、且是同一个值**:
51
- * · 写侧(治理策略的观察器跑在 core 的 `policy.check` 里,入参只有 `ToolCallRequest`)⇒ 经
52
- * AsyncLocalStorage 拿 —— 作用域由 `runWithContext` 与审批 ctx **同拍**进出。
53
- * · 读侧(`askBroadcast`)⇒ 由 `origin`/`primary` **显式算出**同一把 key。
54
- *
55
- * 🔴 读侧**显式算**而不是也读 ALS(codex 交叉复审 round5 [medium],红先复现后改的形):委派/长命 bg 子代
56
- * 的 ask 可能在**另一条腿**的上下文里发出(宿主腿退场、壳重连出新腿,子代的卡落到新腿的流上)。读侧若取
57
- * 环境里的那张表,拿到的是**新腿**的空表,信号静默丢失 —— 那条路径已由
58
- * `test/governance-forced-signal.test.ts` 的「长命 bg 子代形」用例钉住(改形前它是红的)。scope 由**出处**
59
- * 决定、与卡投递到哪条连接无关,与本仓 `AskOriginIdentity` 顶注「投递面与出处面是两件事」同源。
60
- *
61
- * 残余(成文):同一 (owner, sessionId) 下的**并发多 run** 共用一格 —— 那正是 broker 自己的分组粒度
62
- * (卡本来就跨该会话投递),同租户同会话内的 id 碰撞才会互串,比进程级一锅烩窄得多。要再窄需要一个
63
- * 写侧也够得到的 run 级身份,core 的 `ToolCallRequest` 今天给不出。
64
- */
65
1
  import { AsyncLocalStorage } from "node:async_hooks";
66
- /** 默认表容量(全进程合计,复合键计数)。一次 turn 的工具调用数远小于此;满表淘汰的是最旧的标记
67
- * (那些早已不可能再被读到)。 */
68
2
  const DEFAULT_MAX_MARKS = 1024;
69
- /** 复合键。JSON 元组编码(同 `streamKey`/`sessionAllowKey` 先例):任何一段里含分隔符都不可能与下一段
70
- * 的边界产生歧义 —— 一个精心构造的 sessionId 因此无法伪造出另一格。 */
71
3
  const compositeKey = (scope, toolCallId) => JSON.stringify([scope, toolCallId]);
72
- /** 一张独立的标记表(不进程共享)。生产路径用的是下面 `governanceAskMarksFor` 给出的**分格视图**;
73
- * 本工厂给测试(与将来的多实例形)一个不依赖任何环境的干净表。 */
74
4
  export function createGovernanceAskMarks(opts) {
75
5
  return createScopedView(new Set(), "", Math.max(1, Math.floor(opts?.max ?? DEFAULT_MAX_MARKS)));
76
6
  }
@@ -78,12 +8,11 @@ function createScopedView(marks, scope, max) {
78
8
  return {
79
9
  setMarked(toolCallId, marked) {
80
10
  if (typeof toolCallId !== "string" || toolCallId === "")
81
- return; // 无身份 ⇒ 无从回查,不占格
11
+ return;
82
12
  const key = compositeKey(scope, toolCallId);
83
- // 重标记要**刷新新鲜度**:先删后加把它挪到插入序尾部,免得一只长命的 ask 被自己后来的调用挤掉。
84
13
  marks.delete(key);
85
14
  if (!marked)
86
- return; // 覆写成「不是治理门」= 清格(setMarked 顶注:复查路径的陈旧标)
15
+ return;
87
16
  marks.add(key);
88
17
  while (marks.size > max) {
89
18
  const oldest = marks.values().next();
@@ -109,20 +38,15 @@ function createScopedView(marks, scope, max) {
109
38
  },
110
39
  };
111
40
  }
112
- /** 进程级底表(分格由复合键承担,见顶注「作用域」段)。 */
113
41
  const processMarks = new Set();
114
- /** 写侧的 scope 载体 —— 由 `ToolApprovalCoordinator.runWithContext` 与审批 ctx 同拍进出。 */
115
42
  const scopeAls = new AsyncLocalStorage();
116
- /** 进入一条 run 腿的标记作用域(唯一调用点 = `ToolApprovalCoordinator.runWithContext`)。 */
117
43
  export function runWithGovernanceAskScope(scope, fn) {
118
44
  return scopeAls.run(scope, fn);
119
45
  }
120
- /** **写侧**入口:当前异步作用域那一格的视图;作用域外 = `undefined`(写 no-op,见顶注)。 */
121
46
  export function currentGovernanceAskMarks() {
122
47
  const scope = scopeAls.getStore();
123
48
  return scope === undefined ? undefined : createScopedView(processMarks, scope, DEFAULT_MAX_MARKS);
124
49
  }
125
- /** **读侧**入口:按**显式** scope 取视图(读侧不读 ALS —— 理由见顶注 round5 那段)。 */
126
50
  export function governanceAskMarksFor(scope) {
127
51
  return createScopedView(processMarks, scope, DEFAULT_MAX_MARKS);
128
52
  }
@@ -1,47 +1,5 @@
1
- /**
2
- * 把 core 的 `StopHookContext.getBranch()` 交出来的 **entries** 渲染成评估者能读的 transcript。
3
- *
4
- * ── 为什么渲染这件事本身带一条安全判据 ──────────────────────────────────────────────────────────
5
- * CC 的条件评估提示词里有这一句(我从语料逐字核过,见 `cc-stop-prompt.ts`):
6
- * > If the transcript does not contain clear evidence that the condition is satisfied, return
7
- * > `{"ok": false, "reason": "insufficient evidence in transcript"}`.
8
- * 而 Stop 钩子的 `ok:false` = **拦住停止**。
9
- * ⇒ **证据面为空时,一个完全守规矩的模型会给出一个格式良好的「拦」** —— 那不是解析失败(解析会成功、
10
- * 判词有效),所以任何解析层的防线都救不了。**唯一的防线是不进这条路。**
11
- *
12
- * 🔴 判据必须挂在**真正决定结果的那个量**上,也就是**最终送进模型的那段文本**,
13
- * **不是**挂在 `getBranch` 这个访问器的存在性上(core 黑板 [1758] 指出我原设计挂错了位置):
14
- * · `getBranch` 在、返回 `[]` —— 结构上可能(还没产出条目的分支);
15
- * · 更常见的一格:**渲染之后为空** —— entries 有,但我这边的过滤条件一条都没命中,
16
- * 或预算把它截没了。**那是纯自伤,而且每一层看起来都正常。**
17
- * ⇒ 所以本模块的返回类型是**判别式**的(`sufficient: true | false`),而不是"返回一个字符串,
18
- * 调用方自己记得判空" —— 后者迟早会有人忘。
19
- *
20
- * ── 截断的第二个坑(同源) ──────────────────────────────────────────────────────────────────────
21
- * **静默截断会自己制造「证据不足」**:证据在被截掉的那一段里,模型如实回 `ok:false` ⇒ 拦。
22
- * ⇒ 截断时必须在文本里**明说截了**,让模型知道自己看到的是一个后缀,而不是全部。
23
- */
24
1
  import { redactSecrets } from "../trace/redact.js";
25
- /**
26
- * ── [2092] 渲染臂的判据:**证据面与模型所见同进同出** ─────────────────────────────────────────────
27
- * core 的 `SessionTreeEntry` 是 12 型联合,曾经这里只渲 message/compaction 两支 ⇒ 10 型静默丢弃。
28
- * 其中 `custom_message` 正是非标准入口用户内容(steering)的落点:core `convertToLlm` 把它按
29
- * `role:"user"` 送模型,而证据面渲没了 ⇒ 评估者如实判「证据不足」⇒ 拦停死循环直到 CAP(cli 带
30
- * 最小复现的真缺陷)。所以分类规则不是「渲会话内容」,而是:**模型看得见的必须在,看不见的必须不在**。
31
- *
32
- * 两张表 = 对 core 型全集的**显式全分类**;test/branch-transcript.test.ts 里的闭合钉从 core d.ts
33
- * 逐字取联合成员核对「型全集 = 渲染臂 ∪ 跳过表」—— core 加第 13 型 ⇒ 红,逼一次显式分类,
34
- * 默认可见(下面循环的 default 臂做通用文本抽取)而不是默认失踪。
35
- */
36
2
  export const RENDERED_ENTRY_TYPES = ["message", "compaction", "custom_message"];
37
- /**
38
- * 跳过的理由逐型给(「纯控制型」不是一句话,是逐个核过模型看不见):
39
- * thinking_level_change / model_change / label / session_info / leaf / prompt_epoch /
40
- * announced_listing / workspace_state / git_announcement / reminder_mark —— 控制面元数据,core 折
41
- * messages 时不产出内容;
42
- * custom —— data-only(session.js 折 messages 只认 message/custom_message 两型),模型看不见它的
43
- * data;把它渲出来反而是把模型没见过的东西冒充成证据,与 [2092] 是同一判据的反方向。
44
- */
45
3
  export const SKIPPED_ENTRY_TYPES = [
46
4
  "thinking_level_change",
47
5
  "model_change",
@@ -51,24 +9,11 @@ export const SKIPPED_ENTRY_TYPES = [
51
9
  "prompt_epoch",
52
10
  "announced_listing",
53
11
  "workspace_state",
54
- // core 5.36.0(env-tail 迁移):git-status 帧的**已通告态**快照条目(`{kind,hash,entryId?,pending?}`)。
55
- // 与上面两位同族、同理由 —— 它是控制面的「模型上次被展示的是哪一份 git 视图」记号,**不是**模型看见的
56
- // 内容:真正被看见的那份帧正文骑在它 `entryId` 指的那条 `message` 上(已由渲染臂收下)。把这条快照渲进
57
- // 证据面 = 把一串 sha256 摘要当成对话冒充给评估者,正是 [2092] 判据的反方向。
58
12
  "git_announcement",
59
- // core 5.46.0(design/319 A 件):会话的**提醒出处标记**条目(`{type:"reminder_mark", mark}`)。同族
60
- // 控制面元数据 —— 它是「本会话的引擎提醒/系统提示声明用的是哪一枚 mark」的记号,模型看见的是**别处**
61
- // 那些带 mark 的提醒正文本身(已由渲染臂按 message 收下),这条条目本身从不进 `convertToLlm`。
62
- // 把一枚不透明 token 渲进证据面 = 拿模型没见过的东西冒充证据([2092] 判据的反方向)。
63
13
  "reminder_mark",
64
14
  "custom",
65
15
  ];
66
- /** 单次评估送进模型的 transcript 字符上限。超出保留**尾部**(最近的对话对"条件是否已达成"更相关)。 */
67
16
  export const BRANCH_TRANSCRIPT_MAX_CHARS = 60_000;
68
- /** content(string | 片段数组)→ 可读文本。只取 text 片段;image/工具结果等非文本片段不进 transcript。
69
- * 已登记的残余窄形(F5,2026-07-30 复审):core `toNormalizedTextBlock` 会把混进 content 的非法块
70
- * 文本化成 `[normalized] {json}` 送模型,证据面这里丢弃 —— 模型可见/证据缺席方向的缺口,但触发
71
- * 前提是非标准块混进 custom content(上游产生面不可达);若上游真出现该形,闭合钉/评估者误拦会先暴露。 */
72
17
  function contentText(c) {
73
18
  const parts = [];
74
19
  if (typeof c === "string")
@@ -82,11 +27,6 @@ function contentText(c) {
82
27
  }
83
28
  return parts.join("\n").trim();
84
29
  }
85
- /**
86
- * `role:"bashExecution"` 消息(`!` 命令)→ 模型所见文本。镜像 core `bashExecutionToText`
87
- * (dist/engine/harness/messages.js —— core 根不导出,本地镜像,锚在闭合钉+本注上):
88
- * convertToLlm 把它按 `role:"user"` 送模型,除非 `excludeFromContext`(那时模型看不见 ⇒ 证据面也不该有)。
89
- */
90
30
  function bashExecutionText(m) {
91
31
  if (m["excludeFromContext"])
92
32
  return undefined;
@@ -99,25 +39,17 @@ function bashExecutionText(m) {
99
39
  text += `\n\nCommand exited with code ${String(m["exitCode"])}`;
100
40
  return text;
101
41
  }
102
- /** 从一条 message entry 里抽人类可读文本。 */
103
42
  function messageText(entry) {
104
43
  const m = entry.message;
105
44
  if (!m || typeof m.role !== "string")
106
45
  return undefined;
107
46
  if (m.role === "bashExecution") {
108
47
  const text = bashExecutionText(m)?.trim();
109
- // convertToLlm 对 bashExecution 的送模型 role 是 "user" —— 证据面同款,别让评估者猜生僻 role。
110
48
  return text ? { role: "user", text } : undefined;
111
49
  }
112
50
  const text = contentText(m.content);
113
51
  return text ? { role: m.role, text } : undefined;
114
52
  }
115
- /**
116
- * 渲染成 transcript。
117
- *
118
- * ⚠️ **compaction 的 summary 也算证据**:一次压缩之后,早期内容**只**活在那段摘要里 ——
119
- * 把它滤掉等于亲手制造「证据不足」(而那正好会被判成"拦")。
120
- */
121
53
  export function renderBranchTranscript(entries, opts) {
122
54
  if (!entries || entries.length === 0)
123
55
  return { sufficient: false, reason: "no-entries" };
@@ -136,8 +68,6 @@ export function renderBranchTranscript(entries, opts) {
136
68
  continue;
137
69
  }
138
70
  if (e.type === "custom_message") {
139
- // [2092] 真缺陷的落点:steering 等非标准入口的用户内容。core convertToLlm 把它按 role:"user"
140
- // 送模型(display 只管 UI,不改模型可见性)—— 证据面同进同出,渲成 user 行。
141
71
  const text = contentText(e.content);
142
72
  if (text)
143
73
  lines.push(`user: ${text}`);
@@ -145,8 +75,6 @@ export function renderBranchTranscript(entries, opts) {
145
75
  }
146
76
  if (SKIPPED_ENTRY_TYPES.includes(e.type))
147
77
  continue;
148
- // default 臂:比我们的分类表更新的型(core 加了第 13 型、部署窗里还没重新分类)——
149
- // **默认可见而不是默认失踪**:通用文本抽取,能抽出来就带型标渲出;闭合钉会在 CI 红,逼显式分类。
150
78
  const generic = contentText(e.content ?? e.text);
151
79
  if (generic)
152
80
  lines.push(`[${e.type}] ${generic}`);
@@ -154,11 +82,9 @@ export function renderBranchTranscript(entries, opts) {
154
82
  if (lines.length === 0)
155
83
  return { sufficient: false, reason: "nothing-renderable" };
156
84
  const max = opts?.maxChars ?? BRANCH_TRANSCRIPT_MAX_CHARS;
157
- // UNTRUSTED:transcript 是会话内容,与 fleet 行 / workflow 名同一条脱敏纪律。
158
85
  let text = redactSecrets(lines.join("\n\n"));
159
86
  let truncated = false;
160
87
  if (text.length > max) {
161
- // 保留**尾部**;并**明说截了** —— 静默截断会让模型把"证据在被截掉那段里"如实报成"证据不足" ⇒ 拦。
162
88
  text = `[earlier turns omitted — this transcript is truncated to the most recent portion]\n\n${text.slice(text.length - max)}`;
163
89
  truncated = true;
164
90
  }