@sema-agent/server 1.323.0 → 2.0.1

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 (565) hide show
  1. package/dist/approval-hmac.d.ts +40 -0
  2. package/dist/approval-hmac.js +67 -0
  3. package/dist/approval.d.ts +31 -0
  4. package/dist/approval.js +72 -4
  5. package/dist/audit.d.ts +26 -0
  6. package/dist/audit.js +40 -1
  7. package/dist/auth-bridge.d.ts +10 -0
  8. package/dist/auth-bridge.js +47 -4
  9. package/dist/auth-keys.d.ts +19 -0
  10. package/dist/auth-keys.js +13 -0
  11. package/dist/bake-runner/main.d.ts +5 -0
  12. package/dist/bake-runner/main.js +43 -4
  13. package/dist/bake-runner/protocol.d.ts +57 -0
  14. package/dist/bake-runner/protocol.js +58 -3
  15. package/dist/bake-runner/runner.d.ts +54 -0
  16. package/dist/bake-runner/runner.js +87 -5
  17. package/dist/bench/l8/artifact.d.ts +63 -0
  18. package/dist/bench/l8/artifact.js +61 -2
  19. package/dist/bench/l8/escape.d.ts +36 -0
  20. package/dist/bench/l8/escape.js +25 -0
  21. package/dist/bench/l8/index.d.ts +14 -0
  22. package/dist/bench/l8/index.js +14 -0
  23. package/dist/bench/l8/probes.d.ts +74 -0
  24. package/dist/bench/l8/probes.js +60 -0
  25. package/dist/bench/l8/run-probes.d.ts +43 -0
  26. package/dist/bench/l8/run-probes.js +45 -2
  27. package/dist/bench/s1/arms.d.ts +126 -0
  28. package/dist/bench/s1/arms.js +188 -13
  29. package/dist/bench/s1/live-deps.d.ts +66 -0
  30. package/dist/bench/s1/live-deps.js +249 -11
  31. package/dist/bench/s1/oracle.d.ts +49 -0
  32. package/dist/bench/s1/oracle.js +36 -1
  33. package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
  34. package/dist/bench/s1/repair-oracle-adapter.js +30 -0
  35. package/dist/bench/s1/reviewer.d.ts +38 -0
  36. package/dist/bench/s1/reviewer.js +34 -0
  37. package/dist/bench/s1/row.d.ts +77 -0
  38. package/dist/bench/s1/row.js +62 -0
  39. package/dist/bench/s1/run-firm.d.ts +47 -0
  40. package/dist/bench/s1/run-firm.js +70 -4
  41. package/dist/bench/s1/runner-ctx.d.ts +71 -0
  42. package/dist/bench/s1/runner-ctx.js +36 -0
  43. package/dist/bench/s1/tasks.d.ts +108 -0
  44. package/dist/bench/s1/tasks.js +115 -0
  45. package/dist/boot/budget-tracing.d.ts +48 -0
  46. package/dist/boot/budget-tracing.js +86 -0
  47. package/dist/boot/config-center.d.ts +62 -0
  48. package/dist/boot/config-center.js +995 -0
  49. package/dist/boot/coordinators.d.ts +33 -0
  50. package/dist/boot/coordinators.js +97 -0
  51. package/dist/boot/execution-env.d.ts +26 -0
  52. package/dist/boot/execution-env.js +370 -0
  53. package/dist/boot/leader.d.ts +27 -0
  54. package/dist/boot/leader.js +81 -0
  55. package/dist/boot/reapers.d.ts +53 -0
  56. package/dist/boot/reapers.js +252 -0
  57. package/dist/boot/resolve-spec.d.ts +70 -0
  58. package/dist/boot/resolve-spec.js +1072 -0
  59. package/dist/boot/runner-deps.d.ts +101 -0
  60. package/dist/boot/runner-deps.js +343 -0
  61. package/dist/boot/runtime-caps.d.ts +21 -0
  62. package/dist/boot/runtime-caps.js +62 -0
  63. package/dist/boot/session-faces.d.ts +57 -0
  64. package/dist/boot/session-faces.js +157 -0
  65. package/dist/boot/shutdown.d.ts +50 -0
  66. package/dist/boot/shutdown.js +129 -0
  67. package/dist/boot/stores.d.ts +32 -0
  68. package/dist/boot/stores.js +361 -0
  69. package/dist/boot/workflow-orchestration.d.ts +46 -0
  70. package/dist/boot/workflow-orchestration.js +150 -0
  71. package/dist/boot-reclaim.d.ts +19 -0
  72. package/dist/boot-reclaim.js +20 -0
  73. package/dist/brain.d.ts +25 -0
  74. package/dist/brain.js +61 -1
  75. package/dist/budget.d.ts +66 -0
  76. package/dist/budget.js +128 -4
  77. package/dist/capabilities/builtin-tools.d.ts +3 -0
  78. package/dist/capabilities/builtin-tools.js +3 -0
  79. package/dist/capabilities/center-plugins.d.ts +17 -1
  80. package/dist/capabilities/center-plugins.js +45 -4
  81. package/dist/capabilities/center-prompts.d.ts +46 -0
  82. package/dist/capabilities/center-prompts.js +57 -3
  83. package/dist/capabilities/code-review-council.d.ts +28 -0
  84. package/dist/capabilities/code-review-council.js +40 -7
  85. package/dist/capabilities/collab-wire.d.ts +2 -0
  86. package/dist/capabilities/collab-workflows.d.ts +7 -0
  87. package/dist/capabilities/collab-workflows.js +44 -1
  88. package/dist/capabilities/oa-tools.d.ts +6 -0
  89. package/dist/capabilities/oa-tools.js +10 -0
  90. package/dist/capabilities/prompt.d.ts +14 -0
  91. package/dist/capabilities/prompt.js +14 -0
  92. package/dist/capabilities/prompts/code-review.d.ts +14 -0
  93. package/dist/capabilities/prompts/code-review.js +14 -0
  94. package/dist/capabilities/prompts/identity.d.ts +6 -0
  95. package/dist/capabilities/prompts/identity.js +6 -0
  96. package/dist/capabilities/prompts/team.d.ts +4 -0
  97. package/dist/capabilities/prompts/team.js +4 -0
  98. package/dist/capabilities/repo-tools.d.ts +11 -0
  99. package/dist/capabilities/repo-tools.js +8 -0
  100. package/dist/capabilities/sandbox-file-send.d.ts +95 -0
  101. package/dist/capabilities/sandbox-file-send.js +91 -3
  102. package/dist/capabilities/scenarios.d.ts +130 -0
  103. package/dist/capabilities/scenarios.js +180 -7
  104. package/dist/capabilities/select-environment-tool.d.ts +7 -0
  105. package/dist/capabilities/select-environment-tool.js +30 -0
  106. package/dist/capabilities/send-user-file-tool.d.ts +33 -0
  107. package/dist/capabilities/send-user-file-tool.js +65 -1
  108. package/dist/capabilities/skills.d.ts +29 -0
  109. package/dist/capabilities/skills.js +13 -2
  110. package/dist/capabilities/team.d.ts +22 -0
  111. package/dist/capabilities/team.js +22 -2
  112. package/dist/capabilities/tool-defer.d.ts +26 -0
  113. package/dist/capabilities/tool-defer.js +3 -0
  114. package/dist/config-center/apply-effective.d.ts +65 -0
  115. package/dist/config-center/apply-effective.js +204 -12
  116. package/dist/config-center/http-client.d.ts +34 -0
  117. package/dist/config-center/http-client.js +68 -0
  118. package/dist/config-center/restart-signal.d.ts +22 -0
  119. package/dist/config-center/restart-signal.js +39 -0
  120. package/dist/config-center/skills-mcp.d.ts +20 -1
  121. package/dist/config-center/skills-mcp.js +42 -7
  122. package/dist/config-center/types.d.ts +92 -1
  123. package/dist/config-lkg.d.ts +12 -0
  124. package/dist/config-lkg.js +41 -0
  125. package/dist/config-provider.d.ts +78 -0
  126. package/dist/config-provider.js +165 -2
  127. package/dist/config-types.d.ts +585 -1
  128. package/dist/config.d.ts +78 -2
  129. package/dist/config.js +874 -301
  130. package/dist/degenerate-instrument.d.ts +38 -0
  131. package/dist/degenerate-instrument.js +55 -3
  132. package/dist/elicitation.d.ts +39 -0
  133. package/dist/elicitation.js +87 -3
  134. package/dist/env-facts.d.ts +61 -0
  135. package/dist/env-facts.js +73 -7
  136. package/dist/fleet/fleet-bus.d.ts +185 -0
  137. package/dist/fleet/fleet-bus.js +333 -27
  138. package/dist/fleet/subagent-tail-bus.d.ts +11 -0
  139. package/dist/fleet/subagent-tail-bus.js +33 -2
  140. package/dist/fleet-client.d.ts +38 -0
  141. package/dist/fleet-client.js +44 -9
  142. package/dist/fleet-lease.d.ts +46 -0
  143. package/dist/fleet-lease.js +73 -5
  144. package/dist/hooks/branch-transcript.d.ts +8 -0
  145. package/dist/hooks/branch-transcript.js +33 -0
  146. package/dist/hooks/cc-stop-prompt.d.ts +42 -0
  147. package/dist/hooks/cc-stop-prompt.js +43 -1
  148. package/dist/hooks/hook-llm.d.ts +15 -0
  149. package/dist/hooks/hook-llm.js +47 -0
  150. package/dist/hooks/hook-runner.d.ts +89 -0
  151. package/dist/hooks/hook-runner.js +327 -18
  152. package/dist/http/idempotency.d.ts +31 -0
  153. package/dist/http/idempotency.js +34 -0
  154. package/dist/http/principal-gate.d.ts +17 -0
  155. package/dist/http/principal-gate.js +35 -3
  156. package/dist/http/route-ctx.d.ts +36 -0
  157. package/dist/http/routes/approvals-assistant.d.ts +25 -0
  158. package/dist/http/routes/approvals-assistant.js +253 -23
  159. package/dist/http/routes/attachments.js +22 -4
  160. package/dist/http/routes/capabilities.d.ts +7 -0
  161. package/dist/http/routes/capabilities.js +202 -1
  162. package/dist/http/routes/fleet.d.ts +6 -0
  163. package/dist/http/routes/fleet.js +94 -5
  164. package/dist/http/routes/images.d.ts +17 -0
  165. package/dist/http/routes/images.js +222 -14
  166. package/dist/http/routes/leader.d.ts +7 -0
  167. package/dist/http/routes/leader.js +10 -0
  168. package/dist/http/routes/memory-policy.d.ts +6 -0
  169. package/dist/http/routes/memory-policy.js +38 -6
  170. package/dist/http/routes/notify-wake.d.ts +10 -0
  171. package/dist/http/routes/notify-wake.js +19 -3
  172. package/dist/http/routes/observability.d.ts +6 -0
  173. package/dist/http/routes/observability.js +19 -2
  174. package/dist/http/routes/runs.d.ts +15 -0
  175. package/dist/http/routes/runs.js +464 -41
  176. package/dist/http/routes/session-sync.d.ts +15 -0
  177. package/dist/http/routes/session-sync.js +229 -19
  178. package/dist/http/routes/sessions-list.d.ts +6 -0
  179. package/dist/http/routes/sessions-list.js +35 -4
  180. package/dist/http/routes/sessions.d.ts +14 -0
  181. package/dist/http/routes/sessions.js +176 -31
  182. package/dist/http/routes/side-query.d.ts +8 -0
  183. package/dist/http/routes/side-query.js +26 -1
  184. package/dist/http/routes/tasks.d.ts +18 -0
  185. package/dist/http/routes/tasks.js +501 -38
  186. package/dist/http/routes/trace-usage.d.ts +10 -0
  187. package/dist/http/routes/trace-usage.js +66 -14
  188. package/dist/http/routes/workflows.d.ts +12 -0
  189. package/dist/http/routes/workflows.js +180 -19
  190. package/dist/http/run-meta.d.ts +11 -0
  191. package/dist/http/run-meta.js +6 -0
  192. package/dist/http/send.d.ts +19 -0
  193. package/dist/http/send.js +11 -0
  194. package/dist/http/server.d.ts +255 -0
  195. package/dist/http/server.js +921 -75
  196. package/dist/http/sse-log.d.ts +15 -0
  197. package/dist/http/sse-log.js +16 -0
  198. package/dist/http/tar.d.ts +9 -0
  199. package/dist/http/tar.js +21 -5
  200. package/dist/http/wire-gate.d.ts +9 -0
  201. package/dist/http/wire-gate.js +9 -0
  202. package/dist/http/wire-types.d.ts +187 -0
  203. package/dist/http/workspace-content.d.ts +8 -0
  204. package/dist/http/workspace-content.js +10 -0
  205. package/dist/images/bake-validate.d.ts +63 -0
  206. package/dist/images/bake-validate.js +71 -1
  207. package/dist/images/manifest.d.ts +19 -0
  208. package/dist/images/manifest.js +6 -1
  209. package/dist/index.d.ts +1 -5
  210. package/dist/index.js +10 -5
  211. package/dist/key-resolver.d.ts +23 -0
  212. package/dist/key-resolver.js +27 -2
  213. package/dist/leader/diffout.d.ts +23 -0
  214. package/dist/leader/diffout.js +8 -1
  215. package/dist/leader/diffup.d.ts +32 -0
  216. package/dist/leader/diffup.js +43 -0
  217. package/dist/leader/endpoint.d.ts +20 -0
  218. package/dist/leader/endpoint.js +25 -2
  219. package/dist/leader/fanout.d.ts +60 -0
  220. package/dist/leader/fanout.js +29 -3
  221. package/dist/leader/grader-env-factory.d.ts +74 -0
  222. package/dist/leader/grader-env-factory.js +62 -3
  223. package/dist/leader/leader.d.ts +97 -0
  224. package/dist/leader/leader.js +151 -4
  225. package/dist/leader/merge.d.ts +94 -0
  226. package/dist/leader/merge.js +112 -9
  227. package/dist/leader/planner.d.ts +52 -0
  228. package/dist/leader/planner.js +47 -3
  229. package/dist/leader/repair-oracle.d.ts +61 -0
  230. package/dist/leader/repair-oracle.js +59 -1
  231. package/dist/leader/repair-wire.d.ts +78 -0
  232. package/dist/leader/repair-wire.js +79 -2
  233. package/dist/leader/wire.d.ts +79 -0
  234. package/dist/leader/wire.js +255 -8
  235. package/dist/lsp/e2b-bridge.d.ts +16 -0
  236. package/dist/lsp/e2b-bridge.js +32 -4
  237. package/dist/lsp/e2b-manager.d.ts +10 -0
  238. package/dist/lsp/e2b-manager.js +18 -2
  239. package/dist/lsp/lsp-frames.d.ts +12 -0
  240. package/dist/lsp/lsp-frames.js +12 -0
  241. package/dist/lsp/manager.d.ts +12 -0
  242. package/dist/lsp/manager.js +35 -2
  243. package/dist/lsp/types.d.ts +10 -0
  244. package/dist/lsp/ws-transport.d.ts +12 -0
  245. package/dist/lsp/ws-transport.js +53 -5
  246. package/dist/lsp-evict.d.ts +14 -0
  247. package/dist/lsp-evict.js +15 -1
  248. package/dist/main.js +530 -2129
  249. package/dist/memory-export.d.ts +1 -0
  250. package/dist/memory-export.js +4 -0
  251. package/dist/memory-scope.d.ts +49 -0
  252. package/dist/memory-scope.js +87 -4
  253. package/dist/memory-sync-client.d.ts +24 -0
  254. package/dist/memory-sync-client.js +41 -2
  255. package/dist/memory-sync.d.ts +23 -0
  256. package/dist/memory-sync.js +70 -1
  257. package/dist/model-select.d.ts +39 -0
  258. package/dist/model-select.js +42 -1
  259. package/dist/observability/cost-quota.d.ts +20 -0
  260. package/dist/observability/cost-quota.js +4 -0
  261. package/dist/observability/cost-taxonomy.d.ts +45 -0
  262. package/dist/observability/cost-taxonomy.js +34 -0
  263. package/dist/observability/logger.d.ts +1 -0
  264. package/dist/observability/logger.js +6 -0
  265. package/dist/observability/metrics.d.ts +13 -0
  266. package/dist/observability/metrics.js +63 -0
  267. package/dist/observability/otel-exporter.d.ts +9 -0
  268. package/dist/observability/otel-exporter.js +11 -3
  269. package/dist/observability/principal-context.d.ts +9 -0
  270. package/dist/observability/principal-context.js +9 -0
  271. package/dist/observability/prompt-manifest.d.ts +37 -0
  272. package/dist/observability/prompt-manifest.js +35 -1
  273. package/dist/observability/rate-limit.d.ts +10 -0
  274. package/dist/observability/rate-limit.js +1 -0
  275. package/dist/observability/tool-trace.d.ts +24 -0
  276. package/dist/observability/tool-trace.js +41 -1
  277. package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
  278. package/dist/orchestration/hardened-vm-runner.js +119 -4
  279. package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
  280. package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
  281. package/dist/orchestration/hardened-vm-worker.js +27 -0
  282. package/dist/orchestration/subagent-steer.d.ts +44 -0
  283. package/dist/orchestration/subagent-steer.js +45 -1
  284. package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
  285. package/dist/orchestration/workflow-agent-steer.js +80 -1
  286. package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
  287. package/dist/orchestration/workflow-completion-inbox.js +283 -32
  288. package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
  289. package/dist/orchestration/workflow-notify-journal.js +187 -13
  290. package/dist/parked-decide.d.ts +61 -0
  291. package/dist/parked-decide.js +58 -1
  292. package/dist/per-task-image.d.ts +54 -0
  293. package/dist/per-task-image.js +41 -0
  294. package/dist/plan-cache-probe.d.ts +35 -0
  295. package/dist/plan-cache-probe.js +18 -3
  296. package/dist/plugins/approval-exemption-store.d.ts +7 -0
  297. package/dist/plugins/approval-exemption-store.js +28 -2
  298. package/dist/plugins/approval-store-sql.d.ts +64 -0
  299. package/dist/plugins/approval-store-sql.js +32 -0
  300. package/dist/plugins/background-agent-store-sql.d.ts +55 -0
  301. package/dist/plugins/background-agent-store-sql.js +91 -4
  302. package/dist/plugins/background-shell-support.d.ts +98 -0
  303. package/dist/plugins/background-shell-support.js +117 -13
  304. package/dist/plugins/blob-backend.d.ts +104 -0
  305. package/dist/plugins/blob-backend.js +163 -6
  306. package/dist/plugins/breaker-state-sql.d.ts +65 -1
  307. package/dist/plugins/breaker-state-sql.js +46 -8
  308. package/dist/plugins/caching-session-store.d.ts +42 -0
  309. package/dist/plugins/caching-session-store.js +63 -3
  310. package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
  311. package/dist/plugins/checkpoint-store-sql.js +239 -11
  312. package/dist/plugins/file-outcome-sink.d.ts +4 -0
  313. package/dist/plugins/file-outcome-sink.js +9 -0
  314. package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
  315. package/dist/plugins/file-resume-anchor-store.js +43 -4
  316. package/dist/plugins/file-run-store.d.ts +90 -0
  317. package/dist/plugins/file-run-store.js +145 -13
  318. package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
  319. package/dist/plugins/file-snapshot-store-sql.js +178 -10
  320. package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
  321. package/dist/plugins/file-workflow-journal-store.js +10 -0
  322. package/dist/plugins/fork-routing-session-store.d.ts +47 -0
  323. package/dist/plugins/fork-routing-session-store.js +51 -3
  324. package/dist/plugins/host-platform.d.ts +69 -0
  325. package/dist/plugins/host-platform.js +81 -2
  326. package/dist/plugins/image-bake-store-sql.d.ts +136 -1
  327. package/dist/plugins/image-bake-store-sql.js +241 -7
  328. package/dist/plugins/image-index-sql.d.ts +46 -0
  329. package/dist/plugins/image-index-sql.js +113 -4
  330. package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
  331. package/dist/plugins/k8s-bg-scripts.js +88 -4
  332. package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
  333. package/dist/plugins/k8s-exec-protocol.js +41 -0
  334. package/dist/plugins/local-checkpoint-store.d.ts +40 -0
  335. package/dist/plugins/local-checkpoint-store.js +86 -7
  336. package/dist/plugins/local-session-store.d.ts +157 -0
  337. package/dist/plugins/local-session-store.js +289 -34
  338. package/dist/plugins/local-task-attachment-store.js +14 -2
  339. package/dist/plugins/mailbox-store-sql.d.ts +38 -0
  340. package/dist/plugins/mailbox-store-sql.js +39 -6
  341. package/dist/plugins/memory-engine-pg.d.ts +30 -0
  342. package/dist/plugins/memory-engine-pg.js +122 -4
  343. package/dist/plugins/memory-engine-tidb.d.ts +26 -0
  344. package/dist/plugins/memory-engine-tidb.js +115 -5
  345. package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
  346. package/dist/plugins/memory-engine-vector-util.js +5 -0
  347. package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
  348. package/dist/plugins/memory-resume-anchor-store.js +17 -0
  349. package/dist/plugins/memory-run-store.d.ts +63 -0
  350. package/dist/plugins/memory-run-store.js +73 -8
  351. package/dist/plugins/memory-session-policy-store.d.ts +17 -0
  352. package/dist/plugins/memory-session-policy-store.js +17 -0
  353. package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
  354. package/dist/plugins/memory-sync-store-pg.js +45 -2
  355. package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
  356. package/dist/plugins/memory-sync-store-tidb.js +31 -1
  357. package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
  358. package/dist/plugins/outcome-ledger-sql.js +97 -3
  359. package/dist/plugins/pg-approval-store.d.ts +7 -0
  360. package/dist/plugins/pg-approval-store.js +7 -0
  361. package/dist/plugins/pg-breaker-state.d.ts +6 -0
  362. package/dist/plugins/pg-breaker-state.js +6 -0
  363. package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
  364. package/dist/plugins/pg-checkpoint-store.js +8 -0
  365. package/dist/plugins/pg-cost-quota.d.ts +17 -0
  366. package/dist/plugins/pg-cost-quota.js +6 -0
  367. package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
  368. package/dist/plugins/pg-file-snapshot-store.js +6 -0
  369. package/dist/plugins/pg-image-bake.d.ts +9 -0
  370. package/dist/plugins/pg-image-bake.js +9 -0
  371. package/dist/plugins/pg-image-index.d.ts +9 -0
  372. package/dist/plugins/pg-image-index.js +9 -0
  373. package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
  374. package/dist/plugins/pg-outcome-ledger.js +9 -0
  375. package/dist/plugins/pg-pool.d.ts +50 -0
  376. package/dist/plugins/pg-pool.js +78 -0
  377. package/dist/plugins/pg-query.d.ts +11 -0
  378. package/dist/plugins/pg-rate-limiter.d.ts +23 -0
  379. package/dist/plugins/pg-rate-limiter.js +12 -2
  380. package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
  381. package/dist/plugins/pg-resume-anchor-store.js +5 -0
  382. package/dist/plugins/pg-run-store.d.ts +7 -0
  383. package/dist/plugins/pg-run-store.js +7 -0
  384. package/dist/plugins/pg-safe-json.d.ts +13 -0
  385. package/dist/plugins/pg-safe-json.js +40 -4
  386. package/dist/plugins/pg-session-policy-store.d.ts +5 -0
  387. package/dist/plugins/pg-session-policy-store.js +5 -0
  388. package/dist/plugins/pg-session-storage.d.ts +72 -0
  389. package/dist/plugins/pg-session-storage.js +152 -20
  390. package/dist/plugins/pg-session-store.d.ts +10 -0
  391. package/dist/plugins/pg-session-store.js +10 -0
  392. package/dist/plugins/pg-tool-result-store.d.ts +7 -0
  393. package/dist/plugins/pg-tool-result-store.js +7 -0
  394. package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
  395. package/dist/plugins/pg-workflow-journal-store.js +7 -0
  396. package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
  397. package/dist/plugins/pg-workflow-run-store.js +7 -0
  398. package/dist/plugins/posix-shell-fs.d.ts +3 -0
  399. package/dist/plugins/posix-shell-fs.js +31 -1
  400. package/dist/plugins/remote-env-adb.d.ts +25 -0
  401. package/dist/plugins/remote-env-adb.js +95 -12
  402. package/dist/plugins/remote-env-e2b.d.ts +146 -0
  403. package/dist/plugins/remote-env-e2b.js +371 -40
  404. package/dist/plugins/remote-env-file-error.d.ts +34 -0
  405. package/dist/plugins/remote-env-file-error.js +37 -0
  406. package/dist/plugins/remote-env-host.d.ts +151 -0
  407. package/dist/plugins/remote-env-host.js +468 -70
  408. package/dist/plugins/remote-env-k8s.d.ts +135 -0
  409. package/dist/plugins/remote-env-k8s.js +318 -32
  410. package/dist/plugins/remote-env-local-docker.d.ts +65 -0
  411. package/dist/plugins/remote-env-local-docker.js +149 -24
  412. package/dist/plugins/remote-env-ssh.d.ts +35 -0
  413. package/dist/plugins/remote-env-ssh.js +117 -20
  414. package/dist/plugins/remote-scratchpad.d.ts +38 -0
  415. package/dist/plugins/remote-scratchpad.js +32 -2
  416. package/dist/plugins/remote-shell.d.ts +22 -0
  417. package/dist/plugins/remote-shell.js +27 -1
  418. package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
  419. package/dist/plugins/resume-anchor-store-sql.js +11 -0
  420. package/dist/plugins/roster-store-sql.d.ts +27 -0
  421. package/dist/plugins/roster-store-sql.js +54 -0
  422. package/dist/plugins/run-store-sql.d.ts +194 -0
  423. package/dist/plugins/run-store-sql.js +210 -9
  424. package/dist/plugins/s3-presign.d.ts +20 -0
  425. package/dist/plugins/s3-presign.js +49 -2
  426. package/dist/plugins/scheduler-support.d.ts +12 -0
  427. package/dist/plugins/scheduler-support.js +75 -3
  428. package/dist/plugins/send-file-ledger.d.ts +15 -0
  429. package/dist/plugins/send-file-ledger.js +53 -4
  430. package/dist/plugins/send-user-file.d.ts +77 -0
  431. package/dist/plugins/send-user-file.js +92 -4
  432. package/dist/plugins/session-policy-store-sql.d.ts +15 -0
  433. package/dist/plugins/session-policy-store-sql.js +75 -3
  434. package/dist/plugins/session-store.d.ts +45 -0
  435. package/dist/plugins/session-store.js +51 -0
  436. package/dist/plugins/sql-driver.d.ts +77 -0
  437. package/dist/plugins/sql-driver.js +4 -0
  438. package/dist/plugins/sql-escape.d.ts +8 -0
  439. package/dist/plugins/sql-escape.js +8 -0
  440. package/dist/plugins/sql-row-helpers.d.ts +25 -0
  441. package/dist/plugins/sql-row-helpers.js +25 -0
  442. package/dist/plugins/store-backend.d.ts +103 -0
  443. package/dist/plugins/store-backend.js +120 -27
  444. package/dist/plugins/store-contracts.d.ts +62 -0
  445. package/dist/plugins/store-contracts.js +3 -0
  446. package/dist/plugins/task-attachment-store.d.ts +60 -0
  447. package/dist/plugins/task-attachment-store.js +49 -3
  448. package/dist/plugins/task-list-store-sql.d.ts +25 -0
  449. package/dist/plugins/task-list-store-sql.js +21 -0
  450. package/dist/plugins/tidb-approval-store.d.ts +6 -0
  451. package/dist/plugins/tidb-approval-store.js +6 -0
  452. package/dist/plugins/tidb-breaker-state.d.ts +5 -0
  453. package/dist/plugins/tidb-breaker-state.js +5 -0
  454. package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
  455. package/dist/plugins/tidb-checkpoint-store.js +7 -0
  456. package/dist/plugins/tidb-cost-quota.d.ts +6 -0
  457. package/dist/plugins/tidb-cost-quota.js +1 -0
  458. package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
  459. package/dist/plugins/tidb-file-snapshot-store.js +6 -0
  460. package/dist/plugins/tidb-image-bake.d.ts +8 -0
  461. package/dist/plugins/tidb-image-bake.js +8 -0
  462. package/dist/plugins/tidb-image-index.d.ts +8 -0
  463. package/dist/plugins/tidb-image-index.js +8 -0
  464. package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
  465. package/dist/plugins/tidb-outcome-ledger.js +10 -0
  466. package/dist/plugins/tidb-pool.d.ts +56 -0
  467. package/dist/plugins/tidb-pool.js +181 -1
  468. package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
  469. package/dist/plugins/tidb-rate-limiter.js +5 -2
  470. package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
  471. package/dist/plugins/tidb-resume-anchor-store.js +5 -0
  472. package/dist/plugins/tidb-run-store.d.ts +7 -0
  473. package/dist/plugins/tidb-run-store.js +7 -0
  474. package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
  475. package/dist/plugins/tidb-session-policy-store.js +5 -0
  476. package/dist/plugins/tidb-session-storage.d.ts +43 -0
  477. package/dist/plugins/tidb-session-storage.js +62 -3
  478. package/dist/plugins/tidb-session-store.d.ts +142 -0
  479. package/dist/plugins/tidb-session-store.js +264 -16
  480. package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
  481. package/dist/plugins/tidb-tool-result-store.js +7 -0
  482. package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
  483. package/dist/plugins/tidb-workflow-journal-store.js +7 -0
  484. package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
  485. package/dist/plugins/tidb-workflow-run-store.js +8 -0
  486. package/dist/plugins/tool-result-store-sql.d.ts +58 -0
  487. package/dist/plugins/tool-result-store-sql.js +38 -2
  488. package/dist/plugins/web-search.d.ts +35 -0
  489. package/dist/plugins/web-search.js +45 -9
  490. package/dist/plugins/workflow-journal-limits.d.ts +10 -0
  491. package/dist/plugins/workflow-journal-limits.js +10 -0
  492. package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
  493. package/dist/plugins/workflow-journal-store-sql.js +58 -2
  494. package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
  495. package/dist/plugins/workflow-run-store-sql.js +84 -6
  496. package/dist/plugins/worktree-isolation.d.ts +45 -0
  497. package/dist/plugins/worktree-isolation.js +105 -6
  498. package/dist/plugins/write-behind-counter.d.ts +56 -1
  499. package/dist/plugins/write-behind-counter.js +53 -14
  500. package/dist/principal-jwt.d.ts +24 -0
  501. package/dist/principal-jwt.js +60 -5
  502. package/dist/project-memory.d.ts +22 -0
  503. package/dist/project-memory.js +144 -15
  504. package/dist/prompts-domain-validate.d.ts +28 -0
  505. package/dist/prompts-domain-validate.js +42 -1
  506. package/dist/question.d.ts +33 -0
  507. package/dist/question.js +69 -2
  508. package/dist/resource-suspend.d.ts +21 -0
  509. package/dist/resource-suspend.js +16 -0
  510. package/dist/router/route-orchestration.d.ts +78 -0
  511. package/dist/router/route-orchestration.js +77 -0
  512. package/dist/run-local.d.ts +32 -0
  513. package/dist/run-local.js +172 -8
  514. package/dist/runs.d.ts +189 -4
  515. package/dist/runs.js +465 -24
  516. package/dist/runtime-caps-resolver.d.ts +106 -0
  517. package/dist/runtime-caps-resolver.js +89 -4
  518. package/dist/runtime-governance.d.ts +84 -0
  519. package/dist/runtime-governance.js +127 -1
  520. package/dist/sandbox-pkg-source.d.ts +23 -0
  521. package/dist/sandbox-pkg-source.js +37 -0
  522. package/dist/sealed-key.d.ts +54 -0
  523. package/dist/sealed-key.js +68 -3
  524. package/dist/security.d.ts +108 -0
  525. package/dist/security.js +145 -6
  526. package/dist/sema-registry.d.ts +21 -0
  527. package/dist/sema-registry.js +21 -0
  528. package/dist/session-leaf-bus.d.ts +31 -0
  529. package/dist/session-leaf-bus.js +32 -0
  530. package/dist/session-sync-content.d.ts +46 -0
  531. package/dist/session-sync-content.js +47 -1
  532. package/dist/session-sync-kernel.d.ts +89 -0
  533. package/dist/session-sync-kernel.js +48 -3
  534. package/dist/session-sync.d.ts +125 -0
  535. package/dist/session-sync.js +181 -8
  536. package/dist/session-titler.d.ts +26 -0
  537. package/dist/session-titler.js +41 -7
  538. package/dist/session-watch.d.ts +53 -1
  539. package/dist/session-watch.js +73 -9
  540. package/dist/sighup-idle.d.ts +30 -0
  541. package/dist/sighup-idle.js +12 -1
  542. package/dist/spec-fields.d.ts +91 -3
  543. package/dist/spec-fields.js +116 -4
  544. package/dist/task-cwd.d.ts +50 -0
  545. package/dist/task-cwd.js +68 -1
  546. package/dist/task-mcp.d.ts +47 -0
  547. package/dist/task-mcp.js +29 -0
  548. package/dist/task-settings.d.ts +175 -0
  549. package/dist/task-settings.js +226 -11
  550. package/dist/task-workflow.d.ts +67 -0
  551. package/dist/task-workflow.js +75 -8
  552. package/dist/tool-approval.d.ts +91 -0
  553. package/dist/tool-approval.js +215 -0
  554. package/dist/trace/artifacts.d.ts +9 -0
  555. package/dist/trace/artifacts.js +30 -5
  556. package/dist/trace/core-keyset-guard.d.ts +18 -0
  557. package/dist/trace/ledger-sink.d.ts +43 -0
  558. package/dist/trace/ledger-sink.js +64 -5
  559. package/dist/trace/project.d.ts +133 -0
  560. package/dist/trace/project.js +189 -2
  561. package/dist/trace/redact.d.ts +19 -0
  562. package/dist/trace/redact.js +66 -9
  563. package/dist/usage-analytics.d.ts +26 -0
  564. package/dist/usage-analytics.js +26 -3
  565. package/package.json +1 -1
