@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,21 +1,3 @@
1
- /**
2
- * design/158 A10:composition root 分段 —— 执行环境装配(per-task 镜像 / cwd·shellEnv 注册表 /
3
- * 五条 remote-exec 腿的工厂 / 三层工厂装饰 / LSP 管理器)。
4
- *
5
- * 纯搬运:函数体逐字来自 `main.ts`(原 997-1302 行),缩进不变;新增的只有 import 与包壳。
6
- *
7
- * ⚠️ **位置即契约 —— 装饰顺序就是行为**:`executionEnvFactory` 由内向外被依次包装,
8
- * 次序逐字保留不可动:
9
- * base(e2b|k8s|ssh|adb|host|local-docker)
10
- * → `withRemoteScratchpad`(远程车道首 exec 才 mkdir)
11
- * → `withWorktreeIsolation`(host 车道 per-agent worktree,定 cwd)
12
- * → `TaskEnvRegistry.wrapFactory`(SendUserFile 直传登记 taskId→env)
13
- * → 附件物化(内层先定 cwd,附件才落在最终 cwd —— 原文逐字注释即此)
14
- * → `withSandboxPathEnvSlot`(#165,**最外层**:沙箱写门要裁决的是 core 最终拿到手的那个实例,
15
- * 而内层会换实例/改写方法,所以登记必须排在全部换手之后)。
16
- * 另:`lspManager` 声明在 host 工厂**之后**,host 工厂闭包对它是同作用域前向引用(闭包每任务才跑,
17
- * 那时已初始化)——本段整体搬运保住了这个前向引用,拆开两个模块就会破。
18
- */
19
1
  import { existsSync } from "node:fs";
20
2
  import { join } from "node:path";
21
3
  import { NodeLspManager } from "@sema-agent/core";
@@ -37,50 +19,24 @@ import { reapOrphanWorktrees, withWorktreeIsolation } from "../plugins/worktree-
37
19
  import { customPkgSourceFromEnv, derivePkgSourceEnv } from "../sandbox-pkg-source.js";
38
20
  import { effectiveHostWorkspace } from "../task-cwd.js";
39
21
  import { isSandboxPathAdjudicationLane, sandboxPathEnvSlots, withSandboxPathEnvSlot } from "./deferred-sandbox-path-env.js";
40
- /**
41
- * #97 R11: the perSessionCwd/perSessionShellEnv setters below share one idiom (delete-then-set so a
42
- * re-set moves the tail to MRU, then oldest-first eviction on overflow). Local to this file — NOT the
43
- * public {@link import("../bounded-session-map.js").BoundedSessionMap} seam, because both callers below
44
- * still hand out the raw `Map` (its declared type threads through `resolve-spec.ts` as `Map<string, V>`,
45
- * not a class instance) — this only DRYs the two identical closures, it changes no public shape.
46
- */
47
22
  function boundedSessionSetter(map, maxEntries) {
48
23
  return (sid, value) => {
49
- map.delete(sid); // re-insert at the tail = most-recently-used
24
+ map.delete(sid);
50
25
  map.set(sid, value);
51
26
  if (map.size > maxEntries)
52
- map.delete(map.keys().next().value); // evict oldest
27
+ map.delete(map.keys().next().value);
53
28
  };
54
29
  }
