@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,71 +1,13 @@
1
- /**
2
- * TaskRequest.settings wire — project a client's per-request `SemaSettings` stamp onto the per-task `TaskSpec`,
3
- * TIGHTEN-ONLY (deny-wins), at the SAME seam as runtime-governance (after the approval baseline + operator
4
- * governance). Backs the remote-mode settings path: sema runs over HTTP→engine with no local Runner, so the
5
- * resolved settings can't be applied by a local `tightenTaskSpec` — they ride on `TaskRequest.settings` and the
6
- * SERVICE projects them into the engine here.
7
- *
8
- * Design: `TaskRequest.settings` is a
9
- * PER-REQUEST ephemeral stamp = the v2-design TASK layer (a single-run override, NOT durable). It folds into THIS
10
- * spec via {@link tightenTaskSpec} and that is all — it is NEVER written to the durable `SessionPolicyStore` (the
11
- * operator/principal axis, design/99 §E6, a different trust axis + lifecycle). Re-applying on a resume leg is
12
- * idempotent (the same tighten), so no special resume handling is needed (mirrors runtime-governance).
13
- *
14
- * 🔒 SECURITY INVARIANTS (SDK settings.ts §3.3):
15
- * - permissions are TIGHTEN-ONLY — folded via `tightenTaskSpec` (deny-wins `combinePolicies`); it THROWS on any
16
- * override that would LOOSEN a stricter base safety field. The client can only ADD denies/asks/handsReadOnly on
17
- * top of the deployment + operator baseline, never widen it.
18
- * - permissionMode(轴B #2 注释保鲜,1.254——[816]/[820] 后的真实现):`defaultMode` 收**五**模式
19
- * (default | acceptEdits | plan | bypassPermissions | auto),bypass/auto 被 HONOR 为 gate-shape 选择
20
- * ——但深层不变量未变:bypass=「不加 ask 门」,恒不越部署基线(tightenTaskSpec deny-wins,基线 deny/
21
- * ask 照压);精化语义见 :2xx 行内注释。旧头注的「NEVER honored/三模式白名单」是 pre-[816] 拍照。
22
- *
23
- * 🔴 SYNC WITH sema-cli(壳仓)的 `src/sema/settings/seam.ts`: the permissions→toolPolicy + §6.4 permissionMode derive MIRRORS
24
- * the shell's LOCAL-Runner resolver — both compose the SAME core primitives (`createAllowDenyPolicy` /
25
- * `combinePolicies` / `tightenTaskSpec`), so the deny-wins ENFORCEMENT is core-single-source. Only the COMPOSITION
26
- * is duplicated for the remote path; a proposed follow-up extracts it to `@sema-agent/sdk` for true
27
- * single-source. Because the core backstops (deny-wins + tighten-throws-on-loosen + this bypass-reject) hold either
28
- * way, a composition drift would be a local/remote CONSISTENCY gap, never a privilege escalation.
29
- *
30
- * v1 scope: `permissions` + `permissions.defaultMode` + `model` + `outputStyle` + `env` (shipped 1.26.0). DEFERRED
31
- * (need a seam, flagged to the team + advertised in capabilities so a caller never silently loses a setting):
32
- * - (`env` is NO LONGER deferred — systematic-audit doc fix: core kept no untrusted `TaskSpec.env` seam (types §7
33
- * Q4), so the SERVICE applies it on the TRUSTED host lane: `settings.env` → `cleanEnvMap` → `shellEnv` on the
34
- * executionEnvFactory, single-user-gated. See `deriveSettings` below + main.ts.)
35
- * - (`hooks` NO LONGER deferred either — 轴B #2 注释保鲜:applied 形已接线(本文件 hooks 分片 +
36
- * main.ts 单用户闸),仅 malformed 才报 deferred;旧「需要 remote hook-runner」是接线前拍照。)
37
- */
38
1
  import { combinePolicies, createAllowDenyPolicy, createFsWriteGatePolicy, tightenTaskSpec, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, } from "@sema-agent/core";
39
2
  import { findCcRuleFormNames, findUnmatchableToolNames, formatUnmatchableToolNames } from "./config.js";
40
3
  import { parseHooksConfig } from "./hooks/hook-runner.js";
41
- /**
42
- * R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends the
43
- * CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
44
- * 🔴 TIGHTEN-ONLY vs the DEPLOYMENT baseline still holds for every value: the mode only selects the CLIENT-derived
45
- * gate ({@link deriveSettingsPolicy}); it can never subtract from the deployment/operator policy (applyTaskSettings
46
- * folds via tightenTaskSpec, deny-wins). Post-[816] the honored set is the five-word table above (NOT "CC verbatim" —
47
- * see the {@link SettingsPermissionMode} header for the three-set contrast; the earlier "five CC modes" wording here
48
- * was A-065 P2-1's finding: no CC set has five members). `acceptEdits`,
49
- * `bypassPermissions` and `auto` are HONORED as gate-shape choices (they no longer coerce to `default`, which
50
- * would FORCE the manual ask gate onto a caller that explicitly asked for less asking — with the gate live, the old
51
- * coercion would have been a behavior change for them, incl. a headless-deny regression for bypass callers). An
52
- * UNKNOWN string still coerces to `default` (fail-safe: the most-asking mode).
53
- * #157-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-five-mode value
54
- * (server.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
55
- * population the gate deliberately does not cover: RESUME replay of a persisted pre-gate body (outputStyle/model
56
- * two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
57
- */
58
4
  export function coercePermissionMode(raw) {
59
5
  if (raw === "plan" || raw === "acceptEdits" || raw === "default" || raw === "bypassPermissions" || raw === "auto")
60
6
  return raw;
61
7
  if (typeof raw === "string" && raw.length > 0)
62
- return "default"; // unknown → default (most-asking of OUR five; A-065 P3-1: for CC's `dontAsk` (=auto-deny) this fold IS a widening by CC semantics — reachable only via resume replay, fresh submits 400 at the HTTP gate)
63
- return undefined; // absent → no opinion (don't touch the spec)
8
+ return "default";
9
+ return undefined;
64
10
  }