@@ -1,24 +1,89 @@
1
+ /**
2
+ * hooks hook-runner — hooks 支持的 service 半场,阶段二。
3
+ *
4
+ * 契约 = `@sema-agent/registry-core/hooks`(0.1.51,30 事件全集、CC 2.1.198 逐字形状);runner 把
5
+ * `TaskRequest.settings.hooks` 里的 **command 类型** 条目翻成 core `TaskSpec.hooks`(types §hooks,
6
+ * 9 个 engine-owned 回调 core 1.254 已收满)的进程内回调。
7
+ *
8
+ * 阶段二范围(engine-owned 9 事件全点亮;加事件=纯实现不动契约):
9
+ * - 事件:PreToolUse/PostToolUse(阶段一,语义不动)+ UserPromptSubmit/Stop/StopFailure/
10
+ * PostToolUseFailure/PostToolBatch/PreCompact/PostCompact(阶段二)。matcher 按契约
11
+ * HOOK_EVENT_MATCHER_FIELD:null 事件(UserPromptSubmit/Stop/PostToolBatch)matcher 被忽略、
12
+ * 全条目都跑;StopFailure 对 error(AssistantTurnError 枚举)、PreCompact/PostCompact 对 trigger、
13
+ * PostToolUseFailure 对 tool_name 匹配。core 回调面是 observe-only 的事件(StopFailure/PostCompact)
14
+ * 的决策类输出记 `hook_result_unsupported`,warn 不吞。
15
+ * - shell-owned 事件(其余 21 个)的配置到达引擎=正常(壳自己跑,owner-single):记一条
16
+ * `hook_events_shell_owned` debug 可见,不告警;engine-owned 且未点亮的事件(阶段二后=空集,
17
+ * 机制保留防契约扩容)才记 `hook_events_not_fired` warn,绝不静默。
18
+ * - 条目类型:`command`(bash 族)+ `http`(阶段三a,见 {@link runHttpHook}——POST 输入 JSON、
19
+ * 2xx JSON body 走同一 SyncHookOutput 决策面)。`prompt`/`agent`(需模型调用设计:默认模型/用量
20
+ * 归属/catalog 门=阶段三b 挂账)与 `shell:"powershell"` 跳过(`hook_entries_skipped` 记账);
21
+ * `async` 支持为 fire-and-forget;`asyncRewake` = async + exit 2 时经 ctx.wake 唤醒模型(live
22
+ * TaskStream steer 注入;无活流/无管道则记账降级);`if` 条件=CC-exact permission-rule 模式
23
+ * (见 {@link hookIfMatches}:不匹配/评不出→跳过该条目记账,对照 CC 2.1.187/198 实测行为核验);
24
+ * `statusMessage` 是壳 UI 字段、服务端无面。
25
+ *
26
+ * 🔒 安全边界(design/107 class ② 能力授予):hook 命令跑在 **worker host**(server 进程环境),不在
27
+ * 沙箱——与 CC 语义一致(CC hooks 跑在用户机器上,哪怕工具在远端执行)。因此挂载点(main.ts)用
28
+ * **单用户闸** `requirePrincipal !== true`,与 MCP 注入/shellEnv/scheduler 同一姿势;多租户 lane
29
+ * 收到 hooks 配置只警告忽略(`task_hooks_ignored`),绝不执行。
30
+ *
31
+ * 🔒 整槽覆盖陷阱(core runtask `spec.hooks ?? deps.hooks`):task 级 hooks 一旦挂上会 shadow 部署级
32
+ * hooks(TOOL_TRACE 观测就挂在 deps.hooks)。{@link composeHooks} 把部署基线折进 task hooks——部署槽
33
+ * 先跑(观测在前,看到真实执行),用户槽后跑,结果按槽语义合并。
34
+ *
35
+ * CC exit-code 语义(与 CC 对齐):
36
+ * - exit 0:stdout 若是 JSON → SyncHookOutput(decision/hookSpecificOutput/continue);非 JSON → 忽略。
37
+ * - exit 2:阻断——PreToolUse → deny;UserPromptSubmit → block 提交;Stop → block 停止(继续干活);
38
+ * PreCompact → block 本次压缩(stderr 均作为模型/追溯可读原因);PostToolUse/PostToolUseFailure/
39
+ * PostToolBatch → 调用已落地无从阻断,stderr 作为 additionalContext 反馈给模型;observe-only 事件
40
+ * (StopFailure/PostCompact)无阻断面,按 hook 故障记账。
41
+ * - 其他 exit / 超时:非阻断,警告记录(hook 故障绝不误伤任务)。
42
+ */
1
43
  import { spawn } from "node:child_process";