55
30
  export function createExecutionEnv(ctx) {
56
31
  const { config, logger, metrics, taskAttachmentStore } = ctx;
57
- // design/48 v1b: deployment-level remote execution(部署级路由 + 懒汉连接). When
58
- // REMOTE_EXEC=e2b, this deployment is a "code-agent worker" — each task's hand runs in a per-task E2B VM,
59
- // provisioned lazily on first hand use (a plan-only task pays zero VM cost). Unset → in-process stub env.
60
- // design/48 v1b + design/61: pick the remote-exec backend by provider (peer adapters). SSH/ADB target REAL
61
- // systems (not isolated/suspendable) — the leader's autonomy + the design/37 gate + HITL run accordingly.
62
- // §7 P0.5 per-task sandbox image: the trusted-control-plane bridge from resolveSpec (resolves the requested
63
- // profile → digest with the caller's principal, fail-closed) to the k8s factory (applies it per-pod). See
64
- // per-task-image.ts + resolveSpec below. Worker-global default image is the fallback when no profile is requested.
65
32
  const perTaskImage = new PerTaskImageRegistry();
66
- // RFC A2: session-keyed environment selection (PROFILE intent, never a digest) written by the model-facing
67
- // SelectEnvironment tool; resolveSpec re-resolves it FAIL-CLOSED per task (same re-admit as a body profile).
68
33
  const sessionEnvSelection = new SessionEnvironmentSelection();
69
- // [#40 / TOC cwd seam] per-session launch dir → the host lane's agent workspace (resolveSpec registers it gated by
70
- // cwdHonored; the host factory reads it by ctx.sessionId). Only ever written for the single-user host lane.
71
- // BOUNDED (avoid unbounded growth over the persistent local engine's lifetime): an LRU cap; the shell
72
- // re-sends cwd on every request, so evicting a stale session is harmless (it re-registers on next use).
73
34
  const MAX_CWD_SESSIONS = 4096;
74
35
  const perSessionCwd = new Map();
75
36
  const setSessionCwd = boundedSessionSetter(perSessionCwd, MAX_CWD_SESSIONS);
76
- // [R-survey / TOC shellEnv seam, core PLAN批注] per-session `settings.env` → the host lane's agent shell env
77
- // (resolveSpec registers it gated by cwdHonored — single-user host lane only; the host factory merges it by
78
- // ctx.sessionId). design/107 "env = capability axis". Same LRU bound + re-send-on-every-request semantics as cwd.
79
37
  const perSessionShellEnv = new Map();
80
38
  const setSessionShellEnv = boundedSessionSetter(perSessionShellEnv, MAX_CWD_SESSIONS);
81
39
  let executionEnvFactory;
82
- // SVC-3 worktree isolation: the reaper (defined far below) reuses ONE long-lived git base env + repoRoot to
83
- // `git worktree prune` crash-orphaned worktrees. Holders are populated when the wrapper is wired (host lane).
84
40
  let worktreeReap;
85
41
  if (config.remoteExec?.provider === "e2b") {
86
42
  executionEnvFactory = e2bExecutionEnvFactory({
@@ -89,11 +45,7 @@ export function createExecutionEnv(ctx) {
89
45
  ...(config.remoteExec.timeoutMs != null ? { timeoutMs: config.remoteExec.timeoutMs } : {}),
90
46
  ...(config.remoteExec.livenessMs != null ? { livenessMs: config.remoteExec.livenessMs } : {}),
91
47
  ...(config.remoteExec.allowInternetAccess != null ? { allowInternetAccess: config.remoteExec.allowInternetAccess } : {}),
92
- // [1452]/[1454] CWD-A: workspace 根透传(E2B_MOUNT_PATH)——缺省仍 /home/user(adapter 默认)。
93
48
  ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
94
- // RFC B5: region package-source env set rides the operator-trusted sandboxEnv — ENV-FIRST, the derived
95
- // ecosystem overrides (pip/uv/npm/go/rustup/flutter/…) beat the image's baked CN ENV on existing images,
96
- // no rebake needed. Explicit E2B_SANDBOX_ENV keys win over derived on collision (deliberate override).
97
49
  ...(() => {
98
50
  const merged = { ...derivePkgSourceEnv(config.sandboxPkgSource, customPkgSourceFromEnv()), ...(config.remoteExec.sandboxEnv ?? {}) };
99
51
  return Object.keys(merged).length > 0 ? { sandboxEnv: merged } : {};
@@ -113,26 +65,14 @@ export function createExecutionEnv(ctx) {
113
65
  ...(config.remoteExec.runtimeClass != null ? { runtimeClass: config.remoteExec.runtimeClass } : {}),
114
66
  ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
115
67
  ...(config.remoteExec.timeoutMs != null ? { timeoutMs: config.remoteExec.timeoutMs } : {}),
116
- // Resource-profile knobs. config.ts reads K8S_MEMORY/K8S_CPU; this passthrough was
117
- // missing, so the adapter silently stayed on its 2Gi default (Kata VM = default_memory 2G + limit 2G = 4G
118
- // MemTotal — task02b run9/10 integration gradle OOM'd there even after the env was set).
119
68
  ...(config.remoteExec.memory ? { memory: config.remoteExec.memory } : {}),
120
69
  ...(config.remoteExec.cpu ? { cpu: config.remoteExec.cpu } : {}),
121
70
  ...(config.remoteExec.s3Snapshot ? { s3Snapshot: config.remoteExec.s3Snapshot } : {}),
122
- // RFC B5: region package-source env set → pod container env (operator-trusted, never task-controlled).
123
- // ENV-FIRST: pod env beats image ENV and reaches every exec — the derived ecosystem overrides switch
124
- // region on existing images; the SEMA_PKG_SOURCE marker drives the in-image hook (file-bound pieces).
125
71
  ...(() => {
126
72
  const derived = derivePkgSourceEnv(config.sandboxPkgSource, customPkgSourceFromEnv());
127
73
  return Object.keys(derived).length > 0 ? { podEnv: derived } : {};
128
74
  })(),
129
75
  };
130
- // §7 P0.5: per-pod image override. resolveSpec resolved the requested profile→digest (with the caller's
131
- // principal, fail-closed) and registered it by sessionId — the ONLY identifier stable across the factory ctx
132
- // on every path (adversarial-review round-2: /v1/runs mints its own durable taskId that CLOBBERS spec.taskId,
133
- // so taskId-keying misses on the primary path; sessionId survives). get() is non-removing (the factory may be
134
- // invoked >once per logical task). Absent ⇒ the worker-global k8sCfg.image. Building the factory per task is a
135
- // cheap closure alloc; the ref is immutable (repo@digest). podSpecPatch can NOT carry the image (hard invariant).
136
76
  executionEnvFactory = (ctx) => k8sExecutionEnvFactory({ ...k8sCfg, image: perTaskImage.get(ctx.sessionId) ?? k8sCfg.image })(ctx);
137
77
  }
138
78
  else if (config.remoteExec?.provider === "ssh") {
@@ -152,22 +92,10 @@ export function createExecutionEnv(ctx) {
152
92
  });
153
93
  }
154
94
  else if (config.remoteExec?.provider === "host") {
155
- // DUAL-MODE §5: the TOC `host` lane — run on THIS machine, no container (isolation=none). Fan-out still
156
- // works (bounded by one box). Secrets are env-NAMEs the host resolves from its own process.env.
157
95
  {
158
- // R7 self-wake: inject the self-wake SchedulerCapability backend on the single-user TOC host lane
159
- // (opt-in SCHEDULER_ENABLED). `hasScheduler(env)` then mounts CronCreate/CronDelete/CronList(旧名 CronCancel、Sleep 已 design/136 撤除——L8 注释订正,且挂载在 core prepare-task 非 createHandsToolkit); the TOC shell
160
- // daemon reads the SAME ~/.sema/scheduled_tasks.json store (registry-core /node binding) and fires due intents.
161
- // Multi-tenant (requirePrincipal) routes scheduling to center, NOT this host daemon → gate it off there. ONE
162
- // backend instance at boot → every host task shares the same store file.
163
96
  const hostScheduler = config.schedulerEnabled && config.requirePrincipal !== true
164
97
  ? new FileSchedulerBackend({
165
98
  ...(config.schedulerStorePath ? { storePath: config.schedulerStorePath } : {}),
166
- // [1009]② host-signal: the spawning shell knows whether a resident daemon will honor session
167
- // wakeups — SCHEDULER_SESSION_WAKEUP=false flips the capability off at CONSTRUCTION (instance-
168
- // lifetime snapshot, core TOCTOU 契约) so core's ScheduleWakeup refuses up front.
169
- // [2436] session-lifetime 同族 host-signal(5.6.0):headless(无 daemon 收割)⇒
170
- // supportsSessionLifetime:false ⇒ CronCreate 前门拒收 + backend 第二道拒落盘,孤儿不产生。
171
99
  ...(config.schedulerSessionWakeup === false || config.schedulerSessionLifetime === false
172
100
  ? {
173
101
  caps: {
@@ -184,37 +112,21 @@ export function createExecutionEnv(ctx) {
184
112
  ...(config.remoteExec.workspaceBase ? { workspaceBase: config.remoteExec.workspaceBase } : {}),
185
113
  ...(config.remoteExec.commandTimeoutMs != null ? { commandTimeoutMs: config.remoteExec.commandTimeoutMs } : {}),
186
114
  ...(hostScheduler ? { scheduler: hostScheduler } : {}),
187
- // design/103 background shell: single-user host lane only (parity with scheduler/cwd). Multi-tenant host
188
- // lane (which shouldn't exist — host runs on the worker's own box) → INERT (run_in_background/etc. don't mount).
189
115
  backgroundShell: config.requirePrincipal !== true,
190
116
  };
191
- // [#40 / TOC cwd seam] per-request workspace: if resolveSpec registered a caller `cwd` for this session (gated by
192
- // cwdHonored — single-user host lane only), run the agent VERBATIM in that dir (persistent, never deleted — the
193
- // user's project). Else the boot-time host config (ephemeral random subdir). Mirrors the k8s perTaskImage wrap.
194
- // core 1.219 `ctx.parentCwd` (dogfood finding: sub-agents landed in an EMPTY sandbox): a DELEGATED child
195
- // (workflow ctx.agent / Task tool) now roots at its PARENT's working dir — CC parity — via core's TRUSTED
196
- // RunInternals channel. Precedence in effectiveHostWorkspace: session cwd → parentCwd (unless the child asked
197
- // for isolation:"worktree" — that wrapper wins below) → ephemeral. Container lanes never consult this.
198
117
  executionEnvFactory = (ctx) => {
199
118
  const cwd = effectiveHostWorkspace(perSessionCwd.get(ctx.sessionId), ctx);
200
119
  const shellEnv = perSessionShellEnv.get(ctx.sessionId);
201
120
  const env = hostExecutionEnvFactory({
202
121
  ...hostCfg,
203
122
  ...(cwd ? { workspaceDir: cwd } : {}),
204
- ...(shellEnv ? { env: shellEnv } : {}), // R-survey: per-task shell env (merged UNDER per-command options.env by the host adapter)
123
+ ...(shellEnv ? { env: shellEnv } : {}),
205
124
  })(ctx);
206
- // core 1.191: an EPHEMERAL host workspace (no honored `cwd` → a per-task `sema-host-<id>` dir destroy()
207
- // rm's) evicts its LSP servers at task-end so a busy server doesn't accumulate one heavy language server per
208
- // task. cwdHonored (persistent project) is NOT evicted — its stable root stays warm across turns (CC-parity);
209
- // worktree/fan-out is bounded by maxSessions=16. Only wraps when a NodeLspManager is actually active.
210
125
  return lspManager && !cwd ? evictLspOnDestroy(env, lspManager) : env;
211
126
  };
212
127
  }
213
128
  }
214
129
  else if (config.remoteExec?.provider === "local-docker") {
215
- // DUAL-MODE §5: the TOC `local-docker` lane — a per-task container on THIS machine's docker daemon
216
- // (isolation:true, suspendable:false). The base image comes ENTIRELY from config (no docker.io default —
217
- // domestic-images iron rule). Secrets are resolved env-NAME→value here (config.ts already did the forward).
218
130
  executionEnvFactory = localDockerExecutionEnvFactory({
219
131
  image: config.remoteExec.image,
220
132
  ...(config.remoteExec.mountPath ? { mountPath: config.remoteExec.mountPath } : {}),
@@ -222,10 +134,6 @@ export function createExecutionEnv(ctx) {
222
134
  ...(config.remoteExec.dockerHost ? { dockerHost: config.remoteExec.dockerHost } : {}),
223
135
  ...(config.remoteExec.memory ? { memory: config.remoteExec.memory } : {}),
224
136
  ...(config.remoteExec.cpus != null ? { cpus: config.remoteExec.cpus } : {}),
225
- // A-002.7: the two sandbox-hardening knobs were parsed by loadConfig and honored by the adapter, but this
226
- // call site dropped them — so DOCKER_PIDS_LIMIT always resolved to the adapter's `?? 512` default and
227
- // DOCKER_DROP_CAPS never emitted `--cap-drop ALL`. Absent stays absent (the adapter's defaults are the
228
- // unconfigured shape); only a SET knob rides, same additive form as the siblings above.
229
137
  ...(config.remoteExec.pidsLimit != null ? { pidsLimit: config.remoteExec.pidsLimit } : {}),
230
138
  ...(config.remoteExec.dropAllCaps ? { dropAllCaps: true } : {}),
231
139
  ...(config.remoteExec.network ? { network: config.remoteExec.network } : {}),
@@ -233,24 +141,10 @@ export function createExecutionEnv(ctx) {
233
141
  ...(config.remoteExec.env ? { env: config.remoteExec.env } : {}),
234
142
  });
235
143
  }
236
- // [848] remote scratchpad: decorate the REMOTE sandbox lanes (e2b/k8s/local-docker/ssh) so each env
237
- // lazily `mkdir -p`s `/tmp/scratchpad/<sessionId>` on its FIRST exec — never at factory time (e2b is
238
- // lazy-VM; a factory-time exec would force-boot the VM). adb is skipped (no standard /tmp on Android);
239
- // host keeps its worker-local scratchpad (ensureScratchpadDir at the envFacts consumer). The envFacts
240
- // advertisement below uses the SAME remoteScratchpadDirFor rule, so fact and mkdir can never disagree.
241
144
  if (executionEnvFactory && isRemoteScratchpadLane(config.remoteExec?.provider)) {
242
145
  executionEnvFactory = withRemoteScratchpad(executionEnvFactory, logger);
243
146
  }
244
- // SVC-3 (design/97 CORE-6): wrap the factory with git-worktree isolation. When core marks an agent
245
- // `isolation:"worktree"` (TRUSTED RunInternals — never a TaskSpec), the wrapper mints a per-agent detached
246
- // worktree under the operator-trusted repoRoot; otherwise it passes the base env through untouched. v1
247
- // covers the `host` lane (the TOC fan-out lane). Other lanes would need a worktree-ROOTED REMOTE env from
248
- // `rootEnvAt` (a follow-on; a host/NodeExecutionEnv worktree is not durable-suspendable — see the plugin
249
- // header) — they are left unwrapped here, isolation:"worktree" is then a no-op for them (the base env wins).
250
147
  if (config.worktreeIsolation && executionEnvFactory) {
251
- // M4 (adversarial-review): a mis-wired WORKTREE_REPO_ROOT must fail LOUD at boot, not per-task at runtime. The
252
- // root must exist AND be a git repo (`.git` dir or worktree-link file). On a bad root, log an error + LEAVE
253
- // isolation OFF (the factory unwrapped) rather than wiring a factory that throws on every isolated agent.
254
148
  const wtRoot = config.worktreeIsolation.repoRoot;
255
149
  const wtValid = existsSync(wtRoot) && existsSync(join(wtRoot, ".git"));
256
150
  if (config.remoteExec?.provider === "host" && !wtValid) {
@@ -261,35 +155,18 @@ export function createExecutionEnv(ctx) {
261
155
  });
262
156
  }
263
157
  else if (config.remoteExec?.provider === "host") {
264
- // The SHARED base env that runs `git worktree add/remove/prune` (cwd:repoRoot is passed explicitly by
265
- // core on every git call, so this env's own root is irrelevant — it just needs `exec` + git on PATH).
266
- // ONE long-lived env per deployment; the reaper reuses it. `inheritEnv:"all"` so the operator's git
267
- // config/credentials are visible (this is the operator's own machine + own repo, the TOC posture).
268
- // systematic-audit (gate-fail-direction): every worktree-lane RemoteHostExecutionEnv carries the SAME
269
- // background-shell gate as the primary host factory (main.ts ~442) — not the env's `?? true` default. The
270
- // git-base env is git-ops only (agents run on rootEnvAt below), but gating it keeps the policy uniform / future-proof.
271
158
  const gitBaseEnv = new RemoteHostExecutionEnv({ workspaceDir: config.worktreeIsolation.repoRoot, inheritEnv: "all", backgroundShell: config.requirePrincipal !== true });
272
159
  executionEnvFactory = withWorktreeIsolation(executionEnvFactory, {
273
160
  repoRoot: config.worktreeIsolation.repoRoot,
274
161
  ...(config.worktreeIsolation.allowedRoots ? { allowedRoots: config.worktreeIsolation.allowedRoots } : {}),
275
162
  ...(config.worktreeIsolation.commit ? { commit: config.worktreeIsolation.commit } : {}),
276
163
  baseEnvForGit: gitBaseEnv,
277
- // Host-lane worktree-rooted env: a host adapter in PERSISTENT-DIR mode — its own destroy() does NOT
278
- // rm the dir (so core's `git worktree remove` owns teardown), and it keeps the host adapter's secret
279
- // scrub + timeout semantics (vs a bare NodeExecutionEnv). inheritEnv defaults to "scrub" (model-driven).
280
164
  rootEnvAt: (dir) => {
281
165
  const env = new RemoteHostExecutionEnv({ workspaceDir: dir, backgroundShell: config.requirePrincipal !== true });
282
- // A worktree root is per-agent + ephemeral (core's `git worktree remove` reclaims it at
283
- // task-end), so evict its LSP servers DETERMINISTICALLY too — not just via the maxSessions LRU backstop. The
284
- // worktree dir IS the LSP cache root (env.cwd), so evict(env.cwd) on destroy hits it. (`lspManager` is declared
285
- // below but this closure only runs per-task, well after it's initialized — same forward-ref as the host factory.)
286
166
  return lspManager ? evictLspOnDestroy(env, lspManager) : env;
287
167
  },
288
168
  logger,
289
169
  });
290
- // 复审 2026-07-30 F3:in-flight 单飞语义内建在铸造处——本函数有**两个**调用点(下方 boot recovery
291
- // sweep 与 reapers.ts 的周期 tick),守卫只装 tick 侧会漏 boot 长跑(>REAP_INTERVAL_SEC 的
292
- // `git worktree prune` 与首个 tick 叠跑,正是守卫要防的并发)。装在源头,调用点全免疫。
293
170
  {
294
171
  let inFlight = false;
295
172
  worktreeReap = () => {
@@ -300,28 +177,15 @@ export function createExecutionEnv(ctx) {
300
177
  };
301
178
  }
302
179
  logger.info("worktree_isolation_enabled", { provider: "host", repoRoot: config.worktreeIsolation.repoRoot });
303
- // BOOT recovery sweep: reap any worktrees orphaned by a crash BEFORE this process started (the periodic
304
- // reaper would otherwise wait a full interval). Best-effort, fire-and-forget; never blocks boot.
305
180
  void worktreeReap().catch(() => undefined);
306
181
  }
307
182
  else {
308
- // Not a no-op-silent: an operator who set WORKTREE_ISOLATION_ENABLED on a non-host lane should know it
309
- // doesn't take effect yet (the worktree-rooted REMOTE env is a follow-on). Fail-loud-ish via a warning.
310
183
  logger.warn("worktree_isolation_unsupported_lane", {
311
184
  provider: config.remoteExec?.provider,
312
185
  note: "git-worktree isolation v1 covers only the 'host' lane — isolation:'worktree' is a no-op here (the base env is used). Worktree-rooted remote envs are a follow-on.",
313
186
  });
314
187
  }
315
188
  }
316
- // SendUserFile v2(clay 拍 2026-07-14,Plan B 直传):沙箱 lane 的文件源需要「工具执行时拿到当前
317
- // 任务的 env」——core 把 per-task env 关在 prepare-task 闭包里(ToolExecuteContext 无 env 面),但
318
- // 工厂是我们装配的、工厂 ctx 自带 taskId,包一层登记 taskId→env(destroy 时注销)即可,零 core 改动
319
- // (withWorktreeIsolation 同款包装先例)。lane 门+租户门=sandboxSendLaneEnabled(判定依据在其 doc):
320
- // e2b/k8s=一任务一沙箱(沙箱文件系统=租户边界)任意租户可用;ssh(后补 2026-07-14)=同一 exec 契约
321
- // (timeout 秒×1000/Result shape 同形)直传链零适配,但 peer=一台跨任务共享的常驻真实主机
322
- // (sshExecutionEnvFactory 忽略 ctx、destroy=断连不删文件、isolation:false)→「沙箱 scoped 读=租户
323
- // 隔离」不成立,与 host 同门单用户 only(requirePrincipal!==true 恒关多租户,不留口子)。
324
- // 其余 lane 不包(零开销);签发面未配同样不包。
325
189
  const sendUserFileTaskEnvs = (() => {
326
190
  const provider = config.remoteExec?.provider;
327
191
  if (!config.sendUserFile || !executionEnvFactory || !sandboxSendLaneEnabled(provider, config.requirePrincipal))
@@ -330,11 +194,6 @@ export function createExecutionEnv(ctx) {
330
194
  executionEnvFactory = registry.wrapFactory(executionEnvFactory);
331
195
  return registry;
332
196
  })();
333
- // D-1 附件物化包装(半场③):env 每次建立(fresh + resume/重建沙箱)都把该 session 绑定的附件写进
334
- // 工作目录 `attachments/`。全 lane 统一(host/e2b/k8s/ssh/adb 都走 factory;ExecutionEnv.writeFile
335
- // 双向通用);路径与 objective 告知共用 materializedRelPaths(确定性,两处不漂)。**fail-loud**:
336
- // objective 已向模型宣告文件在场,静默缺文件=模型按幻影文件行动,比任务失败更糟 ⇒ 写失败即抛
337
- // (env 建立失败,任务带明确错误)。链尾最外层:worktree 隔离等内层先定 cwd,附件落在最终 cwd。
338
197
  if (taskAttachmentStore && executionEnvFactory) {
339
198
  const inner = executionEnvFactory;
340
199
  const attStore = taskAttachmentStore;
@@ -346,54 +205,29 @@ export function createExecutionEnv(ctx) {
346
205
  return env;
347
206
  };
348
207
  }
349
- // #165 沙箱写门的真 env slot(**最外层**,见文件头的装饰顺序):写门在 spec 期只能拿到一个代理,
350
- // 裁决时刻由它去 slot 里取本 task 的真 env 转发 fs 读原语。所以登记的必须是 core 最终拿到手的那一个
351
- // ——内层装饰器会换实例(worktree 的 rootEnvAt)或改写方法(scratchpad 的 exec/canonicalPath 前置)。
352
- // 沙箱 lane 限定:host-semantics lane 的写门直接拿本机 NodeExecutionEnv,不经代理,包了也无人读。
353
208
  if (executionEnvFactory && isSandboxPathAdjudicationLane(config.remoteExec?.provider)) {
354
209
  executionEnvFactory = withSandboxPathEnvSlot(executionEnvFactory, sandboxPathEnvSlots, logger);
355
210
  }
356
211
  if (executionEnvFactory) {
357
- // k8s (Kata) is isolated; it becomes WORKSPACE-suspendable when an S3 snapshot store is configured
358
- // (tar→S3→fresh pod restore — files durable, in-VM memory not); e2b is fully suspendable.
359
212
  const isolated = config.remoteExec.provider === "e2b" ||
360
213
  config.remoteExec.provider === "k8s" ||
361
- config.remoteExec.provider === "local-docker"; // a container is a real OS-level isolation boundary
214
+ config.remoteExec.provider === "local-docker";
362
215
  const suspendable = config.remoteExec.provider === "e2b" ||
363
216
  (config.remoteExec.provider === "k8s" && !!config.remoteExec.s3Snapshot);
364
- // Out-of-band sandbox env (e2b): log the KEY NAMES only (never the secret values) so ops can confirm
365
- // injection is active without leaking the credential.
366
217
  const sandboxEnvKeys = config.remoteExec.provider === "e2b" ? Object.keys(config.remoteExec.sandboxEnv ?? {}) : [];
367
218
  logger.info("remote_exec_enabled", { provider: config.remoteExec.provider, isolated, suspendable, ...(sandboxEnvKeys.length > 0 ? { sandboxEnvKeys } : {}) });
368
219
  if (!isolated)
369
220
  logger.warn("remote_exec_real_system", { provider: config.remoteExec.provider, note: "non-isolated target — actions are permanent; rely on the policy gate + HITL (design/61 §5)" });
370
221
  }
371
- // LSP sidecar (design/64 §13.1, the 1.86.2 seam): ONE stateless manager on RunnerDeps — core passes each
372
- // task's env into sessionFor at the tool mount point, so the manager reaches the SAME sandbox the agent edits
373
- // (no sessionId registry / per-task construction). Opt-in (LSP_ENABLED — the SANDBOX lane's knob; the host lane
374
- // below has its own, LSP_HOST_ENABLED, because its default is the opposite); with the baked `ai-agent-code-lsp`
375
- // template the first call is fast, without it the language server installs on first use (slow once per sandbox).
376
- // k8s lane (gate#2 alignment): same manager/bridge over the pod network — `ws://podIP:port` instead of E2B's
377
- // public wss proxy, so it needs an IN-CLUSTER worker and a sandbox image with node + the language servers baked
378
- // (no on-the-fly npm path on Kata; a miss degrades the lsp tool gracefully, same as E2B).
379
222
  const lspProvider = config.remoteExec?.provider;
380
223
  const lspManager = (lspProvider === "e2b" || lspProvider === "k8s") && config.lspEnabled
381
224
  ? createE2bLspManager({
382
225
  log: (event, fields) => logger.info(event, fields),
383
226
  scheme: lspProvider === "k8s" ? "ws" : "wss",
384
- // §DESIGN-V2 F11: pass the lane explicitly — the bridge metrics' `provider` label names the
385
- // DEPLOYMENT lane, not the WS scheme (the two happen to correlate today but are different axes).
386
227
  provider: lspProvider,
387
228
  metrics,
388
229
  })
389
- : // TOC local LSP (core 1.190): the host lane runs on THIS machine, so core's `NodeLspManager` spawns the
390
- // language server as a LOCAL child_process over stdio (CC `services/lsp` parity). Its default resolveRoot uses
391
- // `env.cwd` — and core passes each task's executionEnv (the per-agent WORKTREE env, so the server roots in the
392
- // worktree not the base repo) into sessionFor. host ONLY: ssh/adb target another host/device and local-docker a
393
- // container, none of which a local child_process can reach (those keep the E2B-style bridge / stay degraded).
394
- // 🔒 `requirePrincipal !== true` gate = uniform with the sibling host powers (scheduler/backgroundShell/
395
- // loadProjectMemory): the host lane is single-user by design; a (discouraged) multi-tenant host fails SAFE to
396
- // grep/read rather than fanning a per-tenant×language×root pool of heavy language servers (double-review B).
230
+ :
397
231
  lspProvider === "host" && config.lspHostEnabled && config.requirePrincipal !== true
398
232
  ? new NodeLspManager({ log: (event, fields) => logger.info(event, fields) })
399
233
  : undefined;
@@ -1,59 +1,4 @@
1
- /**
2
- * design/170 件B/C/D(#252 件1)—— 三个**部署治理座席**的装配点:把 core 5.13.0 起就存在、本仓一直
3
- * 零接线的三个 `RunnerDeps` seam 接上。
4
- *
5
- * · 件B `compliancePostureResolver` —— per-principal 合规档位否决(闭集 profile × 闭集 capability;
6
- * 生效 deny 集 = `BUILTIN_COMPLIANCE_DENIES[profile] ∪ additionalDenies`,**供数只能收紧**)。
7
- * · 件C `lockedConfig` —— 管理员锁定层的**声明**(闭集注册表;spec 占了锁着的字段 ⇒ core 整拒 prepare)。
8
- * · 件D `retentionPolicy` —— 托管留存期。core **只**拿它做启动期能力校验,引擎在任务路径上从不删数据。
9
- *
10
- * ## 真源:为什么三件都只接 env
11
- *
12
- * design/170 §1 的三层分工表里,center 那一列对 B/C/D 写的都是「**新建**」——中心侧的档位真源 / 策略
13
- * 真源 / 保留期真源**至今不存在**。接一条 center 腿等于造一个恒缺席的假面(件A 的 org 目录能接 center,
14
- * 是因为那一面真被建出来了)。所以本装配点的供数只有部署配置面,并且这**恰好**是 design/170 §4.3 对
15
- * 「谁能解锁」的裁定:锁是**配置期物**,铸锁真源只有 center governance 域与部署 env,二者都是 operator
16
- * 控制的配置面,自带各自既有鉴权 —— 「谁能解锁」≡「谁能改部署配置」,不引入新鉴权面。
17
- *
18
- * ## 件B 的窄化:档位是**部署级**,不是 per-principal
19
- *
20
- * core 的座席签名按 principal 取值,本仓的 resolver 对每个 principal 返回同一份部署档位。这是诚实的窄
21
- * 实现:per-principal 档位需要 center 的下发面(§3.2 R5「与 caps 同一次 fetch」),那一面尚未建。
22
- * 座席在场即恒生效,不存在「某些 principal 悄悄没档位」的分岔。
23
- *
24
- * ## 件B 的**不采纳**:多租户缺解析器不拒启(显式定界)
25
- *
26
- * §3.4 建议「部署自称多租户却未配 B 的解析器 ⇒ 拒启动」。**本仓不采纳**,理由是三问里的第二问:
27
- * 谁被伤到 —— 今天每一个 `REQUIRE_PRINCIPAL=true` 的部署都没有这一格,采纳即是让它们**全部**在下一次
28
- * 升级时起不来,而它们并没有任何东西变得更不安全(B 缺席 = 无合规限制 = 与今天逐字相同的行为)。
29
- * 补偿是这条 env 旋钮本身:要档位的部署显式写下它。将来 center 真有了档位下发面,「多租户必须能解析出
30
- * 档位」才是一条有对象可指的判据,那时再立。
31
- *
32
- * ## 件C 的两条**装配相容性**判据(本仓特有,见 {@link assertLockAssemblable})
33
- *
34
- * core 的锁按「spec 是否占了这个字段」判,而本仓是**部署自己**在铸那两个 spec 字段:
35
- * · `spec.toolPolicy` —— 治理拍(`applyRuntimeGovernance`)在**任何**客户端表态下都会铸出它(敏感路径
36
- * 守卫集默认非空);于是 `toolPolicy` 锁会让 core 的 preflight **整拒每一条腿**。
37
- * · `spec.mcp` —— 部署自带的 MCP 基线(场景/center 下发)同样经 `spec.mcp` 进 core;部署配了 MCP 又锁
38
- * `mcp`,同样是每条腿必拒。
39
- * 两者都在**启动期**拒(安全控件不得半开 §3.4 C 行),而不是让运维在每条任务腿上各撞一次
40
- * `config.locked_key` 去猜。
41
- *
42
- * ## 件D 的边界(成文,别读成「数据会被删」)
43
- *
44
- * 本仓**没有任何** store 声明 `retention:"managed"`(亲验:session/checkpoint/tool-result 三族 SQL 双生
45
- * 与 local 形都没有这一位),托管留存的执行面(调度/重试/副本协调/逐行审计)按 §5.2 是「server 半场四件
46
- * 皆新建」,不在本批。所以本装配点对件D 只做两件事:①把策略递给 core 的启动期校验
47
- * (`assertRetentionCapability` —— 锁着 + 店不能删 ⇒ 拒启,「策略锁着、数据永存」不许发生);②策略在场
48
- * 而没有任何 managed 店时打一条响亮 warn。**不**假装有执行面。
49
- */
50
1
  import { assertRetentionCapability, resolveComplianceDenies, resolveLockedKeys } from "@sema-agent/core";
51
- /**
52
- * 件C 的装配相容性判据(纯,可单测)——见文件头「两条判据」。
53
- *
54
- * 🔴 失败方向朝**拒启**:一把在本部署上必然把每条腿都拒掉的锁,不是「配得比较严」,是配错了;继续启动
55
- * 只会让服务对每一个请求说 `config.locked_key`,而运维手里没有任何线索指向那把锁。
56
- */
57
2
  export function assertLockAssemblable(input) {
58
3
  if (input.lockedKeys.has("toolPolicy")) {
59
4
  throw new Error('LOCKED_CONFIG_KEYS contains "toolPolicy", which this server cannot arm: it COMPOSES the effective tool policy itself on ' +
@@ -69,20 +14,6 @@ export function assertLockAssemblable(input) {
69
14
  "MCP servers — a locked deployment mounts no MCP at all.");
70
15
  }
71
16
  }
72
- /**
73
- * 治理面的**装配相容性**总判据(codex 对抗复审 R2-F2 起扩面)——锁与合规档位在这条轴上是同一件事:
74
- * 两者都可能把「本部署自己的装配」判成 core 门口的违规,而那不是「配得更严」,是**这台从此每条腿必失败**。
75
- *
76
- * 合规半场的判据来自亲核 core 的 prepare 门(`prepare-task` 的 compliance 段):被否决的能力若**已在
77
- * spec 上**,core 是**整拒 prepare**(`config.compliance_denied`),不是「不挂载」。于是:
78
- * · `mcp_servers` 被禁 ∧ 部署自带 MCP 基线 ⇒ 每条腿的 `spec.mcp` 都在 ⇒ 必拒;
79
- * · `web_fetch` 被禁 ∧ 花名册挂了 WebFetch ⇒ 必拒。⚠️ `hipaa` 的**内建 floor 就含 `web_fetch`**,而本仓
80
- * 的单用户花名册**恒挂** WebFetch(`capabilities/scenarios.ts` 的 roster 过滤只在多租户下摘它)——
81
- * 也就是说「单用户 + hipaa」在本仓是一个每条腿必失败的组合。这条判据的全部价值就在这儿:让它在
82
- * 启动期说出来,而不是让运维看着一台"启动成功、任务全失败"的 worker 猜。
83
- * · `workflows` 被禁只在 `spec.selfOrchestration === true` 的腿上拒(per-task,不是部署恒真)⇒ 不在本
84
- * 判据内,由那条腿自己响亮拒;`org_memory_mount` 走件A 的准入面。**成文定界,不留白。**
85
- */
86
17
  export function assertGovernanceAssemblable(input) {
87
18
  assertLockAssemblable({ lockedKeys: input.lockedKeys, deploymentMcpConfigured: input.deploymentMcpConfigured });
88
19
  if (input.complianceDenies.has("mcp_servers") && input.deploymentMcpConfigured) {
@@ -97,25 +28,11 @@ export function assertGovernanceAssemblable(input) {
97
28
  "currently honor a web_fetch denial. Run the posture multi-tenant, or drop the profile/deny.");
98
29
  }
99
30
  }
100
- /**
101
- * 装配三座席。**拒启口即装配口**:相容性判据与 core 的留存能力校验都在这里跑,调用方不必记得再调一次
102
- * (件A 的 C12 探测同姿势)。
103
- */
104
31
  export function createGovernanceSeams(ctx) {
105
32
  const { config, logger, retentionStores } = ctx;
106
- // 件C:锁集。config 层已用同一只 `resolveLockedKeys` 验过一遍;这里再验是因为 config 也可能由
107
- // sema-registry 侧写入(env 不是唯一写点),而「安全控件不得半开」这条对每个写点都成立。
108
33
  const lockedKeys = resolveLockedKeys(config.lockedConfigKeys !== undefined ? { keys: config.lockedConfigKeys } : undefined);
109
- // 件B:合规档位。`resolveComplianceDenies` 在装配期跑一次 —— 它是 core 的 fail-loud 解析口,同时给出
110
- // 这台部署真正在禁哪些能力(启动行的内容 + 下面那道相容性判据的输入)。resolver 本身返回**冻结的部署
111
- // 档位**,不做任何 per-call 工作。
112
34
  const posture = config.compliancePosture;
113
35
  const complianceDenies = posture !== undefined ? resolveComplianceDenies(posture) : new Set();
114
- // `mcpServers` 读的是**已应用 center 配置之后**的值(本装配点排在 config-center 段之后)。中心侧后来
115
- // 加了 MCP 服务器怎么办:那一族是 baked-at-boot 面,变更会翻 `restartRequired` 让编排器滚动重启 ——
116
- // 重启就撞上这道门。所以这里不需要第二条热路径判据。
117
- // `webFetchMounted` 的判据与花名册那处的过滤**同源**(`capabilities/scenarios.ts`:
118
- // `.filter((t) => t.name !== "WebFetch" || deps.requirePrincipal !== true)`)—— 多租户摘、单用户挂。
119
36
  assertGovernanceAssemblable({
120
37
  lockedKeys,
121
38
  complianceDenies,
@@ -130,13 +47,10 @@ export function createGovernanceSeams(ctx) {
130
47
  logger.info("compliance_posture_enabled", { profile: posture.profile, denies: [...complianceDenies].sort() });
131
48
  compliancePostureResolver = () => posture;
132
49
  }
133
- // 件D:留存期。先跑 core 的启动期校验(策略值本身 + 锁着时的店能力),再补一条本仓特有的诚实 warn。
134
50
  const retentionPolicy = config.retentionPolicy;
135
51
  const locked = lockedKeys.has("retentionPolicy");
136
52
  assertRetentionCapability({ policy: retentionPolicy, locked, stores: retentionStores });
137
53
  if (retentionPolicy !== undefined) {
138
- // 声明位的读法与 core 的校验口**同一个结构型**(`RetentionDeclaring`)——不自造一个平行的形状,
139
- // 否则「core 认为这店 managed / 我方认为不是」这种分歧会静默存在。
140
54
  const managed = retentionStores.filter((s) => s.store?.retention === "managed");
141
55
  const executorWired = ctx.retentionExecutorWired === true;
142
56
  if (managed.length === 0) {
@@ -148,12 +62,6 @@ export function createGovernanceSeams(ctx) {
148
62
  });
149
63
  }
150
64
  else if (!executorWired) {
151
- // 🔴 #270 车1(codex R2-[critical] 验真后修):**店说 managed ≠ 有人在删**。
152
- // · locked ⇒ **拒启**:core 的门因为声明位而放行了,可这台机上没有执行器 —— 那正是它文案里
153
- // 「policy locked, data immortal」的实况,只是伪装成了合规。拒启保住的是车1 之前的**同一个**
154
- // 行为(那时店声明 none ⇒ core 直接拒),不是新增的严格。
155
- // · 未锁 ⇒ **响亮 warn**(不拒启):未锁的策略本就是一句声明,但它此前那条 warn 会被「有 managed
156
- // 店在场」静音,于是最需要说话的那一刻反而没声音。
157
65
  const detail = `retention policy is configured (maxAgeDays=${retentionPolicy.maxAgeDays}) and ${managed.length} wired store(s) declare ` +
158
66
  `retention:"managed", but THIS BUILD WIRES NO RETENTION EXECUTOR (the sweep lane) — nothing enumerates domains or calls ` +
159
67
  `the destructive store methods, so no data is deleted on the policy's account. A "managed" declaration is a statement ` +
@@ -1,15 +1,5 @@
1
1
  import { createLeaderEndpoint } from "../leader/endpoint.js";
2
2
  import { createLeaderRunner } from "../leader/wire.js";
3
- /**
4
- * 鲁棒性批5 A6(2026-08-05):k8s 腿要求 MinIO 三件套(ENDPOINT/ACCESS_KEY/SECRET_KEY)全在——半开(一件缺)
5
- * 就让 `leaderEndpoint` 的三元式落到 undefined,此前**零披露**:运维显式开了 LEADER_ENABLED +
6
- * REMOTE_EXEC=k8s,却在日志里既看不到 `leader_endpoint_enabled` 也看不到任何"为什么没启用"的信号——只能
7
- * 靠读源码才知道要去检查 MinIO 三件套。
8
- *
9
- * 纯谓词,抽出独立函数:只吃 leaderEnabled/leaderProvider/hasS3 三个原语 + 直接读 process.env 取缺失的
10
- * 具体变量名,不依赖完整 `LeaderCtx`(构造一整套 brain/pricing/sessionStore 只为测一个 warn 分支不值当)。
11
- * 返回 null = 不适用该警告(未开启/非 k8s 腿/三件套齐全);非 null = 该报警,携带具体缺的变量名列表。
12
- */
13
3
  export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
14
4
  if (!(leaderEnabled && leaderProvider === "k8s" && !hasS3))
15
5
  return null;
@@ -19,19 +9,6 @@ export function leaderK8sMinioGap(leaderEnabled, leaderProvider, hasS3) {
19
9
  !process.env.MINIO_SECRET_KEY && "MINIO_SECRET_KEY",
20
10
  ].filter((v) => typeof v === "string");
21
11
  }
22
- /**
23
- * #193 车4 件2 的**能力面**门(codex 对抗复审 R1-high①,验真后修)。
24
- *
25
- * config 层的门②判的是 `DB_BACKEND` 的字面值 —— 那句话回答的是「运维要没要 durable」。它回答不了
26
- * 「这次启动**真的拿到**了 durable 吗」:`openStoreBackendWithFallback` 在 `SESSION_BACKEND=auto`
27
- * (默认)下,SQL 连不上 / 建表失败 ⇒ 返回 `backend=undefined` + 一条 warn 就继续启动。那次启动里
28
- * `leaderRun()` 缺席,leader 端点会静默落进单副本内存臂:多副本各自受理同一个 Idempotency-Key、
29
- * 跨副本 GET 404、重启失忆 —— 而这条腿的产物是**真 git push**。
30
- *
31
- * ⇒ 判据锚**真拿到了没有**。纯谓词、只吃两个原语(与 {@link leaderK8sMinioGap} 同姿势),便于单测。
32
- * 抛而不是 warn:这正是 #157「安全/执行车道禁静默 fail-open」说的那一类 —— 降级的代价由用户承担,
33
- * 而承担的方式是重复的真实推送。
34
- */
35
12
  export function assertLeaderDurableStore(leaderEnabled, hasLeaderRunStore) {
36
13
  if (!leaderEnabled || hasLeaderRunStore)
37
14
  return;
@@ -44,11 +21,6 @@ export function assertLeaderDurableStore(leaderEnabled, hasLeaderRunStore) {
44
21
  }
45
22
  export function createLeaderFace(ctx) {
46
23
  const { config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams } = ctx;
47
- // v2 leader endpoint (design/50 + design/68): wire when LEADER_ENABLED + an isolated remote-exec backend
48
- // (E2B or k8s/Kata — SSH/ADB are single-worker real-system backends, not fan-out targets). Default off →
49
- // zero prod impact. 🔴 a real run also needs `git` on the host PATH + a durable remote with write creds.
50
- // F2(a) factory mode (preferred) = the service's executionEnvFactory + MinIO self-upload diff-out; the k8s
51
- // lane REQUIRES it (no static k8s mode). E2B without MinIO falls back to the static compat mode.
52
24
  const leaderMinio = process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
53
25
  ? {
54
26
  s3: {
@@ -61,73 +33,38 @@ export function createLeaderFace(ctx) {
61
33
  }
62
34
  : {};
63
35
  const leaderProvider = config.remoteExec?.provider;
64
- // #193 车4 件1:durable leader-run 登记表(SQL 后端专有;`backend` 本身可缺席=无 DB 的 env-only worker)。
65
- // 件2 能力面门(codex R1-high①)紧随其后:开了 leader 却没真拿到店 ⇒ 拒启,绝不静默落内存臂。
66
36
  const leaderRunStore = ctx.backend?.leaderRun?.();
67
37
  assertLeaderDurableStore(config.leaderEnabled, leaderRunStore !== undefined);
68
38
  const minioGap = leaderK8sMinioGap(config.leaderEnabled, leaderProvider, "s3" in leaderMinio);
69
39
  if (minioGap)
70
40
  logger.warn("leader_k8s_minio_incomplete", { missing: minioGap });
71
- // DUAL-MODE §5: orchestration is an ENGINE capability, not fleet-only — the TOC `host` lane runs leader fan-out
72
- // bounded by ONE box (`remote-env-host.ts` declares `capabilities = { isolation:false, suspendable:false }`).
73
- // e2b/k8s keep their isolated/suspendable posture.
74
- //
75
- // 🔴 #193 车4 件4(台账 P2-10)核实更正 —— 这里原先断言 host 腿「既不 durable 也没有快照,所以下面那个
76
- // durable 子 worker 挂起块会被跳过」。**没有任何判别式这样做**:下面的 `durable` 块只 gate 在
77
- // `checkpointStore && approvalRequire.length > 0` 上,与 provider 无关,host 腿照样拿到它。core 侧亲读
78
- // (5.35.0)也不是「跳过」而是**park-only 降级**:一个 `suspendable:false` 的 remote env 仍然 durable
79
- // suspend,被跳过的只有 `suspendVM` 那一步 —— 见 `@sema-agent/core` `dist/core/remote-env.d.ts` 的
80
- // `WorkspaceHandle.restoreMode:"park_only"`(「non-suspendable env;workspace persists on the target,
81
- // resume skips resumeVM」)与 `dist/core/types.d.ts` 的 `TaskResult.workspaceRestoreMode:"snapshot"|"park_only"`
82
- // (「the env declared itself non-suspendable …, so NOTHING was paused: the machine keeps running (and keeps
83
- // costing) … resume simply reconnects to the still-present workspace」)。
84
- // 运维面的实义差别(也是原注为什么危险):park-only 的 host worker 在挂起期间**继续占着这台机器、继续
85
- // 计费**,而 `suspendable:false` 同时**断言工作区在目标机上外部持久**(core 那条 LOAD-BEARING INVARIANT)
86
- // —— 所以谁清理这台机器上的残留由部署方负责,不能指望「反正被跳过了」。
87
41
  const leaderEndpoint = config.leaderEnabled &&
88
42
  (leaderProvider === "e2b" ||
89
43
  (leaderProvider === "k8s" && "s3" in leaderMinio && executionEnvFactory) ||
90
44
  (leaderProvider === "host" && executionEnvFactory))
91
45
  ? createLeaderEndpoint(createLeaderRunner({
92
46
  brain, models: config.models, roles: config.roles, pricing, logger,
93
- // design/170 件B/C/D:主车道装配出的**同一份**治理座席(单一属主,leader 不另判)。
94
47
  governance: {
95
48
  compliancePostureResolver: governanceSeams.compliancePostureResolver,
96
49
  lockedConfig: governanceSeams.lockedConfig,
97
50
  retentionPolicy: governanceSeams.retentionPolicy,
98
51
  },
99
52
  fanoutEnabled: config.leaderFanoutEnabled,
100
- // Route the single-vs-fanout classification on the cheap model (deepseek-v4-flash): the heavy
101
- // reasoning worker model returns empty ~2/3 of the time on the route prompt → silent collapse to
102
- // single (observed 2026-06-14). MODEL_ROUTER_ID overrides; else the cheap model; else default.
103
53
  ...((process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID)
104
54
  ? { routerModel: (process.env.MODEL_ROUTER_ID || process.env.MODEL_CHEAP_ID) }
105
55
  : {}),
106
56
  ...(leaderProvider === "e2b" ? { e2bApiKey: config.remoteExec.apiKey } : {}),
107
57
  ...(executionEnvFactory ? { envFactory: executionEnvFactory } : {}),
108
58
  ...leaderMinio,
109
- // Durable offload store for sub-worker tool results — without it core REFUSES the durable
110
- // suspend (InMemory offload would not survive a cross-replica resume) and the gated call is
111
- // denied via the unwired onAsk fallback → the worker fails instead of suspending (drill c3).
112
59
  ...(toolResultStore ? { toolResultStore } : {}),
113
- // k8s lane: align the wire's workspace with the adapter's durable-snapshot root (/workspace) —
114
- // the E2B default (/home/user) is OUTSIDE the k8s suspend tar, so a suspended sub-worker's repo
115
- // would silently vanish on resume (drill c3: 105-byte empty snapshots).
116
60
  ...(leaderProvider === "k8s" ? { workspace: "/workspace" } : {}),
117
- // Same store as the main runner: a suspended sub-worker's session must be readable on /decide
118
- // resume (sub-runner-local in-memory sessions die with "Entry <leafId> not found" on resume).
119
61
  sessionStore,
120
- // design/68 C4: sub-workers can durably suspend when the service runs durable approvals.
121
62
  ...(checkpointStore && config.approvalRequire.length > 0
122
63
  ? {
123
64
  durable: {
124
65
  checkpointStore,
125
66
  requireApproval: config.approvalRequire,
126
67
  deny: config.approvalDeny,
127
- // design/80: the leader's orchestrated sub-agents adopt the SAME supervisor loop as the
128
- // top-level agent — the D-E auto-budget (auto-approve NORMAL asks up to the budget, then
129
- // escalate) + the never-auto safety set. Without these a sub-agent's gated ask always
130
- // suspends-to-human, defeating the budget circuit-breaker for the supervised team.
131
68
  autoBudget: config.approvalAutoBudget,
132
69
  neverAuto: config.approvalNeverAuto,
133
70
  ...(config.approvalTimeoutSec > 0 ? { ttlMs: config.approvalTimeoutSec * 1000 } : {}),
@@ -136,13 +73,7 @@ export function createLeaderFace(ctx) {
136
73
  : {}),
137
74
  }), {
138
75
  logger,
139
- // SDK 全量核查(2026-07-24,黑板 GD 组)撞获:wire.ts:368 的 runLeader 实际要求这五个字段全部
140
- // 非空,此前同步门只查 objective,不完整请求会先拿到 202 再在后台必然失败——同步前移,让错误
141
- // 尽早暴露而不是靠后续 GET 才发现。
142
76
  requiredFields: ["objective", "durableRemote", "testCmd", "seedCmd", "baseSha"],
143
- // #193 车4 件1:durable 登记表(跨副本 GET + 提交幂等)。SQL 后端专有;缺席 ⇒ endpoint 落到
144
- // 显式的单副本内存降级臂。生产上这条缺席路径够不着 —— config 层已把「LEADER_ENABLED=true 且
145
- // 无外部 SQL 店」整个组合拒启(件2 门②),所以这里的可选链只服务单机试跑与测试装配。
146
77
  ...(leaderRunStore ? { store: leaderRunStore } : {}),
147
78
  })
148
79
  : undefined;
@@ -1,8 +1,6 @@
1
- /** USD/秒 → micro/ms 的**唯一**换算点(装配期构造与热同步共用;两处各算一次迟早会差一个 1e6)。 */
2
1
  export function buildQuotaBounds(config) {
3
2
  return { limitMicroUsd: Math.round(config.maxPrincipalCostUsd * 1e6), windowMs: config.costQuotaWindowSec * 1000 };
4
3
  }
5
- /** `create*`:返回带行为的活对象(闭包持座 + 上一代读数)。 */
6
4
  export function createLimitSync(ctx) {
7
5
  const { config, rateGate, quota, logger } = ctx;
8
6
  let lastRate;