65
- /** L2 ultracode (design/111): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
66
- * preset. ultracode FLOORS at the "ultra" tier — core's single source `resolveReasoningProfile("ultra").thinking`
67
- * (= "max"), never a hardcoded literal — so an explicit higher tier still wins (none exists above max today;
68
- * future-proof). Awareness rides for free: core composes ORCHESTRATION_AWARENESS from thinking∈{xhigh,max}, no field. */
69
11
  export function effectiveThinking(reasoningEffort, ultracode) {
70
12
  const explicit = isThinkingLevel(reasoningEffort) ? reasoningEffort : undefined;
71
13
  if (!ultracode)
@@ -73,56 +15,13 @@ export function effectiveThinking(reasoningEffort, ultracode) {
73
15
  const ultra = resolveReasoningProfile("ultra").thinking;
74
16
  return explicit && rankOf(explicit) > rankOf(ultra) ? explicit : ultra;
75
17
  }
76
- /** R4: fold a top-level `body.permissionMode` onto the parsed settings as `permissions.defaultMode` (the top-level
77
- * field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
78
- * no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
79
18
  export function withPermissionMode(settings, mode) {
80
19
  const base = settings ?? {};
81
20
  return { ...base, permissions: { ...base.permissions, defaultMode: mode } };
82
21
  }
83
- /**
84
- * design/201 §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
85
- * 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
86
- * 都没有 ⇒ `undefined` = **无表态**(调用点据此不写键,而不是替调用方选一个默认值)。
87
- *
88
- * 🔴 为什么必须是一只函数:同一个「生效模式」此前在 resolve-spec 里被算了两次(hooks 腿的
89
- * `permission_mode` 载荷、settings 折叠腿的 `withPermissionMode`),三审同点判定翻译表**不得**成为
90
- * 第三个算点 —— 三处各算各的,任何一次优先序修订都会让三面分家,而分家在这条轴上的形态是
91
- * 「壳发的 bundle bypass 在 A 面生效、在 B 面没生效」这种最难被外部发现的静默偏差。
92
- */
93
22
  export function effectivePermissionMode(body, settings) {
94
23
  return coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
95
24
  }
96
- /**
97
- * design/201 §2 —— 显式 permission mode → `TaskSpec.shellGate` 档位的**翻译表(单一真源)**。
98
- *
99
- * · `bypassPermissions` ⇒ `"off"` —— CC `--dangerously-skip-permissions` 的对位裁定
100
- * (与 fs-write 面的 bypass 臂两面归一,见 {@link deriveSettingsPolicy})。
101
- * · `auto` ⇒ `"classify"` —— **design/201 §8 开口按施工期新披露亲裁改译**(原裁定字面是 off,
102
- * §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
103
- *
104
- * ⚠️ **`auto` 这一行有一条待裁的开口(design/201 §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
105
- * 表驱动一行」)。施工期对抗复审给出了那条新披露,已亲读安装包核实**:
106
- * · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
107
- * decision.action === "ask" …)`);
108
- * · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
109
- * `shellGate === "off"` 臂反而发一条 `classification:"shell-gate-off"` 的 onError:「真可写 Bash 挂着
110
- * 却没有 shell 安全轴折叠」)。
111
- * 两条合起来:`auto` × off ⇒ Bash 既不产 ask、分类器也就永不被咨询 —— off 档下「筛选权交给分类器」
112
- * 不成立。故 auto 归 classify 组:分类器坐在 classify 产的 ask 之上,语义才真是「交给分类器」
113
- * (良性只读命令由分类器自动放行,其余 ask;这正是 auto 模式的本义)。发车帖向 [3378] 裁定链披露此
114
- * 一行偏离;core auto 分类器若来日在 off 档下也有咨询点,可再裁回(表驱动一行)。
115
- * · `default` / `acceptEdits` / `plan` ⇒ `"classify"` —— 这一档原先由壳无条件注入 `MANUAL_MODE_SHELL_GATE`
116
- * 供给(车道缺省走了 operator 通道),现在归位到表态轴。`plan` 本身 handsReadOnly(core 明写此档下
117
- * shellGate 无效),给它 classify 纯为一致性。
118
- *
119
- * 🔴 **入参非可选**:缺席(无表态)不是这张表的一行 —— 它的语义是「不写这个键」,只能在调用点判。
120
- * 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
121
- * 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
122
- *
123
- * 🔴 **闭集 exhaustive switch,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
124
- * 是**编译错误**(#157 的安全轴纪律:词表的未知项不许有静默兜底臂)。
125
- */
126
25
  export function shellGateForMode(effMode) {
127
26
  switch (effMode) {
128
27
  case "bypassPermissions":
@@ -134,18 +33,11 @@ export function shellGateForMode(effMode) {
134
33
  return "classify";
135
34
  }
136
35
  }
137
- /** Cap on `outputStyle` length — it lands in the system prompt, so it shares the `MAX_SYSTEM_PROMPT_CHARS` (16384)
138
- * posture (an uncapped prompt body is a per-turn token-cost hole; adversarial review). Exported so the HTTP layer
139
- * (prepareSpec) can 400 fail-loud on submit with the SAME bound this defensive parse enforces on every path. */
140
36
  export const MAX_SETTINGS_OUTPUT_STYLE_CHARS = 16_384;
141
- /** Cap on the element count of each permission list (allow/deny/ask) — bounds a pathological rule blast. */
142
37
  export const MAX_SETTINGS_PERMISSION_RULES = 1_000;
143
- /** Caps on `settings.env` — bound a pathological env blast (count + per-value length; values land in the shell env). */
144
38
  export const MAX_SETTINGS_ENV_VARS = 256;
145
39
  export const MAX_SETTINGS_ENV_VALUE_CHARS = 32_768;
146
40
  export const MAX_SETTINGS_ENV_KEY_CHARS = 256;
147
- /** A `Record<string,string>` of non-empty string keys → string values (capped), or undefined. Untrusted input →
148
- * defensive (drop non-string entries, over-long values, and bound the count). */
149
41
  function cleanEnvMap(v) {
150
42
  if (v === null || typeof v !== "object" || Array.isArray(v))
151
43
  return undefined;
@@ -154,7 +46,7 @@ function cleanEnvMap(v) {
154
46
  let seen = 0;
155
47
  for (const [k, val] of Object.entries(v)) {
156
48
  if (n >= MAX_SETTINGS_ENV_VARS || seen >= MAX_SETTINGS_ENV_VARS * 8)
157
- break; // bound BOTH accepted + total iterated (adversarial-review LOW)
49
+ break;
158
50
  seen++;
159
51
  if (typeof k === "string" && k.length > 0 && k.length <= MAX_SETTINGS_ENV_KEY_CHARS && typeof val === "string" && val.length <= MAX_SETTINGS_ENV_VALUE_CHARS) {
160
52
  out[k] = val;
@@ -163,29 +55,17 @@ function cleanEnvMap(v) {
163
55
  }
164
56
  return n > 0 ? out : undefined;
165
57
  }
166
- /** A string[] of non-empty strings (capped at {@link MAX_SETTINGS_PERMISSION_RULES}), or undefined. Untrusted input
167
- * → defensive (drop non-strings/empties; cap the count). */
168
58
  function cleanStringArray(v) {
169
59
  if (!Array.isArray(v))
170
60
  return undefined;
171
61
  const out = v.filter((x) => typeof x === "string" && x.length > 0).slice(0, MAX_SETTINGS_PERMISSION_RULES);
172
62
  return out.length > 0 ? out : undefined;
173
63
  }
174
- /**
175
- * Validate an untrusted `body.settings` (raw `unknown` off the wire) into the service-trusted {@link
176
- * ParsedTaskSettings}. NEVER trusts the wire type — every field is shape-checked; unknown keys are ignored
177
- * (forward-compatible, matching the SDK's open `[k: string]: unknown`). `defaultMode` accepts the FIVE modes
178
- * (post-[816]; unknown values coerce to `default` — see the header note). Returns the
179
- * present-but-deferred field names so the caller can warn/advertise rather than silently dropping `env`/`hooks`.
180
- */
181
64
  export function parseTaskSettings(raw) {
182
65
  if (raw === null || raw === undefined || typeof raw !== "object")
183
66
  return { deferred: [] };
184
67
  const s = raw;
185
68
  const deferred = [];
186
- // `hooks` is NO LONGER deferred(hook-runner 阶段一):契约校验(registry-core HooksConfig +
187
- // 服务侧上限)通过 → 进 applied 形状,由 apply 侧单用户闸决定翻不翻;malformed → 报 deferred(镜像 env 的
188
- // present-but-malformed 姿势——本函数是 resume 等路径的防御层,submit 路径另有 fail-loud 400)。
189
69
  let hooksConfig;
190
70
  if (s.hooks !== undefined && s.hooks !== null) {
191
71
  const parsedHooks = parseHooksConfig(s.hooks);
@@ -196,11 +76,9 @@ export function parseTaskSettings(raw) {
196
76
  }
197
77
  const shellEnv = cleanEnvMap(s.env);
198
78
  if (s.env !== undefined && s.env !== null && shellEnv === undefined)
199
- deferred.push("env"); // present but malformed/empty → report
79
+ deferred.push("env");
200
80
  const out = {};
201
81
  const perms = s.permissions;
202
- // F1(#157 复审留裁裁修):出现但形错(数组/标量)——本层是 RESUME 重放也走的宽容层,不 throw,但必
203
- // 留痕 deferred(镜像 env/hooks malformed 姿势;fresh submit 已被 http 门 400 拦,这里护的是存量行)。
204
82
  if (perms !== undefined && perms !== null && (typeof perms !== "object" || Array.isArray(perms))) {
205
83
  deferred.push("permissions");
206
84
  }
@@ -209,13 +87,6 @@ export function parseTaskSettings(raw) {
209
87
  const allow = cleanStringArray(p.allow);
210
88
  const deny = cleanStringArray(p.deny);
211
89
  const ask = cleanStringArray(p.ask);
212
- // 🔒 whitelist the five honored modes (OUR set, not a CC set — see SettingsPermissionMode header / A-065 P2-1);
213
- // any other value is dropped (absent = no opinion). This drop arm is the RESUME-replay lenient layer — fresh
214
- // submits already 400 at the HTTP gate (server.ts #157-③, [2766] ruling), same two-layer posture as the
215
- // top-level permissionMode leg. `bypassPermissions` is
216
- // accepted post-[816] because it now means "do not ADD the mode-derived fs-write ask gate" — exactly every
217
- // mode's pre-[816] behavior — and can never subtract from the deployment baseline (tightenTaskSpec deny-wins;
218
- // the module-header §3.3 invariant is thereby REFINED, not weakened: bypass still cannot LOOSEN anything).
219
90
  const mode = p.defaultMode;
220
91
  const defaultMode = mode === "default" || mode === "acceptEdits" || mode === "plan" || mode === "bypassPermissions" || mode === "auto" ? mode : undefined;
221
92
  if (allow || deny || ask || defaultMode) {
@@ -224,15 +95,11 @@ export function parseTaskSettings(raw) {
224
95
  }
225
96
  if (typeof s.model === "string" && s.model.length > 0)
226
97
  out.model = s.model;
227
- // outputStyle is appended to the system prompt → cap it (defense in depth; prepareSpec 400s an over-cap value on
228
- // submit, this drops it on any path the HTTP gate didn't cover, e.g. a resume of a pre-cap stored body).
229
98
  if (typeof s.outputStyle === "string" && s.outputStyle.length > 0 && s.outputStyle.length <= MAX_SETTINGS_OUTPUT_STYLE_CHARS) {
230
99
  out.outputStyle = s.outputStyle;
231
100
  }
232
101
  if (shellEnv)
233
102
  out.shellEnv = shellEnv;
234
- // L2 ultracode preset (design/111): a CC-parity boolean. Only `true` opts in; any other value is ignored (a settings
235
- // bundle that ONLY sets ultracode still returns `settings` via the hasApplied OR below).
236
103
  if (s.ultracode === true)
237
104
  out.ultracode = true;
238
105
  if (hooksConfig)
@@ -240,33 +107,12 @@ export function parseTaskSettings(raw) {
240
107
  const hasApplied = out.permissions !== undefined || out.model !== undefined || out.outputStyle !== undefined || out.shellEnv !== undefined || out.ultracode !== undefined || out.hooks !== undefined;
241
108
  return { settings: hasApplied ? out : undefined, deferred };
242
109
  }
243
- /** Defensive acceptance of the `appendSystemPrompt` rider ([1478] R2 + codex F2): the HTTP 400 gate (prepareSpec,
244
- * MAX_SYSTEM_PROMPT_CHARS) only covers FRESH submits — every resume family re-enters resolveSpec on a STORED body,
245
- * and a pre-1.243 store preserved unknown request keys verbatim (the field was INERT then), so an over-cap legacy
246
- * value must not become active prompt content after rollout (context exhaustion / spend hole). Over-cap ⇒ dropped +
247
- * warned — the rider was inert pre-upgrade, so dropping preserves the stored task's prior behavior; a 4xx here would
248
- * permanently brick that task's resume instead (same defensive posture as parseTaskSettings' outputStyle drop).
249
- * Cap value = the submit gate's (16384; MAX_SETTINGS_OUTPUT_STYLE_CHARS — both land in the same spec field). */
250
- /** TRUE when `text` is an ALREADY-ASSEMBLED system prompt — core's migration guard detects the same three
251
- * constitution anchors and takes the pass-through assembler arm, whose section subset (OPAQUE_KEPT, dist-read)
252
- * has NO `core/role.append`: any append rider (top-level or settings.outputStyle) would be SILENTLY discarded
253
- * there. Shared predicate for the submit 400 gate, the resolveSpec resume mirror, and the center-declaration
254
- * probe — one definition so the three gates can't drift. */
255
110
  export function hasConstitutionAnchors(text) {
256
111
  return typeof text === "string" && text.includes(CYBER_RISK) && text.includes(URL_SAFETY) && text.includes(HARNESS_SECTION_ANCHOR);
257
112
  }
258
- /** codex R9 (provider-aware append-less detection): mirrors core's ACTUAL pass-through triggers instead of a raw
259
- * body-text heuristic — the assembler's migration guard probes (a) `stableBlocks` identity declarations and
260
- * (b) `stableSystem` OUTPUT for the three anchors; a plain `userSystemPrompt` under the DEFAULT provider (no
261
- * scenario provider) never triggers it and `core/role.append` is retained, so an anchored systemPrompt there
262
- * must NOT block/strip a rider (R9 false-positive: unbranded default / scan rejected valid combos). Probing
263
- * calls the provider hooks with a minimal ctx — server providers (and centerPromptProvider) are pure functions
264
- * of ctx, and the same hooks run again inside core's assembly; a hook throw = conservative false (core will
265
- * fail the task loudly on its own). `replaceAll` owns the whole prompt(legacy free-form `system()` 腿已随 core 2.0.0 删除)
266
- * (REPLACE_ALL_EXCLUDED strips role.append) → append-less true. */
267
113
  export function providerDropsAppend(provider, userSystemPrompt) {
268
114
  if (!provider)
269
- return false; // core defaultPromptProvider — role.append always retained
115
+ return false;
270
116
  if (provider.replaceAll === true)
271
117
  return true;
272
118
  const ctx = { userSystemPrompt, tools: [], memoryEnabled: false, consolidationEnabled: false };
@@ -286,11 +132,11 @@ export function providerDropsAppend(provider, userSystemPrompt) {
286
132
  return false;
287
133
  }
288
134
  }
289
- return false; // core 2.0.0 删除了 legacy free-form `system()` 腿(不存在的腿不需要探测)——结构化 provider 到此都保 append
135
+ return false;
290
136
  }
291
137
  export function acceptAppendSystemPrompt(v, warn, packDropsAppend) {
292
138
  if (v === undefined)
293
- return undefined; // absent = the only QUIET arm (codex R8: defined-but-malformed must warn)
139
+ return undefined;
294
140
  if (typeof v !== "string" || v.length === 0) {
295
141
  warn?.(`appendSystemPrompt dropped: defined but malformed (${typeof v === "string" ? "empty string" : `non-string: ${typeof v}`}) — fresh submits 400 this shape; a stored resume body carrying it is normalized to absent`);
296
142
  return undefined;
@@ -299,29 +145,13 @@ export function acceptAppendSystemPrompt(v, warn, packDropsAppend) {
299
145
  warn?.(`appendSystemPrompt dropped: ${v.length} chars exceeds the ${MAX_SETTINGS_OUTPUT_STYLE_CHARS} cap (pre-cap stored body on a resume leg?)`);
300
146
  return undefined;
301
147
  }
302
- // codex R2/R3/R9: `packDropsAppend` = the caller's PROVIDER-AWARE append-less verdict ({@link
303
- // providerDropsAppend} over the effective provider / center snapshot — NOT a raw body-text heuristic, which
304
- // false-positived on default-provider scenarios where core retains role.append). Fresh submits 400 the combo
305
- // in resolveSpec; this is the resume-family mirror — the rider was INERT pre-1.243, so dropping WITH a warn
306
- // preserves the stored task's prior behavior and leaves an audit trail. (core typed error at the assembly
307
- // boundary = the structural endgame, requested on the board.)
308
148
  if (packDropsAppend === true) {
309
149
  warn?.("appendSystemPrompt dropped: the effective prompt pack cannot mount the append rider (assembled-identity/stableSystem pass-through, replaceAll, or legacy provider — no core/role.append section)");
310
150
  return undefined;
311
151
  }
312
152
  return v;
313
153
  }
314
- /** A name-keyed ASK-list ToolPolicy: a tool whose NAME is in `askNames` tightens to `ask` (HITL gate); every other
315
- * tool is out of scope (`allow`). core's `createAllowDenyPolicy` has only allow/deny slots (no `ask`), so the
316
- * CC-parity `permissions.ask` list — "prompt the human before THIS tool" — needs this thin policy. Composed
317
- * deny-wins with the allow/deny base, so `ask` only ever tightens (a baseline `deny` still wins; `allow`→`ask`). */
318
154
  function createAskListPolicy(askNames) {
319
- // RAW matching (core 5.0.0 RB-476 — the canonicalToolName fold is GONE): the rule name is compared to
320
- // `req.toolName` verbatim, no alias folding in either direction. A rule written against a retired alias
321
- // ("MultiEdit"/"Task") therefore matches NOTHING — which is why such a list is rejected up front
322
- // ({@link rejectUnmatchableSettingsNames}) instead of being silently mounted as a no-op gate.
323
- // (旧注写的 "Canonical-space matching, mirroring core's createAllowDenyPolicy (1.202)" 是折叠面还在时的
324
- // 拍照,与下面这行 RAW 代码整段矛盾——同批改正,连同 :348 的 "canonical toolName key space"。)
325
155
  const askSet = new Set(askNames);
326
156
  return {
327
157
  check(req) {
@@ -329,59 +159,28 @@ function createAskListPolicy(askNames) {
329
159
  ? { action: "ask", message: `tool "${req.toolName}" requires approval by your settings`, decisionReason: "rule" }
330
160
  : { action: "allow" };
331
161
  },
332
- // [931]③ core 1.300 名单池审计面:自定义 closure 无元数据曾是审计盲区(`ask:["*"]`/typo 名静默
333
- // no-op)——附 nameSets 后 core prepare 对实挂宇宙交叉核,无匹配名 → onError config.toolpolicy.
334
- // unmatched_names(enforcement 字节不变,纯观测)。core combinePolicies 全链透传。
335
162
  nameSets: [{ ask: askNames }],
336
163
  };
337
164
  }
338
- /** [1245]/[1247]③ — the mode-derived dynamic-workflow ask gate (CC "Run a dynamic workflow?" dialog, engine half).
339
- * core mounts `run_workflow` with effect="write" and NO exemption in the gate stack, but the mode-derived leg here
340
- * only ever gated fs writes — so manual/default mode ran workflows un-asked ([1245] clay dogfood). This leg keys
341
- * the canonical name(`Workflow`;5.0.0:旧拼法 aliases 已随 core RB-476 退役,raw 直比)
342
- * and attaches on default/auto/acceptEdits, independent of the fs-gate wiring (workflow gating is not
343
- * fs-dependent — sandbox lanes get it too). acceptEdits asks ON PURPOSE: CC's acceptEdits auto-allows file edits
344
- * only, a workflow confirmation is not an edit ([1248]② — cli to verify against the 212 live shell; flip there
345
- * if the real shell differs). bypassPermissions/undefined-mode attach nothing (pre-existing behavior); plan is
346
- * blocked upstream by handsReadOnly (effect="write"). Deployment disable stays `RuntimeCaps.allowWorkflows` —
347
- * this leg is the per-request HITL gate, not the capability switch.
348
- *
349
- * 🔴 A-033.1(#256):这三处曾**手抄字面量** `"Workflow"`。core 改名该工具时,手抄的名字与 core 实挂的名字
350
- * 分家 ⇒ ask 腿的 `req.toolName !== "Workflow"` 恒真、deny 腿恒假,**整条门不触发**,而且不报错、不留痕
351
- * —— 安全轴上的静默 fail-open。现在四处引用(本文件两条腿 + `nameSets` 审计面 + `http/server.ts` 的
352
- * 可满足性预检)全部派生自 core 导出的 {@link RUN_WORKFLOW_TOOL_NAME},core 一改名这四处同步跟着改。 */
353
165
  const WORKFLOW_ASK_NAMES = [RUN_WORKFLOW_TOOL_NAME];
354
166
  function createWorkflowAskPolicy(isExempt) {
355
167
  return {
356
168
  async check(req) {
357
169
  if (req.toolName !== RUN_WORKFLOW_TOOL_NAME)
358
170
  return { action: "allow" };
359
- // codex F2 (durable lane): the "don't ask again this session" grant (decide remember="session" →
360
- // approvalExemptionStore) is probed at the POLICY layer here — the durable lane parks every policy `ask`
361
- // and its own exempt probe only covers the deployment policy's asks, so without this probe a remembered
362
- // Workflow approval would re-park on every later call. Same posture as the fs-write gate's isExempt
363
- // (RAW toolName key space — 5.0.0 折叠面退役后各层比对的都是 `req.toolName` 逐字,一个 grant 服务所有层
364
- // 靠的是「同一个 raw 名」而不是「同一个 canonical 空间」);a THROWN probe ⇒ not exempt (fail-closed).
365
171
  if (isExempt) {
366
172
  try {
367
173
  if (await isExempt(req.toolName))
368
174
  return { action: "allow", decisionReason: "rule" };
369
175
  }
370
176
  catch {
371
- /* fail-closed: keep asking */
372
177
  }
373
178
  }
374
179
  return { action: "ask", message: `dynamic workflow "${req.toolName}" requires approval in this permission mode`, decisionReason: "rule" };
375
180
  },
376
- nameSets: [{ ask: WORKFLOW_ASK_NAMES }], // [931]③ audit face, same as createAskListPolicy
181
+ nameSets: [{ ask: WORKFLOW_ASK_NAMES }],
377
182
  };
378
183
  }
379
- /** codex F1 (plan-mode escape): `run_workflow` mounts off self-orchestration INDEPENDENT of the hands band
380
- * (core prepare-task dist 亲读:handsReadOnly only strips/reads the hand tools; the runtime plan blocker
381
- * `planModeRef` starts INACTIVE and only the model-driven EnterPlanMode tool arms it), so USER-driven plan
382
- * (permissionMode=plan) would run workflows — whose child agents write by default — during "read-only"
383
- * research. Plan therefore composes an explicit canonical-space DENY (not ask: plan is research, CC blocks
384
- * write-effect tools outright), deny-wins under the fold so nothing can un-tighten it. */
385
184
  function createWorkflowPlanDenyPolicy() {
386
185
  return {
387
186
  check(req) {
@@ -392,52 +191,6 @@ function createWorkflowPlanDenyPolicy() {
392
191
  nameSets: [{ deny: WORKFLOW_ASK_NAMES }],
393
192
  };
394
193
  }
395
- /**
396
- * Build the per-request permission ToolPolicy from the parsed settings (mirrors shell `seam.ts` — see header).
397
- * - base = name-keyed allow/deny (`createAllowDenyPolicy`): an `allow` list narrows to those tools; a `deny` list
398
- * hard-blocks — composed deny-wins with an ASK-list ({@link createAskListPolicy}) so `permissions.ask` actually
399
- * forces an approval gate (NOT silently dropped — review finding). `deny` > `ask` > `allow` on overlap.
400
- * - `defaultMode` derive onto that base ([820]/[822] five-mode table; the fs-write gate needs `gate` wiring —
401
- * without it every mode degrades to the base rules only, the pre-[816] behavior):
402
- * `default` → base + `createFsWriteGatePolicy({rootPath: cwd, exemptDirs:[scratchpad], ask})` — CC
403
- * manual: every fs write asks (the [816] P0 fix), scratchpad writes ride free.
404
- * `acceptEdits` → base + the same factory with `acceptDirs:[cwd]` — writes INSIDE the cwd subtree
405
- * auto-allow, OUTSIDE still ask (CC 207 dirname-variant card). NOT a loosen vs the
406
- * deployment: the factory only ever emits allow/ask and is folded deny-wins, so a
407
- * baseline deny/ask still wins; vs `default` it is simply a narrower CLIENT gate, and
408
- * both are client-side ADDITIONS the client may size itself.
409
- * `plan` → handsReadOnly + present_plan (unchanged; the write tools never reach an ask).
410
- * `bypassPermissions` → base only, NO factory ([820] table: bypass = "不加门" not "开门" — deployment
411
- * approval/governance baselines still apply; this is exactly every mode's pre-[816]
412
- * derive, so bypass grants nothing that wasn't already granted).
413
- * `auto` → base + the `default` factory (ask 兜底) — core's auto-mode classifier (entitlement-
414
- * gated: RunnerDeps.autoMode ∧ runtimeCaps.autoMode, 1.276/1.277) screens asks UPSTREAM
415
- * of resolveAsk, allowing safe calls and leaving suspicious ones on the ask path ([822]②).
416
- * absent (undefined) → base only, NO factory: an expressed-no-mode request keeps its pre-[816] behavior
417
- * (attaching the ask gate on a mere allow/deny-list stamp would regress headless callers
418
- * that never asked for a mode; the shell always sends its mode explicitly).
419
- * Returns the composed ToolPolicy + whether `handsReadOnly` should tighten. Returns `undefined` policy when there
420
- * is no permission rule and no mode-derived gate (nothing to project).
421
- */
422
- /**
423
- * A3 — the per-request leg of the ONE "this name can never match a live tool" judgment (single source:
424
- * config.ts {@link findUnmatchableToolNames}; the other two legs are the `APPROVAL_*` env vars, which reject at
425
- * boot, and the center-published `runtime.approvalRequire`, which rejects that domain and keeps the prior value).
426
- *
427
- * WHY this leg exists: `ask`/`deny` are mounted as RAW name comparisons (`createAskListPolicy` /
428
- * core's `createAllowDenyPolicy`, the 5.0.0 fold removal), so a retired name ("Task"/"bash"/"KillShell") or a
429
- * pre-prefix/incomplete MCP name ("figma__x"/"mcp__figma") mounts a gate that matches NOTHING — the caller
430
- * believes it added an approval gate or a hard block and got neither. That is the FAIL-OPEN direction, and it was
431
- * silent on this leg. Throwing here surfaces as a 422 at the HTTP boundary (resolve-spec already wraps every
432
- * throw out of `applyTaskSettings`), i.e. the same fail-loud posture the other two legs have.
433
- *
434
- * `allow` is deliberately NOT judged by the GENERAL arm: it is a WHITELIST — a name that matches nothing leaves
435
- * the allowlist effectively empty, so every tool falls to "not in the allowlist" and is DENIED. That fails closed
436
- * and is immediately visible to the caller, which is a different (and acceptable) failure mode from a silent hole.
437
- * The ONE exception is the CC-rule-form arm ({@link findCcRuleFormNames}, #186): `allow: ["Bash(ps:*)"]` is the
438
- * shape a CC user writes to get FEWER prompts and it silently costs them Bash entirely — a spelling-family error,
439
- * not a typo, so it is refused on all three lists. See the config.ts docblock for the two knobs it points at.
440
- */
441
194
  function rejectUnmatchableSettingsNames(perms) {
442
195
  for (const [field, names] of [["permissions.ask", perms.ask], ["permissions.deny", perms.deny]]) {
443
196
  if (!names)
@@ -458,39 +211,22 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
458
211
  return {};
459
212
  const { allow, deny, ask, defaultMode } = perms;
460
213
  rejectUnmatchableSettingsNames(perms);
461
- // The base name-keyed gate: allow/deny via core's helper + an ask-list (FINDING-1: `ask` must enforce, not drop).
462
- // Each is composed deny-wins (`deny` > `ask` > `allow`), so the fold can only ever tighten.
463
214
  const policies = [];
464
215
  if (allow || deny)
465
216
  policies.push(createAllowDenyPolicy({ ...(allow ? { allow } : {}), ...(deny ? { deny } : {}) }));
466
217
  if (ask)
467
218
  policies.push(createAskListPolicy(ask));
468
- // The mode-derived fs-write ask gate (core 1.290 factory — real shape read in dist: {env, rootPath?, acceptDirs?,
469
- // exemptDirs?, defaultWrite}, gates Write/Edit/NotebookEdit, exempt→accept→default layering, canonicalize inside
470
- // the given env, only ever allow/ask). undefined without wiring (see FsWriteGateWiring). #156/#165: the sandbox
471
- // lanes supply wiring too — same factory, a deferred real-env proxy and no cwd; the shape difference lives
472
- // entirely in what `resolve-spec.ts` puts in the wiring, not in a second code path here.
473
- // (③ sensitive-path DENY 已随 #177 搬 governance 层——见 FsWriteGateWiring 尾注。deny-wins 由
474
- // tightenTaskSpec 折叠保证:豁免/acceptDirs/settings allow 仍然越不过部署 DENY 基线。)
475
219
  const fsWriteGate = (acceptCwd) => {
476
220
  if (!gate)
477
221
  return undefined;
478
- // #156/#165: `cwd` is optional (absent on the sandbox lanes). Omitting `rootPath` makes core leave a relative
479
- // target relative → the sandbox proxy's lexical `absolutePath` refuses it → ask; omitting `acceptDirs` means
480
- // acceptEdits mints no auto-allow domain at all. Both are the intended fail-safe, NOT a degraded host arm.
481
222
  const gatePolicy = createFsWriteGatePolicy({
482
223
  env: gate.env,
483
224
  ...(gate.cwd !== undefined ? { rootPath: gate.cwd } : {}),
484
225
  ...(acceptCwd && gate.cwd !== undefined ? { acceptDirs: [gate.cwd] } : {}),
485
226
  ...(gate.scratchpadDir ? { exemptDirs: [gate.scratchpadDir] } : {}),
486
- ...(gate.isExempt ? { isExempt: gate.isExempt } : {}), // 1.294 session-exemption probe (see FsWriteGateWiring)
227
+ ...(gate.isExempt ? { isExempt: gate.isExempt } : {}),
487
228
  defaultWrite: "ask",
488
229
  });
489
- // NotebookEdit coverage is core-native since 1.296.1(我方 [886]① 请修 core 已兑现,1.207 的临时参数桥
490
- // 就此撤除):DEFAULT_GUARDED_TOOLS 含 NotebookEdit,且两 policy 共用 `writeTargetPath` 提取器——canonical
491
- // NotebookEdit 时 notebook_path 恒先(诱饵 file_path 失效),其余工具 file_path 先(反向诱饵也封);均 dist
492
- // 亲读。回归锚保留在 test/task-settings.test.ts(NotebookEdit deny + 双向诱饵),现在锁的是 core 行为经
493
- // 我方折叠——core 若回退,锚变红。
494
230
  return gatePolicy;
495
231
  };
496
232
  const compose = (...extra) => {
@@ -500,10 +236,6 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
500
236
  const basePolicy = compose();
501
237
  switch (defaultMode) {
502
238
  case "plan":
503
- // plan = read-only hands + the plan workflow: a TIGHTEN (handsReadOnly false→true), honored remotely.
504
- // ALSO mount core's `present_plan` (enablePlanMode) so the model explores read-only then
505
- // proposes a plan (CC EnterPlanMode→ExitPlanMode parity). Both are tighten/additive — no loosening.
506
- // codex F1: run_workflow mounts OUTSIDE the hands band — deny it explicitly (see createWorkflowPlanDenyPolicy).
507
239
  return { toolPolicy: compose(createWorkflowPlanDenyPolicy()), handsReadOnly: true, enablePlanMode: true };
508
240
  case "acceptEdits": {
509
241
  const p = compose(fsWriteGate(true), createWorkflowAskPolicy(workflowGate?.isExempt));
@@ -511,10 +243,6 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
511
243
  }
512
244
  case "default":
513
245
  case "auto": {
514
- // (historical note kept: the pre-[816] impl fell through to base-only here — with no gate to attach,
515
- // acceptEdits-as-loosen could not be honored. The [820] factory inverts the frame: default now ADDS an ask
516
- // gate and acceptEdits ADDS a narrower one, both tighten-safe under the deny-wins fold.)
517
- // ([1557]§四 的 shellGate 便车已随 #153 搬 governance 层——见 FsWriteGateWiring 尾注。)
518
246
  const p = compose(fsWriteGate(false), createWorkflowAskPolicy(workflowGate?.isExempt));
519
247
  return p ? { toolPolicy: p } : {};
520
248
  }
@@ -523,20 +251,6 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
523
251
  return basePolicy ? { toolPolicy: basePolicy } : {};
524
252
  }
525
253
  }
526
- /**
527
- * Apply a client's parsed per-request settings onto a base `TaskSpec`, TIGHTEN-ONLY, in a SINGLE {@link
528
- * tightenTaskSpec} pass (so the permission override composes deny-wins onto the baseline — TRAP #1 — and
529
- * `handsReadOnly`/`onAsk` can only narrow). `tightenTaskSpec` THROWS `TaskSpecTightenError` if any override would
530
- * LOOSEN a stricter base — surfaced by the caller as a 4xx (a client settings stamp that tries to widen is a
531
- * client error, never a silent weakening). `model`/`outputStyle` are applied directly (model is the caller's
532
- * choice WITHIN the catalog — the caller gates it before calling; outputStyle appends to the system prompt).
533
- *
534
- * Returns `base` untouched when the parsed settings project nothing onto the spec.
535
- */
536
- // ([1557]§四 的 SHELL_GATE_RANK 副本与 shellGateSafe 守卫已随 #153 搬 runtime-governance ——
537
- // settings 折叠不再触碰 TaskSpec.shellGate,base 上 governance 层施加的值原样透传。design/201 的
538
- // {@link shellGateForMode} 也不改这句话:那只纯函数产的是 governance 的 base,由 resolve-spec 阶段④
539
- // 写进 spec 字面量;本折叠拿到的 `base` 里可能因此已经带着一档 shellGate,而它照旧原样透传。)
540
254
  export function applyTaskSettings(base, settings, gate, workflowGate) {
541
255
  const { toolPolicy, handsReadOnly, enablePlanMode } = deriveSettingsPolicy(settings, gate, workflowGate);
542
256
  let next = base;
@@ -546,14 +260,10 @@ export function applyTaskSettings(base, settings, gate, workflowGate) {
546
260
  overrides.toolPolicy = toolPolicy;
547
261
  if (handsReadOnly !== undefined)
548
262
  overrides.handsReadOnly = handsReadOnly;
549
- next = tightenTaskSpec(next, overrides); // deny-wins compose + THROWS on loosen (fail-loud)
263
+ next = tightenTaskSpec(next, overrides);
550
264
  }
551
- // R4: enablePlanMode (mount present_plan) is ADDITIVE — it widens NOTHING (a read-only plan workflow), so it rides
552
- // directly (not through tightenTaskSpec, which guards safety fields). Only ever set true (plan), never cleared.
553
265
  if (enablePlanMode)
554
266
  next = { ...next, enablePlanMode: true };
555
- // outputStyle → appended system prompt (additive UX directive; never a safety field). The caller already set any
556
- // business systemPrompt; core's appendSystemPrompt rides after the scenario base.
557
267
  if (settings.outputStyle) {
558
268
  const appended = next.appendSystemPrompt ? `${next.appendSystemPrompt}\n\n${settings.outputStyle}` : settings.outputStyle;
559
269
  next = { ...next, appendSystemPrompt: appended };
@@ -1,82 +1,26 @@
1
- /**
2
- * Workflow super-set unlock — map the per-task `body.selfOrchestration` intent onto
3
- * `TaskSpec.selfOrchestration`, the gate that mounts core's LLM-authored `run_workflow` engine (the workflow.ts
4
- * agent/parallel/pipeline orchestrator). Same shape as the rank-1 roster gap: the engine (core run_workflow +
5
- * workflow.ts) is BUILT, but nothing mapped a body field to the per-task spec flag, so it was unreachable over HTTP
6
- * (capabilities advertised `workflows:true` but the LLM could never call run_workflow).
7
- *
8
- * Gating:
9
- * - DEPLOYMENT: `selfOrchestrationEnabled` must be on (the workflowScriptRunner + stores are wired). Otherwise a set
10
- * flag is a harmless no-op (core fail-closes run_workflow without the sandbox runner), so it's dropped for clarity.
11
- * - MULTI-TENANT: honored ONLY when a per-principal entitlement RESOLVER is wired (`hasEntitlementResolver`). 🔴 core's
12
- * `allowWorkflows` (RuntimeCaps) is TIGHTEN-ONLY — a resolver can only DENY; `undefined` = NO restriction, so the
13
- * deployment capability governs. Therefore WITHOUT a resolver the engine would mount run_workflow UNGATED for every
14
- * tenant. So we fail-closed here (defense-in-depth: a misconfigured multi-tenant deployment with no entitlement
15
- * source must NOT leak the LLM-authored workflow engine). WITH a resolver, core enforces `allowWorkflows`
16
- * per-principal (deny → fail-closed). Single-user (owns its own worker) honors directly. Three-gate: engine-can ∧
17
- * center-may(allowWorkflows) ∧ shell-show.
18
- */
19
1
  export function selfOrchestrationFromBody(body, config, hasEntitlementResolver) {
20
2
  if (body.selfOrchestration !== true || !config.selfOrchestrationEnabled)
21
3
  return undefined;
22
4
  if (config.requirePrincipal !== true)
23
- return true; // single-user: honor directly
24
- return hasEntitlementResolver ? true : undefined; // multi-tenant: fail-closed unless an entitlement resolver gates it
5
+ return true;
6
+ return hasEntitlementResolver ? true : undefined;
25
7
  }
26
- /**
27
- * §4 (Fork) — map the per-task `body.enableFork` intent onto `TaskSpec.enableFork`. core 1.257 (design/136 BREAKING)
28
- * RETIRED the standalone `Fork` tool; a fork is now `Agent(subagent_type:"fork")`, and `enableFork` is one of the two
29
- * governance axes (`forkGovernanceDenial(spec.enableFork, runtimeCaps.allowFork)`) that gate it.
30
- *
31
- * 🔴 CONSUME-CRITICAL — the default FLIPPED to opt-OUT: core now treats `enableFork === undefined` as **available**
32
- * (only an EXPLICIT `enableFork:false` / `allowFork:false` denies — CC's default posture). So where the SERVICE intends
33
- * to DENY, it must return `false` EXPLICITLY — the old "return undefined = not granted" is now fail-OPEN.
34
- * - DEPLOYMENT posture OFF (`!forkEnabled`, e.g. multi-tenant default): `false` (explicit deny — preserve opt-in posture).
35
- * - PER-TASK OPT-OUT (`body.enableFork === false`): `false` (any tenancy).
36
- * - SINGLE-USER TURNKEY: `true` — DEFAULT ON for the LLM (clay 2026-07-01). (core still only lets it fork where the
37
- * session store is fork-capable and never inside a fork / read-only band — graceful degrade, no capability lie.)
38
- * - MULTI-TENANT: `true` ONLY with a per-principal entitlement RESOLVER wired (core then enforces `allowFork`
39
- * tighten-only per-principal); WITHOUT a resolver `false` (fail-closed — `allowFork` is tighten-only so an absent
40
- * resolver = `undefined` = ungated, and now `enableFork:undefined` also auto-grants, so the service must hard-deny).
41
- */
42
8
  export function enableForkFromBody(body, config, hasEntitlementResolver) {
43
9
  if (!config.forkEnabled)
44
- return false; // posture must enable the primitive (FORK_ENABLED; posture-OFF multi-tenant) — explicit deny (opt-OUT default)
10
+ return false;
45
11
  if (body.enableFork === false)
46
- return false; // explicit per-task opt-OUT (any tenancy)
12
+ return false;
47
13
  if (config.requirePrincipal !== true)
48
- return true; // single-user turnkey: DEFAULT ON for the LLM
49
- return hasEntitlementResolver; // multi-tenant: honor iff a resolver gates it, else fail-closed (core enforces allowFork per-principal, tighten-only)
14
+ return true;
15
+ return hasEntitlementResolver;
50
16
  }
51
- /**
52
- * The workflow-script MODEL allowlist (`agent({model})` picks). core is
53
- * fail-closed: NO allowlist configured ⇒ `WorkflowModelNotAllowedError` on ANY model pick, which on a TOC
54
- * single-user deployment broke clay's own dual-model scripts (决策 v4-pro + 执行 v4-flash) out of the box —
55
- * the operator configured every catalog entry themselves, so there is nothing to protect them from.
56
- * - Explicit `SELF_ORCHESTRATION_MODELS` always wins (any tenancy — the operator's deliberate narrowing).
57
- * - SINGLE-USER: default to the deployment's OWN model catalog (`Object.keys(models)`).
58
- * - MULTI-TENANT: keep core's fail-closed empty default (a model pick is a cost/capability grant the
59
- * operator must make explicitly; a tenant script must not self-select the expensive rung).
60
- * Returns undefined when no allowlist should be set (core then fail-closes picks; the script's `agent()`
61
- * without a model still runs on the workflow's default role — only PICKING is gated).
62
- */
63
17
  export function workflowModelAllowlistFor(config) {
64
18
  if (config.selfOrchestrationModels.length > 0)
65
19
  return config.selfOrchestrationModels;
66
20
  return config.requirePrincipal !== true ? Object.keys(config.models) : undefined;
67
21
  }
68
- /** design/122 caps for a caller-supplied `retainSubagentSessions` object (core defaults 30min/16; the ledger
69
- * is run-scoped and fully released at parent end, so these only bound the WITHIN-RUN memory pin). */
70
- export const RETAIN_SUBAGENT_TTL_MS_CAP = 24 * 60 * 60 * 1000; // 24h — far above any sane interactive run
22
+ export const RETAIN_SUBAGENT_TTL_MS_CAP = 24 * 60 * 60 * 1000;
71
23
  export const RETAIN_SUBAGENT_MAX_CAP = 64;
72
- /**
73
- * design/122 (core 1.225): normalize an untrusted `body.retainSubagentSessions` onto the TaskSpec field.
74
- * `true`/`false` pass through (core's own defaults apply); an object keeps only NUMERIC ttlMs/max, each
75
- * clamped to a positive integer under the cap (retention pins settled child sessions in memory for the
76
- * parent run's lifetime — the knob must not become a caller-controlled resource hold). Anything malformed →
77
- * undefined = OFF (core default). Defensive, never throws: RESUME re-runs resolveSpec WITHOUT the HTTP-layer
78
- * validation, so this normalizer is the single guard on every path.
79
- */
80
24
  export function normalizeRetainSubagentSessions(raw) {
81
25
  if (raw === true || raw === false)
82
26
  return raw;
@@ -87,20 +31,9 @@ export function normalizeRetainSubagentSessions(raw) {
87
31
  const ttlMs = clamp(o.ttlMs, RETAIN_SUBAGENT_TTL_MS_CAP);
88
32
  const max = clamp(o.max, RETAIN_SUBAGENT_MAX_CAP);
89
33
  if (ttlMs === undefined && max === undefined)
90
- return undefined; // an empty/garbage object grants nothing
34
+ return undefined;
91
35
  return { ...(ttlMs !== undefined ? { ttlMs } : {}), ...(max !== undefined ? { max } : {}) };
92
36
  }
93
- /**
94
- * Per-task wall clock by TENANCY POSTURE (clay 2026-07-04, make-real 教训①:600s 默认墙钟砍掉了健康推进的
95
- * 长任务/chaos 恢复 run):
96
- * - SINGLE-USER turnkey(`requirePrincipal !== true`)= the user IS the superadmin → **NO wall clock**
97
- * (undefined; core arms none — its separate wedge safety-net still catches a stuck tool call, so "no wall"
98
- * never means "hangs forever"). CC has no whole-task decapitation either.
99
- * - MULTI-TENANT: base 2400s (big tasks — council/debate/discuss — 3600s). (PAIR-REVIEW F-4 附带修:此行
100
- * 曾写 600s/1500s 与代码漂移——真值以下方 base 为准。)
101
- * - Explicit `TASK_TIMEOUT_SEC` wins in ANY tenancy (the operator's deliberate wall) and keeps its documented
102
- * raise-only floor (`Math.max` with the base, so a misconfigured low value cannot shrink a council budget).
103
- */
104
37
  export function taskWallClockSec(envTimeoutSec, requirePrincipal, big) {
105
38
  const base = big ? 3600 : 2400;
106
39
  if (envTimeoutSec > 0)