2
44
  import { hostShell, resolveHostShell } from "../plugins/host-platform.js";
3
45
  import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, } from "@sema-agent/registry-core/hooks";
4
46
  import { redactSecrets } from "../trace/redact.js";
5
47
  import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTPUT_TOKENS } from "./cc-stop-prompt.js";
6
48
  import { renderBranchTranscript } from "./branch-transcript.js";
49
+ /** 服务侧防线上限(契约本身不设量纲;不设界=单请求可塞任意大配置/任意长命令)。超界=校验错(fail-loud)。 */
50
+ // S1([1870]):hook 的 shell 与 host lane 同源(core getShellConfig,bash 优先)。模块装载即预热
51
+ // 解析(fire-and-forget;未解析先到的 spawn 拿 sh 兜底=修前字节形,解析后恒 bash)。
7
52
  void resolveHostShell().catch(() => undefined);
8
53
  export const MAX_HOOK_ENTRIES_PER_EVENT = 32;
9
54
  export const MAX_HOOK_COMMAND_CHARS = 8_192;
55
+ /** 单条 hook 超时上限(秒;条目可自设 timeout,但被此值夹住——一个 86400 的 timeout 会挂死工具门)。 */
10
56
  export const MAX_HOOK_TIMEOUT_SECONDS = 600;
57
+ /** 一次 hook 事件(一次 pre/post 调用)所有同步条目的墙钟总预算(秒;32 条 × 单条 600s
58
+ * 最坏可串行卡住工具门 ~320min。到点后不再起后续条目,记账降级)。 */
11
59
  export const MAX_HOOK_EVENT_TOTAL_SECONDS = 120;
60
+ /** 一次事件的 matcher 组数上限(配置广度的另一维,防组数爆炸)。 */
12
61
  export const MAX_HOOK_MATCHER_GROUPS = 16;
62
+ /** http 条目的 header 条数 / allowedEnvVars 个数上限(构造放大面)。 */
13
63
  export const MAX_HOOK_HTTP_HEADERS = 32;
64
+ /** hook 进程 stdout/stderr 各自的采集上限(字节)——防输出洪泛打爆内存/日志。 */
14
65
  export const MAX_HOOK_OUTPUT_BYTES = 1024 * 1024;
66
+ /** 喂给模型的 deny 原因/additionalContext 的单条长度上限(字符)。 */
15
67
  const MAX_HOOK_FEEDBACK_CHARS = 4_096;
68
+ /** PostToolUse payload 里 tool_response 文本投影的上限(字符)——工具结果可能是 MB 级。 */
16
69
  const MAX_TOOL_RESPONSE_CHARS = 32_768;
70
+ /** stdin payload 里 tool_input JSON 投影的上限(字符;巨大 tool_input 经 JSON.stringify 造成
71
+ * 内存/阻塞放大——超限替换为截断标记)。 */
17
72
  const MAX_TOOL_INPUT_CHARS = 262_144;
73
+ /**
74
+ * command hook 不再继承完整 `process.env`——worker 进程环境里有模型/DB/service-auth 令牌,
75
+ * 全量透传等于把它们交给任务侧 hook 命令。改成基础运行时 env 的 allowlist(PATH/HOME/SHELL/终端/locale)+
76
+ * CC 约定注入(CLAUDE_PROJECT_DIR)。用户自己要给 hook 的变量走 settings.env(shellEnv,同样单用户闸),
77
+ * 不靠继承服务进程密钥。
78
+ */
18
79
  const HOOK_ENV_ALLOWLIST = [
19
80
  "PATH", "HOME", "SHELL", "USER", "LOGNAME", "TERM", "TZ",
20
81
  "LANG", "LC_ALL", "LC_CTYPE", "LC_MESSAGES", "TMPDIR",
21
82
  ];
83
+ /** hook 可见的 env = 净化基础 allowlist + CLAUDE_PROJECT_DIR + 请求自带的 `settings.env`(shellEnv,
84
+ * 同一单用户闸)——上面注释承诺的"用户变量走 settings.env"通道在这里真兑现(阶段三补齐:
85
+ * 之前 hookEnv 没接 shellEnv,承诺是空头的)。shellEnv 后铺(用户显式给的赢基础值);服务进程密钥
86
+ * 永远不在这张表里。http 条目的 $NAME 插值也从这张表取(见 {@link runHttpHook})。 */
22
87
  function hookEnv(ctx) {
23
88
  const env = { CLAUDE_PROJECT_DIR: ctx.cwd };
24
89
  for (const k of HOOK_ENV_ALLOWLIST) {
@@ -28,6 +93,9 @@ function hookEnv(ctx) {
28
93
  }
29
94
  return { ...env, ...ctx.shellEnv };
30
95
  }
96
+ /** 阶段二点亮的事件 = engine-owned 9 事件全集。与契约 ENGINE_OWNED_HOOK_EVENTS 今天相等但故意独立
97
+ * 声明:契约扩容(新增 engine-owned 事件)时 `hook_events_not_fired` 机制自动把缺口暴露出来,而不是
98
+ * 静默装作点亮。shell-owned 事件不进此表(壳自己跑,owner-single)。 */
31
99
  const LIT_EVENTS = [
32
100
  "PreToolUse",
33
101
  "PostToolUse",
@@ -39,6 +107,10 @@ const LIT_EVENTS = [
39
107
  "PreCompact",
40
108
  "PostCompact",
41
109
  ];
110
+ /** core BrainErrorCode(auth/rate_limit/invalid_request/server/network/http,brain/errors 单一来源)→
111
+ * 契约 AssistantTurnError(CC SDKAssistantMessageError 枚举)。network/http 在 CC 枚举里没有对应
112
+ * 类别 → "unknown"(不妄断编造类别;原始人话错误走 error_details)。StopFailure 的 matcher 对映射
113
+ * 【后】的枚举值匹配(契约 HOOK_EVENT_MATCHER_FIELD.StopFailure = "error" = payload 字段)。 */
42
114
  const BRAIN_TO_TURN_ERROR = {
43
115
  auth: "authentication_failed",
44
116
  rate_limit: "rate_limit",
@@ -49,6 +121,10 @@ const BRAIN_TO_TURN_ERROR = {
49
121
  function toAssistantTurnError(errorKind) {
50
122
  return (errorKind !== undefined ? BRAIN_TO_TURN_ERROR[errorKind] : undefined) ?? "unknown";
51
123
  }
124
+ /**
125
+ * 把 wire 上的 untrusted `settings.hooks` 校验成契约 {@link HooksConfig} + 服务侧上限。
126
+ * 错误返回 error 字符串而不 throw——submit 路径 400 fail-loud、resume 防御路径 warn-drop,由调用方选。
127
+ */
52
128
  export function parseHooksConfig(raw) {
53
129
  const parsed = HooksConfig.safeParse(raw);
54
130
  if (!parsed.success) {
@@ -60,6 +136,7 @@ export function parseHooksConfig(raw) {
60
136
  if (!groups)
61
137
  continue;
62
138
  if (groups.length > MAX_HOOK_MATCHER_GROUPS) {
139
+ // 广度维:组数不设界=条目 cap 可被"多组少条"绕开一半(每组重复扫 matcher)。
63
140
  return { error: `settings.hooks.${event}: ${groups.length} matcher groups exceeds cap ${MAX_HOOK_MATCHER_GROUPS}` };
64
141
  }
65
142
  let entries = 0;
@@ -70,6 +147,7 @@ export function parseHooksConfig(raw) {
70
147
  return { error: `settings.hooks.${event}: command exceeds ${MAX_HOOK_COMMAND_CHARS} chars` };
71
148
  }
72
149
  if (entry.type === "http") {
150
+ // headers/allowedEnvVars 不设界=插值与 fetch header 构造前的内存放大面。
73
151
  if (entry.url.length > MAX_HOOK_COMMAND_CHARS) {
74
152
  return { error: `settings.hooks.${event}: url exceeds ${MAX_HOOK_COMMAND_CHARS} chars` };
75
153
  }
@@ -93,6 +171,8 @@ export function parseHooksConfig(raw) {
93
171
  }
94
172
  return { config };
95
173
  }
174
+ /** CC matcher 语义:精确名、`a|b` 交替、或 JS 正则(锚定全匹配,防 "Bash" 误配 "BashOutput");
175
+ * 空/`*`/缺省匹配一切;非法正则 → 该条不匹配(fail-closed 到"不跑",由调用方记账一次)。 */
96
176
  export function hookMatcherMatches(matcher, value) {
97
177
  if (matcher === undefined || matcher === "" || matcher === "*")
98
178
  return true;
@@ -105,13 +185,21 @@ export function hookMatcherMatches(matcher, value) {
105
185
  return "invalid";
106
186
  }
107
187
  }
188
+ /** `if` 条目条件(CC-exact,对照 CC 2.1.187/198 实测行为核验的真语义,两版一致):
189
+ * 值 = permission-rule 模式(`ToolName` 或 `ToolName(arg-模式)`),【不是】表达式语言——按当前工具调用
190
+ * 求值:名部走 permission matcher(交替/正则同 matcher 字段),括号内 arg-模式(`*` 通配)匹配工具输入的
191
+ * 主字符串(Bash=command,文件工具=file_path/path)。CC 行为逐条目:不匹配 → 跳过该条目(记日志);
192
+ * 非工具事件(payload 无 tool_name)→ "cannot be evaluated" 跳过;解析失败 → 跳过。返回:
193
+ * true=跑 / "not_matched" / "unevaluable"(调用方分别记账;跳过永不静默)。 */
108
194
  export function hookIfMatches(cond, payload) {
195
+ // glob→`.*` 拼正则有回溯放大面(长模式×多星号×长主字符串)——模式长度与星号数设上限,
196
+ // 超限=unevaluable(跳过记账;正常 permission-rule 远在限内)。
109
197
  if (cond.length > 500 || (cond.match(/\*/g)?.length ?? 0) > 16)
110
198
  return "unevaluable";
111
199
  const p = (payload ?? {});
112
200
  const toolName = typeof p.tool_name === "string" ? p.tool_name : undefined;
113
201
  if (!toolName)
114
- return "unevaluable";
202
+ return "unevaluable"; // CC:非工具事件上 if 无法求值 → 跳过(非静默,调用方记账)
115
203
  const m = /^([^(]+?)(?:\((.*)\))?$/s.exec(cond.trim());
116
204
  if (!m)
117
205
  return "unevaluable";
@@ -122,12 +210,13 @@ export function hookIfMatches(cond, payload) {
122
210
  return "not_matched";
123
211
  const spec = m[2];
124
212
  if (spec === undefined || spec === "*" || spec === "")
125
- return true;
213
+ return true; // 纯名 / Tool(*) = 名中即中
126
214
  const input = p.tool_input;
127
215
  const primary = input && (typeof input.command === "string" ? input.command : typeof input.file_path === "string" ? input.file_path : typeof input.path === "string" ? input.path : undefined);
128
216
  if (primary === undefined)
129
- return "unevaluable";
217
+ return "unevaluable"; // 该工具无主字符串可配(CC 同样评不出来)
130
218
  try {
219
+ // glob(`*`)→ 锚定正则;其余字符按字面转义(permission specifier 不是裸正则)。
131
220
  const re = new RegExp(`^${spec.split("*").map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`);
132
221
  return re.test(primary) ? true : "not_matched";
133
222
  }
@@ -135,21 +224,27 @@ export function hookIfMatches(cond, payload) {
135
224
  return "unevaluable";
136
225
  }
137
226
  }
227
+ /** 阶段三b:LLM 条目(prompt+agent 合计)每事件上限——config 滥写不至于把一个工具门拖进 N 次模型调用。 */
138
228
  const MAX_LLM_ENTRIES_PER_EVENT = 4;
229
+ /** prompt 条目默认超时(一次判定调用;entry.timeout 覆盖,MAX_HOOK_TIMEOUT_SECONDS 仍是硬顶)。 */
139
230
  const DEFAULT_PROMPT_HOOK_TIMEOUT_SECONDS = 30;
231
+ /** agent 条目默认超时(读-only 子代理跑到收敛)。 */
140
232
  const DEFAULT_AGENT_HOOK_TIMEOUT_SECONDS = 120;
141
233
  function clip(s, n) {
142
234
  return s.length > n ? `${s.slice(0, n)}…(${s.length})` : s;
143
235
  }
236
+ /** 跑一条 command hook:sh -c,stdin=payload JSON(CC-verbatim),超时 SIGKILL,输出各限 1MiB。 */
144
237
  function runCommandHook(entry, payload, ctx) {
145
238
  return new Promise((resolve) => {
146
239
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
147
240
  let child;
148
241
  try {
149
- const sh = hostShell();
242
+ // detached=true 建独立进程组:超时击杀用负 pid 连孙进程一起收(否则 SIGKILL 只杀 sh,一个
243
+ // `sleep 30` 孙进程抓着继承的 stdout 管道,`close` 事件要等它退出——单测实抓的挂死)。
244
+ const sh = hostShell(); // S1:不再硬编码 /bin/sh(Debian=dash 静默错字节),host 同源 bash
150
245
  child = spawn(sh.shell, [...sh.args, entry.command], {
151
246
  cwd: ctx.cwd,
152
- env: hookEnv(ctx),
247
+ env: hookEnv(ctx), // allowlist+shellEnv,不透传服务进程密钥
153
248
  stdio: ["pipe", "pipe", "pipe"],
154
249
  detached: true,
155
250
  });
@@ -173,14 +268,14 @@ function runCommandHook(entry, payload, ctx) {
173
268
  timedOut = true;
174
269
  try {
175
270
  if (child.pid)
176
- process.kill(-child.pid, "SIGKILL");
271
+ process.kill(-child.pid, "SIGKILL"); // 整组击杀(见 spawn 注释)
177
272
  else
178
273
  child.kill("SIGKILL");
179
274
  }
180
275
  catch {
181
276
  child.kill("SIGKILL");
182
277
  }
183
- settle(null);
278
+ settle(null); // 超时=非阻断故障,立即返回,不等孙进程放开管道
184
279
  }, timeoutMs);
185
280
  child.stdout?.on("data", (d) => {
186
281
  if (stdout.length < MAX_HOOK_OUTPUT_BYTES)
@@ -198,20 +293,29 @@ function runCommandHook(entry, payload, ctx) {
198
293
  }
199
294
  });
200
295
  child.on("close", (code) => settle(code));
296
+ // stdin=hook 输入 JSON(CC 语义)。EPIPE(hook 不读 stdin 就退出)吞掉——不是错误。
297
+ // 巨大 tool_input 经 boundedToolInputPayload 截断防内存放大。
201
298
  child.stdin?.on("error", () => { });
202
299
  child.stdin?.end(`${JSON.stringify(boundedToolInputPayload(payload))}\n`);
203
300
  });
204
301
  }
302
+ /** async 条目:fire-and-forget(不等待、不消费输出、不参与决策)。仍加 detached 进程组 +
303
+ * timeout 整组击杀——否则一个每次工具调用都 spawn 的 async hook 会在长任务里无限堆积不退出的后台进程。
304
+ *
305
+ * asyncRewake(契约:Background + wake the model on exit 2,implies async):与纯 async 的唯一差别=
306
+ * 捕获 stderr(有界)+ close 时 code===2 → ctx.wake(stderr)——CC 的 exit-2-语义在同步条目是"stderr 是给
307
+ * 模型的阻断理由",搬到后台即"stderr 是给模型的唤醒消息"。投递走部署组装的 wake 管道(live TaskStream
308
+ * steer,下个 turn 边界注入);没有活流(任务已结束/挂起)或 wake 缺席 ⇒ warn 记账不重试。 */
205
309
  function fireAsyncCommandHook(entry, payload, ctx, event) {
206
310
  try {
207
311
  const rewake = entry.asyncRewake === true;
208
312
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
209
- const sh = hostShell();
313
+ const sh = hostShell(); // S1:同上,async hook 腿同源
210
314
  const child = spawn(sh.shell, [...sh.args, entry.command], {
211
315
  cwd: ctx.cwd,
212
- env: hookEnv(ctx),
316
+ env: hookEnv(ctx), // allowlist+shellEnv,不透传服务进程密钥
213
317
  stdio: ["pipe", "ignore", rewake ? "pipe" : "ignore"],
214
- detached: true,
318
+ detached: true, // 进程组隔离,超时可整组收
215
319
  });
216
320
  const timer = setTimeout(() => {
217
321
  try {
@@ -231,19 +335,22 @@ function fireAsyncCommandHook(entry, payload, ctx, event) {
231
335
  if (stderr.length < MAX_HOOK_OUTPUT_BYTES)
232
336
  stderr += d.toString("utf8").slice(0, MAX_HOOK_OUTPUT_BYTES - stderr.length);
233
337
  });
338
+ // pipe 的 socket 句柄会 hold 事件循环——unref 让慢 hook 不拖进程关停(数据事件照收,
339
+ // 超时路径的进程组 SIGKILL 会触发 close 收尾)。
234
340
  child.stderr?.unref?.();
235
341
  }
236
342
  child.on("error", () => clearTimeout(timer));
237
343
  child.on("close", (code) => {
238
344
  clearTimeout(timer);
239
345
  if (!rewake || code !== 2)
240
- return;
346
+ return; // 契约:只有 exit 2 唤醒
241
347
  const text = stderr.trim();
242
348
  if (!ctx.wake) {
243
349
  ctx.logger.warn("hook_async_rewake_dropped", { event, reason: "no_wake_pipeline" });
244
350
  return;
245
351
  }
246
352
  if (!text) {
353
+ // exit 2 但 stderr 空:没内容可注入——记账(与同步条目 exit-2-无-stderr 的空理由同类,不造消息)。
247
354
  ctx.logger.warn("hook_async_rewake_dropped", { event, reason: "empty_stderr" });
248
355
  return;
249
356
  }
@@ -259,6 +366,7 @@ function fireAsyncCommandHook(entry, payload, ctx, event) {
259
366
  child.unref();
260
367
  }
261
368
  catch {
369
+ /* fire-and-forget:spawn 失败不影响任务 */
262
370
  }
263
371
  }
264
372
  async function runLlmHook(entry, payload, ctx, extra) {
@@ -269,7 +377,12 @@ async function runLlmHook(entry, payload, ctx, extra) {
269
377
  const timeoutMs = Math.min(entry.timeout ?? defSec, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
270
378
  const args = clip(JSON.stringify(boundedToolInputPayload(payload)), MAX_TOOL_INPUT_CHARS);
271
379
  const substituted = entry.prompt.split("$ARGUMENTS").join(args);
380
+ // Stop×prompt(CC 形):会话 transcript 在前、被包装过的条件在后 —— 顺序是承重的,
381
+ // 因为 CC 的包装句逐字写着 "Based on the conversation transcript **above**"。
382
+ // CC 形:transcript 在前,条件被 CC 的包装句包起来("Based on the conversation transcript **above**…")。
383
+ // 顺序是承重的 —— 包装句逐字预设会话在上文,放反了那句话本身就是在骗模型。
272
384
  const prompt = extra ? `${extra.transcript}\n\n---\n\n${wrapCondition("Stop", substituted)}` : substituted;
385
+ // 载体外再包一层硬顶:契约说载体自己兜超时,但一个部署组装 bug 不该能挂死工具门(纵深)。
273
386
  const hardTop = new Promise((r) => setTimeout(() => r({ ok: false, error: "__hook_llm_hard_timeout__" }), timeoutMs + 5_000).unref?.());
274
387
  const invoke = () => Promise.race([
275
388
  call({
@@ -281,9 +394,14 @@ async function runLlmHook(entry, payload, ctx, extra) {
281
394
  hardTop,
282
395
  ]).catch((e) => ({ ok: false, error: String(e) }));
283
396
  let res = await invoke();
397
+ // 🔴 **"一个字都没吐出来"重试一次**(仅 CC 评估者路)。这个失败形的后果是**判词被丢弃 ⇒ fail-open**
398
+ // (该拦没拦),而它是**瞬态**的(推理档模型偶尔把额度用在 thinking 上)。
399
+ // ⚠️ 只重试**无内容**,不重试"有内容但读不懂" —— 后者重试一次多半还是读不懂,而且那一格按设计就该放行。
284
400
  if (extra && !res.ok && res.error.includes("returned no content")) {
285
401
  ctx.logger.warn("hook_llm_no_content_retry", { event: "Stop" });
286
402
  res = await invoke();
403
+ // 重试之后**仍然**没有内容 ⇒ 这一轮的守卫确实没能评估。发观测帧(纯 observe,不改变运行)——
404
+ // 方向仍是 fail-open,但用户/壳侧要能知道「这轮没看住」,否则那个放行与「已达成」无法区分。
287
405
  if (!res.ok && res.error.includes("returned no content")) {
288
406
  ctx.onHookNotice?.({ kind: "hook_decision_unavailable", event: "Stop", reason: "no_content", detail: "carrier returned no content (after one retry)" });
289
407
  }
@@ -298,9 +416,14 @@ async function runLlmHook(entry, payload, ctx, extra) {
298
416
  async function runHttpHook(entry, payload, ctx) {
299
417
  const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
300
418
  const allowed = new Set(entry.allowedEnvVars ?? []);
419
+ // 插值源=hookEnv(净化 allowlist + shellEnv),【不是】裸 process.env——否则任务
420
+ // 配置可用 allowedEnvVars 点名服务密钥(MODEL_GATEWAY_KEY 等)插进 header 发往任意 URL。用户要给
421
+ // webhook 的 token 走同请求 settings.env(shellEnv),与 command hook 的 env 姿势同一单源。
301
422
  const envSource = hookEnv(ctx);
302
423
  const headers = { "content-type": "application/json" };
303
424
  for (const [k, v] of Object.entries(entry.headers ?? {})) {
425
+ // $NAME 插值:只认 allowedEnvVars 列出【且】在 hookEnv 表里的名字;其余原样保留(不是插值点,
426
+ // fail-closed 到字面)。
304
427
  headers[k] = v.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (whole, name) => {
305
428
  if (!allowed.has(name))
306
429
  return whole;
@@ -318,6 +441,8 @@ async function runHttpHook(entry, payload, ctx) {
318
441
  signal: ac.signal,
319
442
  redirect: "error",
320
443
  });
444
+ // body 有界读取(不 res.text() 一口吞——恶意/故障端点可回无限流)。按【字节】
445
+ // 计预算并先截 Uint8Array 再 decode(字符计数挡不住单个巨 chunk 的先行分配),收尾 flush decoder。
321
446
  let body = "";
322
447
  const reader = res.body?.getReader();
323
448
  if (reader) {
@@ -338,6 +463,9 @@ async function runHttpHook(entry, payload, ctx) {
338
463
  await reader.cancel().catch(() => undefined);
339
464
  }
340
465
  if (res.ok) {
466
+ // CC-exact 定谳:http hook 是硬 JSON 契约(≠ command 的 plainText 优雅降级)。
467
+ // - 空 body → 特判 `{}` 成功(无决策,干净放行);
468
+ // - 非空且非 JSON → non-blocking error 记账(不吞成静默通过),stdout 置空让折叠无决策。
341
469
  const trimmed = body.trim();
342
470
  if (trimmed === "")
343
471
  return { code: 0, stdout: "{}", stderr: "", timedOut: false };
@@ -347,6 +475,7 @@ async function runHttpHook(entry, payload, ctx) {
347
475
  }
348
476
  return { code: 0, stdout: body, stderr: "", timedOut: false };
349
477
  }
478
+ // 非 2xx 不 parse body,`HTTP ${status} from ${url}` non-blocking(exit-2 阻断语义不撞)。
350
479
  return { code: res.status, stdout: "", stderr: body, timedOut: false };
351
480
  }
352
481
  catch (e) {
@@ -358,6 +487,9 @@ async function runHttpHook(entry, payload, ctx) {
358
487
  clearTimeout(timer);
359
488
  }
360
489
  }
490
+ /** 输入截断的单值形式:一个 tool_input 值若 stringify 超上限/不可序列化,换成截断/占位标记。
491
+ * 返回原引用=没动(调用方以此保住常态零拷贝)。PostToolBatch 的 tool_calls[].tool_input 逐条走这里
492
+ * (顶层 {@link boundedToolInputPayload} 只看 payload.tool_input,批量形状会绕过它)。 */
361
493
  function boundedInputValue(v) {
362
494
  let s;
363
495
  try {
@@ -370,6 +502,8 @@ function boundedInputValue(v) {
370
502
  return v;
371
503
  return { __truncated__: true, chars: s.length };
372
504
  }
505
+ /** stdin payload 若 stringify 超上限,把 tool_input 替换成截断标记(防内存/阻塞放大)。
506
+ * 只在真的超限时重建对象——常态零拷贝。 */
373
507
  function boundedToolInputPayload(payload) {
374
508
  if (!payload || typeof payload !== "object")
375
509
  return payload;
@@ -379,6 +513,7 @@ function boundedToolInputPayload(payload) {
379
513
  const bounded = boundedInputValue(p.tool_input);
380
514
  return bounded === p.tool_input ? payload : { ...p, tool_input: bounded };
381
515
  }
516
+ /** stdout 的 SyncHookOutput 宽松读取(exit 0 且可解析才有意义;形状错就当没有——CC 同姿势)。 */
382
517
  function parseHookStdout(stdout) {
383
518
  const t = stdout.trim();
384
519
  if (!t.startsWith("{"))
@@ -391,9 +526,18 @@ function parseHookStdout(stdout) {
391
526
  return undefined;
392
527
  }
393
528
  }
394
- async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceFired, deadlineAt, keyBase, llmExtra) {
529
+ /** 一次事件里跑一组匹配条目的公共骨架:once 去重、if 忽略记账、类型/shell 跳过记账、async 旁路。
530
+ * 返回每条【同步 command 条目】的运行结果 + 该条目引用(供事件专属的语义映射)。 */
531
+ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceFired,
532
+ /** 深度维:本次事件的墙钟截止(epoch ms)——32 条 × 单条 600s 最坏串行卡工具门 ~320min;
533
+ * 到点后不再起【同步】条目(async 旁路不占墙钟照常),记账降级。 */
534
+ deadlineAt,
535
+ /** once 去重键的组维前缀——PreToolUse 逐组合成单组数组调用时必须传真实组号,否则跨组键碰撞。 */
536
+ keyBase,
537
+ /** Stop×prompt 的额外输入(CC 系统提示 + 会话 transcript)。缺席 ⇒ prompt 条目按此前逐位不变。 */
538
+ llmExtra) {
395
539
  const results = [];
396
- let llmEntries = 0;
540
+ let llmEntries = 0; // 阶段三b:prompt+agent 合计每事件 cap(config 滥写不至于一个门 N 次模型调用)
397
541
  let gi = -1;
398
542
  for (const group of groups) {
399
543
  gi++;
@@ -411,10 +555,12 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
411
555
  if (entry.once && onceFired.has(onceKey))
412
556
  continue;
413
557
  if (entry.type !== "command" && entry.type !== "http" && entry.type !== "prompt" && entry.type !== "agent") {
558
+ // registry-core 未来新增条目类型的前向降级:跳过记账可见,绝不静默。
414
559
  ctx.logger.warn("hook_entries_skipped", { event, type: entry.type, reason: "type_not_supported" });
415
560
  continue;
416
561
  }
417
562
  if (entry.type === "prompt" || entry.type === "agent") {
563
+ // 阶段三b:载体缺席(部署没组装模型面)/每事件 LLM cap——都跳过记账可见,非阻断。
418
564
  const carrier = entry.type === "prompt" ? ctx.hookLlm : ctx.hookAgent;
419
565
  if (!carrier) {
420
566
  ctx.logger.warn("hook_entries_skipped", { event, type: entry.type, reason: "llm_carrier_unavailable" });
@@ -430,6 +576,8 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
430
576
  continue;
431
577
  }
432
578
  if (entry.if !== undefined) {
579
+ // CC-exact(对照 CC 2.1.187/198 实测行为核验,两版一致):if=permission-rule 模式,按当前工具求值;不匹配/评不出
580
+ // → 跳过【该条目】并记日志(CC 行为;取代早期"语义未定忽略照跑"的保守占位)。
433
581
  const iv = hookIfMatches(entry.if, payload);
434
582
  if (iv !== true) {
435
583
  ctx.logger.warn("hook_entries_skipped", { event, type: entry.type, reason: iv === "unevaluable" ? "if_unevaluable" : "if_not_matched", if: clip(entry.if, 200) });
@@ -437,15 +585,20 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
437
585
  }
438
586
  }
439
587
  if (entry.type === "command" && (entry.async || entry.asyncRewake)) {
588
+ // asyncRewake:wake 管道就位后不再降级——exit 2 唤醒在 fireAsyncCommandHook 内兑现(无管道时它自记账)。
440
589
  if (entry.once)
441
590
  onceFired.add(onceKey);
442
591
  fireAsyncCommandHook(entry, payload, ctx, event);
443
592
  continue;
444
593
  }
445
594
  if (Date.now() > deadlineAt) {
595
+ // 事件墙钟预算耗尽——剩余同步条目不再起,警告记账(与超时同姿势:非阻断降级)。
446
596
  ctx.logger.warn("hook_event_deadline_exceeded", { event, budgetSec: MAX_HOOK_EVENT_TOTAL_SECONDS });
447
597
  return results;
448
598
  }
599
+ // once 标记必须在 deadline 检查【后】——预算耗尽被跳过的 once 条目没真跑过,
600
+ // 标了=下次事件永久哑火(可能是安全阻断钩子)。标在 spawn 尝试前:spawn 失败也算消耗
601
+ // (once=一次尝试;失败已 warn 记账,重试语义交给用户重发配置)。
449
602
  if (entry.once)
450
603
  onceFired.add(onceKey);
451
604
  if (entry.type === "prompt" || entry.type === "agent")
@@ -459,28 +612,47 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
459
612
  }
460
613
  if (run.timedOut) {
461
614
  ctx.logger.warn("hook_command_timeout", { event, timeoutSec: Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) });
462
- continue;
615
+ continue; // CC:超时=非阻断故障
463
616
  }
464
617
  results.push({ entry, run });
465
618
  }
466
619
  }
467
620
  return results;
468
621
  }
622
+ /** payload 公共字段(CC BaseHookInput;transcript_path 服务端无壳 jsonl → 空串,契约只定字段名)。 */
469
623
  function basePayload(ctx) {
470
624
  return {
471
625
  session_id: ctx.sessionId,
626
+ // 🔴 **恒空串,而且它有真实后果**(cli 黑板 [1730] 的 live 取证):CC 用这个键让 **command 形**钩子
627
+ // (一个能读磁盘的脚本)自己去读会话 JSONL。本服务是**无状态副本、hook 跑在服务端**,这里
628
+ // **没有那样一个本地文件** ⇒ 给不出真路径。
629
+ // 后果不是"少个字段":用 `type:"prompt"` 写「基于会话状态判断」的钩子,其载体(server 自己发起的
630
+ // 模型调用)对本会话**完全是瞎的** —— cli 真跑实录:模型回「I don't have direct access to your
631
+ // file system…」这类散文 ⇒ `parseHookStdout` 拿不到 decision ⇒ **一次都不拦**。整类 prompt 条件钩子
632
+ // 在这个输入面下**结构上不可能工作**。
633
+ // ⚠️ 正确的修**不是**把路径填上(prompt 载体要的是**内容**不是路径),而是给载体喂会话内容;
634
+ // 那是行为面新增(送多少 / 脱敏口径 / token 记账 / 旋钮 / 喂不到时诚实降级),按本仓规矩先答三问,
635
+ // 不在这里顺手改。**键本身保留**是为了 payload 形状与 CC 对位(CC 恒发这个键)——
636
+ // 值为空是一处**已登记的**偏离,不是遗漏。见 test/hook-transcript-path-gap.test.ts。
472
637
  transcript_path: "",
473
638
  cwd: ctx.cwd,
474
639
  ...(ctx.permissionMode ? { permission_mode: ctx.permissionMode } : {}),
475
640
  };
476
641
  }
642
+ /** matcher=null 事件(契约 HOOK_EVENT_MATCHER_FIELD 为 null:UserPromptSubmit/Stop/PostToolBatch)的
643
+ * 契约语义 = matcher 被忽略、每个条目都跑——把组上的 matcher 抹掉再进公共骨架(否则拿空串当
644
+ * matchValue 会让带 matcher 的条目 fail-closed 漏跑,违背"忽略"语义)。 */
477
645
  function ignoreMatchers(groups) {
478
646
  return groups.map((g) => (g.matcher === undefined ? g : { ...g, matcher: undefined }));
479
647
  }
648
+ /** observe-only 事件(StopFailure/PostCompact——core 回调面返回 void,契约也没定这两个事件的输出面)
649
+ * 的公共收尾:命令照常跑(通知型价值在副作用),非零 exit 记账;stdout 里的决策类输出无处落 →
650
+ * `hook_result_unsupported` warn 不吞(别让用户以为 block/decision 生效了)。 */
480
651
  async function runObserveOnlyEvent(event, groups, matchValue, payload, ctx, onceFired) {
481
652
  const singles = await runMatchingEntries(event, groups, matchValue, payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
482
653
  for (const { run } of singles) {
483
654
  if (run.code !== 0) {
655
+ // 含 exit 2:这两个事件没有 CC 阻断面,非零一律按 hook 故障记账(非阻断原则)。
484
656
  ctx.logger.warn("hook_command_failed", { event, code: run.code, stderr: clip(run.stderr, 300) });
485
657
  continue;
486
658
  }
@@ -494,12 +666,19 @@ async function runObserveOnlyEvent(event, groups, matchValue, payload, ctx, once
494
666
  }
495
667
  }
496
668
  }
669
+ /**
670
+ * 把校验过的 {@link HooksConfig} 翻成 core `Hooks` 回调(阶段二:engine-owned 9 事件全点亮)。
671
+ * 没有可点亮的条目 → undefined(spec 不挂 hooks,deps 路径原样)。
672
+ */
497
673
  export function createTaskHooks(config, ctx) {
498
674
  const configured = Object.keys(config).filter((e) => (config[e]?.length ?? 0) > 0);
675
+ // engine-owned 且本 runner 未点亮的事件(阶段二后=空集,契约扩容时自动复活)才是缺口 → warn。
499
676
  const dark = configured.filter((e) => HOOK_EVENT_OWNER[e] === "engine" && !LIT_EVENTS.includes(e));
500
677
  if (dark.length > 0) {
501
678
  ctx.logger.warn("hook_events_not_fired", { events: dark, phase: 2 });
502
679
  }
680
+ // shell-owned 事件的配置到达引擎=正常(owner-single:壳自己跑,引擎绝不 double-fire)——
681
+ // debug 可见记账,不告警(warn 会把合法配置读成缺口)。
503
682
  const shellOwned = configured.filter((e) => HOOK_EVENT_OWNER[e] === "shell");
504
683
  if (shellOwned.length > 0) {
505
684
  ctx.logger.debug("hook_events_shell_owned", { events: shellOwned });
@@ -528,7 +707,9 @@ export function createTaskHooks(config, ctx) {
528
707
  let rewritten = false;
529
708
  let asked;
530
709
  const contexts = [];
531
- const deadlineAt = Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000;
710
+ const deadlineAt = Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000; // 跨全部组的事件级预算
711
+ // 逐条串行(deny 立即短路;updatedInput 串进后续条目 payload——确定性优先,CC 的并行聚合语义
712
+ // 在 deny/ask/allow 折叠上与此等价)。
532
713
  let gi = -1;
533
714
  for (const group of pre) {
534
715
  gi++;
@@ -549,12 +730,13 @@ export function createTaskHooks(config, ctx) {
549
730
  const singles = await runMatchingEntries("PreToolUse", [{ ...group, matcher: undefined }], toolName, payload, ctx, onceFired, deadlineAt, `PreToolUse:${gi}`);
550
731
  for (const { run } of singles) {
551
732
  if (run.code === 2) {
733
+ // CC:exit 2 = 阻断,stderr 喂给模型
552
734
  const reason = clip(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreToolUse hook";
553
735
  return { action: "deny", message: reason, ...(contexts.length ? { additionalContext: contexts.join("\n") } : {}) };
554
736
  }
555
737
  if (run.code !== 0) {
556
738
  ctx.logger.warn("hook_command_failed", { event: "PreToolUse", code: run.code, stderr: clip(run.stderr, 300) });
557
- continue;
739
+ continue; // 非阻断
558
740
  }
559
741
  const out = parseHookStdout(run.stdout);
560
742
  if (!out)
@@ -563,6 +745,7 @@ export function createTaskHooks(config, ctx) {
563
745
  ctx.logger.warn("hook_system_message", { event: "PreToolUse", message: clip(out.systemMessage, 500) });
564
746
  if (out.continue === false) {
565
747
  const reason = clip(typeof out.stopReason === "string" ? out.stopReason : "hook requested stop", MAX_HOOK_FEEDBACK_CHARS);
748
+ // core 的 preToolUse seam 无"终止整个 run"能力(阶段一诚实近似:deny 本次调用)。
566
749
  return { action: "deny", message: reason, ...(contexts.length ? { additionalContext: contexts.join("\n") } : {}) };
567
750
  }
568
751
  if (out.decision === "block") {
@@ -592,6 +775,9 @@ export function createTaskHooks(config, ctx) {
592
775
  }
593
776
  }
594
777
  if (asked !== undefined) {
778
+ // ask 不短路(后续条目可能 deny——deny > ask 已由上面的立即返回保证),折叠成最终 ask。
779
+ // ask 也携带改写(PermissionResult 的 ask 变体同样接受 updatedInput)——否则
780
+ // "改写 + 请示"组合在 approve 后执行的是【原始】args,钩子的 redact/clamp 被静默丢弃。
595
781
  return {
596
782
  action: "ask",
597
783
  message: asked,
@@ -606,7 +792,7 @@ export function createTaskHooks(config, ctx) {
606
792
  ...(contexts.length ? { additionalContext: contexts.join("\n") } : {}),
607
793
  };
608
794
  }
609
- return undefined;
795
+ return undefined; // 无意见(≠ allow 短路;core 门照常裁决)
610
796
  };
611
797
  hooks.preToolUse = preToolUseHook;
612
798
  }
@@ -621,12 +807,14 @@ export function createTaskHooks(config, ctx) {
621
807
  hook_event_name: "PostToolUse",
622
808
  tool_name: toolName,
623
809
  tool_input: input,
810
+ // 有界文本投影(契约字段是 unknown;整个 details 图可能 MB 级且含不可序列化句柄)。
624
811
  tool_response: { isError: output.isError, text: clip(responseText, MAX_TOOL_RESPONSE_CHARS) },
625
812
  tool_use_id: tctx.toolCallId,
626
813
  };
627
814
  const singles = await runMatchingEntries("PostToolUse", post, toolName, payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
628
815
  for (const { run } of singles) {
629
816
  if (run.code === 2) {
817
+ // CC:PostToolUse exit 2 = stderr 作为反馈喂给模型(工具已执行,无从阻断)
630
818
  const fb = clip(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS);
631
819
  if (fb)
632
820
  contexts.push(fb);
@@ -642,6 +830,7 @@ export function createTaskHooks(config, ctx) {
642
830
  if (typeof out.systemMessage === "string")
643
831
  ctx.logger.warn("hook_system_message", { event: "PostToolUse", message: clip(out.systemMessage, 500) });
644
832
  if (out.continue === false) {
833
+ // core postToolUse seam 无 run 终止能力;记账可见(阶段一缺口),不吞。
645
834
  ctx.logger.warn("hook_continue_false_unsupported", { event: "PostToolUse" });
646
835
  }
647
836
  if (out.decision === "block" && typeof out.reason === "string")
@@ -652,6 +841,8 @@ export function createTaskHooks(config, ctx) {
652
841
  if (typeof h.additionalContext === "string")
653
842
  contexts.push(clip(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
654
843
  if (h.updatedMCPToolOutput !== undefined) {
844
+ // CC 把它限定在 MCP 工具的输出重写;core PostToolUseResult.updatedOutput 期望 content 数组,
845
+ // 语义不同型 → 阶段一不映射,记账可见。
655
846
  ctx.logger.warn("hook_updated_mcp_output_unsupported", { event: "PostToolUse" });
656
847
  }
657
848
  }
@@ -661,6 +852,8 @@ export function createTaskHooks(config, ctx) {
661
852
  hooks.postToolUse = postToolUseHook;
662
853
  }
663
854
  if (postFailure) {
855
+ // CC 语义类比 PostToolUse:调用已失败无从阻断——exit 2 的 stderr / decision:block 的 reason 都折成
856
+ // additionalContext 反馈给模型(core 回调面就是 additionalContext-only,CC-exact)。
664
857
  const postToolUseFailureHook = async (toolName, input, f, tctx) => {
665
858
  const contexts = [];
666
859
  const payload = {
@@ -669,7 +862,9 @@ export function createTaskHooks(config, ctx) {
669
862
  tool_name: toolName,
670
863
  tool_input: input,
671
864
  tool_use_id: tctx.toolCallId,
865
+ // 契约 error=模型面错误文本;有界截断(失败结果可能带 MB 级 tool 输出,同 tool_response 姿势)。
672
866
  error: clip(f.error, MAX_TOOL_RESPONSE_CHARS),
867
+ // core isInterrupt 是宽任意-abort 语义(⊇ CC is_interrupt),原样透传——契约字段名 CC-verbatim。
673
868
  is_interrupt: f.isInterrupt,
674
869
  };
675
870
  const singles = await runMatchingEntries("PostToolUseFailure", postFailure, toolName, payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
@@ -705,6 +900,9 @@ export function createTaskHooks(config, ctx) {
705
900
  hooks.postToolUseFailure = postToolUseFailureHook;
706
901
  }
707
902
  if (postBatch) {
903
+ // matcher=null 事件:契约语义=matcher 被忽略、全条目跑。core 回调面支持 additionalContext(一次性
904
+ // 注入在 turn 边界)→ exit 2 stderr / decision:block reason 都折进去;契约没定 PostToolBatch 的
905
+ // hookSpecificOutput 变体 → 出现即记账不吞。
708
906
  const postToolBatchHook = async (calls) => {
709
907
  const contexts = [];
710
908
  const payload = {
@@ -712,8 +910,10 @@ export function createTaskHooks(config, ctx) {
712
910
  hook_event_name: "PostToolBatch",
713
911
  tool_calls: calls.map((c) => ({
714
912
  tool_name: c.toolName,
913
+ // 逐条截断(顶层 boundedToolInputPayload 只看 payload.tool_input,批量形状会绕过它)。
715
914
  tool_input: boundedInputValue(c.input),
716
915
  tool_use_id: c.toolCallId,
916
+ // core 的 response 已是有界文本 digest;投影成与 PostToolUse tool_response 同形({isError,text})。
717
917
  tool_response: { isError: c.isError, ...(c.response !== undefined ? { text: clip(c.response, MAX_TOOL_RESPONSE_CHARS) } : {}) },
718
918
  })),
719
919
  };
@@ -746,8 +946,12 @@ export function createTaskHooks(config, ctx) {
746
946
  hooks.postToolBatch = postToolBatchHook;
747
947
  }
748
948
  if (promptSubmit) {
949
+ // matcher=null 事件(全条目跑)。CC:exit 2 = 阻断本次提交(stderr=模型可读理由;core 语义=task 以
950
+ // 此理由 fail);decision:"block"+reason 同;hookSpecificOutput.additionalContext 注入在 prompt 前。
749
951
  const userPromptSubmitHook = async (prompt) => {
750
952
  const contexts = [];
953
+ // prompt 是用户任意长输入(粘贴大文件),不设界=绕过 stdin 放大防线——与
954
+ // tool_input 同一上限截断。
751
955
  const payload = { ...basePayload(ctx), hook_event_name: "UserPromptSubmit", prompt: clip(prompt, MAX_TOOL_INPUT_CHARS) };
752
956
  const singles = await runMatchingEntries("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
753
957
  for (const { run } of singles) {
@@ -765,6 +969,7 @@ export function createTaskHooks(config, ctx) {
765
969
  if (typeof out.systemMessage === "string")
766
970
  ctx.logger.warn("hook_system_message", { event: "UserPromptSubmit", message: clip(out.systemMessage, 500) });
767
971
  if (out.continue === false) {
972
+ // continue:false=终止整个 run;userPromptSubmit 的 block 语义正是"任务带理由收场"——同义直映射。
768
973
  const reason = clip(typeof out.stopReason === "string" ? out.stopReason : "hook requested stop", MAX_HOOK_FEEDBACK_CHARS);
769
974
  return { block: reason, ...(contexts.length ? { additionalContext: contexts.join("\n") } : {}) };
770
975
  }
@@ -784,16 +989,40 @@ export function createTaskHooks(config, ctx) {
784
989
  hooks.userPromptSubmit = userPromptSubmitHook;
785
990
  }
786
991
  if (stopEntries) {
992
+ // matcher=null 事件(全条目跑)。CC:exit 2 = 阻断停止(stderr=继续干活的理由,模型再来一轮);
993
+ // decision:"block"+reason 同。防环(cap 8/stopHookActive)由 core 管,runner 只翻译。
787
994
  const stopHook = async (sctx) => {
788
995
  const payload = { ...basePayload(ctx), hook_event_name: "Stop", stop_hook_active: sctx.stopHookActive };
996
+ // ── Stop × `type:"prompt"` 的 CC 形(逐字文案见 cc-stop-prompt.ts;渲染与证据面判据见 branch-transcript.ts)──
997
+ // core 1.419 起 `StopHookContext.getBranch()` 交出本次运行的会话分支;没有它,一个"基于会话状态判断"的
998
+ // 条件钩子**结构上不可能工作**(cli live 实证:模型只会回「I don't have direct access…」这类散文)。
999
+ //
1000
+ // 🔴 **证据面为空时不进这条路**,这一条是承重的:CC 的提示词**明确指示**模型在证据不足时回
1001
+ // `{"ok": false, "reason": "insufficient evidence in transcript"}`,而 Stop 的 `ok:false` = **拦住停止**。
1002
+ // 那**不是解析失败**(解析会成功、判词有效),所以解析层的任何防线都救不了 —— 唯一的防线是不启动它。
1003
+ // 判据挂在**最终送进模型的那段文本**上,不是挂在 `getBranch` 的存在性上(core [1758]:
1004
+ // 访问器在、返回 `[]`、或者被我们自己的过滤渲没了,是同一个后果,后者还是纯自伤)。
1005
+ //
1006
+ // ── 三问(行为面新增)──
1007
+ // · 谁需要:任何用 `type:"prompt"` 写「基于会话状态判断」的 Stop 条件钩子 —— 这是**整类**,不是某个功能;
1008
+ // · 谁被伤:①会话内容多进一次**已经在发生**的模型调用(增量是 transcript 的 token,不是新增一次调用);
1009
+ // ②今天"从不拦"的钩子会开始按配置拦 —— 那**正是操作方配置它要做的事**,不是意外;
1010
+ // · 什么补偿:`SEMA_STOP_PROMPT_TRANSCRIPT=0` 显式关;transcript 走 `redactSecrets` 与 fleet 行同纪律;
1011
+ // 上限 `BRANCH_TRANSCRIPT_MAX_CHARS` 且截断**明说**;最坏情况仍被 core 的 `STOP_HOOK_BLOCK_CAP=8` 兜住。
789
1012
  let llmExtra;
790
1013
  const wantsPrompt = stopEntries.some((g) => g.hooks.some((h) => h.type === "prompt"));
791
1014
  if (wantsPrompt) {
792
1015
  if (ctx.stopPromptTranscript === false) {
1016
+ // 旋钮关 ⇒ **整条路不启用**(而不是"退回旧的、送裸条件进去")。
1017
+ // 选这个语义而不是"逐位退回旧行为":旧行为是**已知失效**的(评估者对会话是瞎的),
1018
+ // 退回它等于留下一条"看起来在跑、永远给不出判决"的路 —— 而那正是这次要修掉的东西。
1019
+ // 关掉就干净地不跑,并记账可见。
793
1020
  ctx.logger.warn("hook_entries_skipped", { event: "Stop", type: "prompt", reason: "stop_prompt_transcript_disabled" });
794
1021
  ctx.onHookNotice?.({ kind: "hook_decision_unavailable", event: "Stop", reason: "skipped", detail: "transcript disabled" });
795
1022
  }
796
1023
  else {
1024
+ // ⚠️ `getBranch()` **抛错**与「分支为空」是两回事(引擎故障 vs 一条还没内容的分支),
1025
+ // 记成同一个原因会让排障时分不清该看哪一边。分开记。
797
1026
  let branch;
798
1027
  try {
799
1028
  branch = await sctx.getBranch?.();
@@ -806,18 +1035,43 @@ export function createTaskHooks(config, ctx) {
806
1035
  llmExtra = { system: ccPromptSystemFor("Stop"), transcript: rendered.text };
807
1036
  }
808
1037
  else {
1038
+ // 诚实降级:**跳过 prompt 条目**(记账可见),而不是"送个空会话进去看看模型怎么说"。
809
1039
  ctx.logger.warn("hook_entries_skipped", { event: "Stop", type: "prompt", reason: `stop_prompt_no_evidence:${rendered.reason}` });
810
1040
  ctx.onHookNotice?.({ kind: "hook_decision_unavailable", event: "Stop", reason: "skipped", detail: `no evidence: ${rendered.reason}` });
811
1041
  }
812
1042
  }
813
1043
  }
814
1044
  const stopGroups = ignoreMatchers(stopEntries);
1045
+ // 拿不到证据面(或旋钮关)⇒ 把 **prompt** 条目滤掉;其余类型(command/http/agent)照跑 ——
1046
+ // 证据面判据只管"需要会话才能判"的那一类,不该让整个 Stop 事件停摆。
1047
+ // 滤在这里而不是在骨架里:骨架是跨事件公共的,这条判据是 Stop×prompt 专属。
815
1048
  const effectiveGroups = llmExtra || !wantsPrompt
816
1049
  ? stopGroups
817
1050
  : stopGroups.map((g) => ({ ...g, hooks: g.hooks.filter((h) => h.type !== "prompt") })).filter((g) => g.hooks.length > 0);
818
1051
  const singles = await runMatchingEntries("Stop", effectiveGroups, "", payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000, undefined, llmExtra);
1052
+ // CC 是并行聚合后 continue:false【全局】压过 block("就让它停")——block 若
1053
+ // 短路返回,后到条目的 continue:false 就没机会翻案。改成跑完整匹配集再折叠:首个 block 记下,
1054
+ // 任一 continue:false 最终放行。
819
1055
  let block;
820
1056
  let sawContinueFalse = false;
1057
+ // ⚠️⚠️ **这个通道带副作用,别随手用**(亲验 core 源码,两版都读过):
1058
+ // `additionalContext` **单独出现**(无 block)时,引擎返回的是**非空** follow-up 消息
1059
+ // (`agent-harness.js:506` 的 `getFollowUpMessages`)⇒ **这一轮不结束**。
1060
+ // · **core < 1.422**:`if (!result?.block)` 那支还会把 `consecutiveBlocks` **清零**
1061
+ // —— 不是"不计入上限",是**抵消掉之前累积的** ⇒ 任何**能持续为真**的条件走这个通道,
1062
+ // 会造出一条 `STOP_HOOK_BLOCK_CAP` **完全管不住**的循环(最坏烧完 maxTurns 缺省 1000)。
1063
+ // · **core ≥ 1.422(本仓下限)**:改成 `if (messages.length === 0 && !result?.block)`
1064
+ // ⇒ **任何非空反馈都计数、不再清零**(与 CC 2.1.220 一致)⇒ 最坏被 cap 8 兜住。
1065
+ // ⚠️ 即便有了 cap,**这个通道仍然会让这一轮不结束** —— 它在功能上与 block 同效(该停的没停),
1066
+ // 只是多了个上限。所以判据不变:**我们自己不得用它去"顺便告知一声"**;
1067
+ // 命令形钩子作者**显式**这么配是他自己的选择(CC 对位)。
1068
+ // 🔴 本仓把 core 下限提到 `^1.422.0` 正是为了这条 —— 因为 command 形的透传是**我们 1.277.3 加的**,
1069
+ // 是我们引入了这个曝露面(见 `test/core-dependency-floor.test.ts` 顶注第二条理由)。
1070
+ // 🔴 `StopHookResult` 是 `{ block?, additionalContext? }` 两个**解耦**的字段(core `core/hooks.d.ts:67`):
1071
+ // `additionalContext` 注入给模型但**不计入**连续 block 上限(CC 2.1.201 对位)。此前本折叠只产出
1072
+ // `block`,于是一条「只想补上下文、不想拦停」的 Stop 钩子输出被**静默丢弃** —— 而紧邻的
1073
+ // UserPromptSubmit 折叠一直是带上它的(同一个 `hookSpecificOutput.additionalContext` 形)。
1074
+ // 对 `/goal` 这类用法这正是常见形(「还差验收用例」既可以拦、也可以只提醒),所以按同构补齐。
821
1075
  const contexts = [];
822
1076
  for (const { entry, run } of singles) {
823
1077
  if (run.code === 2) {
@@ -829,11 +1083,32 @@ export function createTaskHooks(config, ctx) {
829
1083
  ctx.logger.warn("hook_command_failed", { event: "Stop", code: run.code, stderr: clip(run.stderr, 300) });
830
1084
  continue;
831
1085
  }
1086
+ // CC 形的 prompt 条目回的是 `{ok, reason, impossible?}`,不是 `{decision}` —— 分开解析。
1087
+ // 🔴 `parseCcVerdict` 解析不出来 ⇒ undefined ⇒ **无判决**(继续下一条),绝不臆测成 ok:false:
1088
+ // Stop 的 ok:false = 拦停,把"读不懂"当成"没满足"会让一次解析失败变成一次拦停。
1089
+ // ⚠️ **两种判词形都认**,而且它们**无歧义**:CC 形有 `ok`,既有形有 `decision`/`continue`/`hookSpecificOutput`,
1090
+ // 两者的键不重叠。只认 CC 形会**破坏**已经按既有形写好的 prompt 钩子(那是对既有配置的回归),
1091
+ // 而只认既有形就没有 CC 对位。⇒ 先试 CC 形;不是 CC 形就**落到下面的既有解析**,不早退。
832
1092
  if (llmExtra && entry.type === "prompt" && parseCcVerdict(run.stdout)) {
833
1093
  const v = parseCcVerdict(run.stdout);
834
1094
  if (v.ok)
835
- continue;
1095
+ continue; // 条件已达成 ⇒ 不拦
836
1096
  if (v.impossible) {
1097
+ // 🔴 第三态:**永远不可能达成** ⇒ **真 fail-open:既不 block,也不注入** —— 理由只进日志。
1098
+ //
1099
+ // ⚠️ 我第一版把理由走了 `additionalContext`,想的是"静默放行会让用户以为钩子没生效"。
1100
+ // **那个选择是错的**,core [1766] 指出、我亲读 core 源码核实(1.419.0):
1101
+ // · `additionalContext` 单独出现 ⇒ 引擎返回**非空** follow-up 消息
1102
+ // (`agent-harness.js:506` 的 `getFollowUpMessages`)⇒ **这一轮不结束**;
1103
+ // · 它**不计入** `STOP_HOOK_BLOCK_CAP`,而且 `runtask.js` 里 `if (!result?.block)` 那支
1104
+ // 会把 `consecutiveBlocks` **清零** —— 不是"不计入",是**抵消掉之前累积的**。
1105
+ // ⇒ 一个**能持续为真**的 `impossible`(模型这段时间就是这么判)会:注入 → 续跑 → 又到停车点 →
1106
+ // 又 impossible → 又注入 → 计数又清零……**专为兜住「该停而不停」建的那个上限,在这条道上保护为零**,
1107
+ // 最坏情况被 `maxTurns`(缺省 1000)或墙钟兜住 = **把整个预算烧完**,而且**看起来是在忙不是卡住**。
1108
+ //
1109
+ // ⇒ 教训:**别用一个「带副作用的通道」去传递信息**。我要传的是"钩子判定不可达",
1110
+ // 而我选的通道本身会让运行继续 —— 那正是 `impossible` 要防的事。
1111
+ // 用户想知道"钩子为什么没生效"应该去日志里找,而不是靠一条让运行继续下去的注入来暗示。
837
1112
  ctx.logger.warn("hook_stop_condition_impossible", { event: "Stop", reason: clip(v.reason?.trim() ?? "", 500) });
838
1113
  continue;
839
1114
  }
@@ -843,6 +1118,8 @@ export function createTaskHooks(config, ctx) {
843
1118
  }
844
1119
  const out = parseHookStdout(run.stdout);
845
1120
  if (!out) {
1121
+ // 🔴 **两种形都解析不出来 ⇒ 无判决**(绝不臆测成"没满足")。prompt 条目单独记账:
1122
+ // 模型回散文是这条路最常见的失败形,静默丢弃会让排障无从下手。
846
1123
  if (llmExtra && entry.type === "prompt") {
847
1124
  ctx.logger.warn("hook_llm_unparsed_verdict", { event: "Stop", head: clip(run.stdout.trim(), 200) });
848
1125
  ctx.onHookNotice?.({ kind: "hook_decision_unavailable", event: "Stop", reason: "unparsed" });
@@ -863,6 +1140,8 @@ export function createTaskHooks(config, ctx) {
863
1140
  contexts.push(clip(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
864
1141
  }
865
1142
  }
1143
+ // continue:false > block(CC 全局优先序)——「就让它停」。⚠️ 此时 additionalContext 也一并丢弃:
1144
+ // 本 run 已经要结束了,注入给"模型的下一轮"的上下文没有下一轮可去。
866
1145
  if (sawContinueFalse)
867
1146
  return undefined;
868
1147
  if (block === undefined && contexts.length === 0)
@@ -875,12 +1154,16 @@ export function createTaskHooks(config, ctx) {
875
1154
  hooks.stop = stopHook;
876
1155
  }
877
1156
  if (stopFailureEntries) {
1157
+ // observe-only(core 回调 void,契约无 StopFailure 输出面);matcher 对契约 error 枚举匹配
1158
+ // (BRAIN_TO_TURN_ERROR 映射后的值);原始人话错误进 error_details(契约可选字段)。
878
1159
  const stopFailureHook = async (fctx) => {
879
1160
  const turnError = toAssistantTurnError(fctx.errorKind);
880
1161
  const payload = {
881
1162
  ...basePayload(ctx),
882
1163
  hook_event_name: "StopFailure",
883
1164
  error: turnError,
1165
+ // brain 错误串可能回显 provider 的 Authorization/token/URL 凭据——脱敏后再截断
1166
+ // (与 elicitation/project-memory 同用 trace/redact 单源)。
884
1167
  ...(fctx.error ? { error_details: clip(redactSecrets(fctx.error), MAX_HOOK_FEEDBACK_CHARS) } : {}),
885
1168
  };
886
1169
  await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, onceFired);
@@ -888,16 +1171,20 @@ export function createTaskHooks(config, ctx) {
888
1171
  hooks.stopFailure = stopFailureHook;
889
1172
  }
890
1173
  if (preCompactEntries) {
1174
+ // matcher 对 trigger 匹配。core 的 trigger 可为 "forced"(契约枚举只有 auto|manual 的超集)——原样
1175
+ // 透传(matcher "*" 仍可见);block 在 forced 上由 core 忽略并 trace(其契约注明),runner 不重复裁决。
891
1176
  const preCompactHook = async (pctx) => {
892
1177
  const payload = {
893
1178
  ...basePayload(ctx),
894
1179
  hook_event_name: "PreCompact",
895
1180
  trigger: pctx.trigger,
1181
+ // 契约 custom_instructions 是 nullable 必填(CC-verbatim)——没有就送 null,不缺字段。
896
1182
  custom_instructions: pctx.customInstructions ?? null,
897
1183
  };
898
1184
  const singles = await runMatchingEntries("PreCompact", preCompactEntries, pctx.trigger, payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
899
1185
  for (const { run } of singles) {
900
1186
  if (run.code === 2) {
1187
+ // CC:exit 2 = 阻断本次压缩(stderr=理由,core trace 记录)。
901
1188
  return { block: clip(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreCompact hook" };
902
1189
  }
903
1190
  if (run.code !== 0) {
@@ -910,6 +1197,7 @@ export function createTaskHooks(config, ctx) {
910
1197
  if (typeof out.systemMessage === "string")
911
1198
  ctx.logger.warn("hook_system_message", { event: "PreCompact", message: clip(out.systemMessage, 500) });
912
1199
  if (out.continue === false) {
1200
+ // "终止整个 run"≠"跳过这次压缩"——不偷换成 block,记账可见。
913
1201
  ctx.logger.warn("hook_continue_false_unsupported", { event: "PreCompact" });
914
1202
  }
915
1203
  if (out.decision === "block") {
@@ -921,6 +1209,8 @@ export function createTaskHooks(config, ctx) {
921
1209
  hooks.preCompact = preCompactHook;
922
1210
  }
923
1211
  if (postCompactEntries) {
1212
+ // observe-only(core 回调 void,契约无 PostCompact 输出面);matcher 对 trigger 匹配;
1213
+ // compact_summary 有界截断(压缩摘要可长)。core 的 tokensBefore/After 契约无字段 → 宁缺毋滥。
924
1214
  const postCompactHook = async (cctx) => {
925
1215
  const payload = {
926
1216
  ...basePayload(ctx),
@@ -934,6 +1224,18 @@ export function createTaskHooks(config, ctx) {
934
1224
  }
935
1225
  return hooks;
936
1226
  }
1227
+ /**
1228
+ * 把部署级 hooks 基线(deps.hooks,如 TOOL_TRACE 观测)折进 task 级 hooks——core 是整槽覆盖
1229
+ * (`spec.hooks ?? deps.hooks`),不折叠就静默 shadow 部署观测(runtask gateBaseline 注释点名的坑)。
1230
+ * 语义:部署槽先跑(观测看到原始输入/输出),task 槽后跑;结果合并:
1231
+ * - preToolUse:deny > ask > allow 折叠;updatedInput 串行线程(部署重写喂给 task 槽);context 拼接。
1232
+ * - postToolUse/postToolUseFailure/postToolBatch:双跑,additionalContext 拼接,updatedOutput 取
1233
+ * 后者(task 槽)优先。
1234
+ * - userPromptSubmit/stop:部署 block 优先短路(不再跑 task 槽),否则双跑、context 拼接、task block 生效。
1235
+ * - preCompact:双跑,block 部署优先,additionalInstructions 拼接。
1236
+ * - stopFailure/postCompact(observe-only void):顺序双跑,无返回可折。
1237
+ * 单在则透传。
1238
+ */
937
1239
  export function composeHooks(deployment, task) {
938
1240
  if (!deployment)
939
1241
  return task;
@@ -962,6 +1264,7 @@ export function composeHooks(deployment, task) {
962
1264
  }
963
1265
  }
964
1266
  if (asked) {
1267
+ // ask 槽自己没带改写、但前面槽已改写 → 把线程中的改写挂上,approve 才执行改写后 args。
965
1268
  const askedUpdated = asked.updatedInput;
966
1269
  return {
967
1270
  ...asked,
@@ -1022,6 +1325,8 @@ export function composeHooks(deployment, task) {
1022
1325
  };
1023
1326
  }
1024
1327
  if (d.stop || t.stop) {
1328
+ // 与 userPromptSubmit 同折叠:部署 block 优先短路;否则双跑、additionalContext(CC 2.1.201,与
1329
+ // block 解耦)拼接、task 的 block 生效——`rt ?? rd` 会把"rd 有 context、rt 有 block"折丢一侧。
1025
1330
  out.stop = async (sctx) => {
1026
1331
  const rd = d.stop ? await d.stop(sctx) : undefined;
1027
1332
  if (rd?.block)
@@ -1034,6 +1339,7 @@ export function composeHooks(deployment, task) {
1034
1339
  };
1035
1340
  }
1036
1341
  if (d.preCompact || t.preCompact) {
1342
+ // 双跑(部署观测也想看到每次压缩);block 部署优先,additionalInstructions 拼接(core 语义=追加)。
1037
1343
  out.preCompact = async (pctx) => {
1038
1344
  const rd = d.preCompact ? await d.preCompact(pctx) : undefined;
1039
1345
  const rt = t.preCompact ? await t.preCompact(pctx) : undefined;
@@ -1048,6 +1354,9 @@ export function composeHooks(deployment, task) {
1048
1354
  };
1049
1355
  }
1050
1356
  for (const slot of ["stopFailure", "postCompact", "permissionDenied"]) {
1357
+ // observe-only void 槽:两侧都在=顺序双跑(部署先),无返回可折;单在则透传。
1358
+ // permissionDenied(core 1.257 新增,gate deny 短路观测)也走这里——createTaskHooks 不产它
1359
+ // (PermissionDenied 是 shell-owned 事件),但部署侧 tracer 未来若设它,整槽覆盖不透传就会被 shadow。
1051
1360
  const ds = d[slot];
1052
1361
  const ts = t[slot];
1053
1362
  if (ds && ts) {