@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.
- package/dist/approval-hmac.d.ts +40 -0
- package/dist/approval-hmac.js +67 -0
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +72 -4
- package/dist/audit.d.ts +26 -0
- package/dist/audit.js +40 -1
- package/dist/auth-bridge.d.ts +10 -0
- package/dist/auth-bridge.js +47 -4
- package/dist/auth-keys.d.ts +19 -0
- package/dist/auth-keys.js +13 -0
- package/dist/bake-runner/main.d.ts +5 -0
- package/dist/bake-runner/main.js +43 -4
- package/dist/bake-runner/protocol.d.ts +57 -0
- package/dist/bake-runner/protocol.js +58 -3
- package/dist/bake-runner/runner.d.ts +54 -0
- package/dist/bake-runner/runner.js +87 -5
- package/dist/bench/l8/artifact.d.ts +63 -0
- package/dist/bench/l8/artifact.js +61 -2
- package/dist/bench/l8/escape.d.ts +36 -0
- package/dist/bench/l8/escape.js +25 -0
- package/dist/bench/l8/index.d.ts +14 -0
- package/dist/bench/l8/index.js +14 -0
- package/dist/bench/l8/probes.d.ts +74 -0
- package/dist/bench/l8/probes.js +60 -0
- package/dist/bench/l8/run-probes.d.ts +43 -0
- package/dist/bench/l8/run-probes.js +45 -2
- package/dist/bench/s1/arms.d.ts +126 -0
- package/dist/bench/s1/arms.js +188 -13
- package/dist/bench/s1/live-deps.d.ts +66 -0
- package/dist/bench/s1/live-deps.js +249 -11
- package/dist/bench/s1/oracle.d.ts +49 -0
- package/dist/bench/s1/oracle.js +36 -1
- package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
- package/dist/bench/s1/repair-oracle-adapter.js +30 -0
- package/dist/bench/s1/reviewer.d.ts +38 -0
- package/dist/bench/s1/reviewer.js +34 -0
- package/dist/bench/s1/row.d.ts +77 -0
- package/dist/bench/s1/row.js +62 -0
- package/dist/bench/s1/run-firm.d.ts +47 -0
- package/dist/bench/s1/run-firm.js +70 -4
- package/dist/bench/s1/runner-ctx.d.ts +71 -0
- package/dist/bench/s1/runner-ctx.js +36 -0
- package/dist/bench/s1/tasks.d.ts +108 -0
- package/dist/bench/s1/tasks.js +115 -0
- package/dist/boot/budget-tracing.d.ts +48 -0
- package/dist/boot/budget-tracing.js +86 -0
- package/dist/boot/config-center.d.ts +62 -0
- package/dist/boot/config-center.js +995 -0
- package/dist/boot/coordinators.d.ts +33 -0
- package/dist/boot/coordinators.js +97 -0
- package/dist/boot/execution-env.d.ts +26 -0
- package/dist/boot/execution-env.js +370 -0
- package/dist/boot/leader.d.ts +27 -0
- package/dist/boot/leader.js +81 -0
- package/dist/boot/reapers.d.ts +53 -0
- package/dist/boot/reapers.js +252 -0
- package/dist/boot/resolve-spec.d.ts +70 -0
- package/dist/boot/resolve-spec.js +1072 -0
- package/dist/boot/runner-deps.d.ts +101 -0
- package/dist/boot/runner-deps.js +343 -0
- package/dist/boot/runtime-caps.d.ts +21 -0
- package/dist/boot/runtime-caps.js +62 -0
- package/dist/boot/session-faces.d.ts +57 -0
- package/dist/boot/session-faces.js +157 -0
- package/dist/boot/shutdown.d.ts +50 -0
- package/dist/boot/shutdown.js +129 -0
- package/dist/boot/stores.d.ts +32 -0
- package/dist/boot/stores.js +361 -0
- package/dist/boot/workflow-orchestration.d.ts +46 -0
- package/dist/boot/workflow-orchestration.js +150 -0
- package/dist/boot-reclaim.d.ts +19 -0
- package/dist/boot-reclaim.js +20 -0
- package/dist/brain.d.ts +25 -0
- package/dist/brain.js +61 -1
- package/dist/budget.d.ts +66 -0
- package/dist/budget.js +128 -4
- package/dist/capabilities/builtin-tools.d.ts +3 -0
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/center-plugins.d.ts +17 -1
- package/dist/capabilities/center-plugins.js +45 -4
- package/dist/capabilities/center-prompts.d.ts +46 -0
- package/dist/capabilities/center-prompts.js +57 -3
- package/dist/capabilities/code-review-council.d.ts +28 -0
- package/dist/capabilities/code-review-council.js +40 -7
- package/dist/capabilities/collab-wire.d.ts +2 -0
- package/dist/capabilities/collab-workflows.d.ts +7 -0
- package/dist/capabilities/collab-workflows.js +44 -1
- package/dist/capabilities/oa-tools.d.ts +6 -0
- package/dist/capabilities/oa-tools.js +10 -0
- package/dist/capabilities/prompt.d.ts +14 -0
- package/dist/capabilities/prompt.js +14 -0
- package/dist/capabilities/prompts/code-review.d.ts +14 -0
- package/dist/capabilities/prompts/code-review.js +14 -0
- package/dist/capabilities/prompts/identity.d.ts +6 -0
- package/dist/capabilities/prompts/identity.js +6 -0
- package/dist/capabilities/prompts/team.d.ts +4 -0
- package/dist/capabilities/prompts/team.js +4 -0
- package/dist/capabilities/repo-tools.d.ts +11 -0
- package/dist/capabilities/repo-tools.js +8 -0
- package/dist/capabilities/sandbox-file-send.d.ts +95 -0
- package/dist/capabilities/sandbox-file-send.js +91 -3
- package/dist/capabilities/scenarios.d.ts +130 -0
- package/dist/capabilities/scenarios.js +180 -7
- package/dist/capabilities/select-environment-tool.d.ts +7 -0
- package/dist/capabilities/select-environment-tool.js +30 -0
- package/dist/capabilities/send-user-file-tool.d.ts +33 -0
- package/dist/capabilities/send-user-file-tool.js +65 -1
- package/dist/capabilities/skills.d.ts +29 -0
- package/dist/capabilities/skills.js +13 -2
- package/dist/capabilities/team.d.ts +22 -0
- package/dist/capabilities/team.js +22 -2
- package/dist/capabilities/tool-defer.d.ts +26 -0
- package/dist/capabilities/tool-defer.js +3 -0
- package/dist/config-center/apply-effective.d.ts +65 -0
- package/dist/config-center/apply-effective.js +204 -12
- package/dist/config-center/http-client.d.ts +34 -0
- package/dist/config-center/http-client.js +68 -0
- package/dist/config-center/restart-signal.d.ts +22 -0
- package/dist/config-center/restart-signal.js +39 -0
- package/dist/config-center/skills-mcp.d.ts +20 -1
- package/dist/config-center/skills-mcp.js +42 -7
- package/dist/config-center/types.d.ts +92 -1
- package/dist/config-lkg.d.ts +12 -0
- package/dist/config-lkg.js +41 -0
- package/dist/config-provider.d.ts +78 -0
- package/dist/config-provider.js +165 -2
- package/dist/config-types.d.ts +585 -1
- package/dist/config.d.ts +78 -2
- package/dist/config.js +874 -301
- package/dist/degenerate-instrument.d.ts +38 -0
- package/dist/degenerate-instrument.js +55 -3
- package/dist/elicitation.d.ts +39 -0
- package/dist/elicitation.js +87 -3
- package/dist/env-facts.d.ts +61 -0
- package/dist/env-facts.js +73 -7
- package/dist/fleet/fleet-bus.d.ts +185 -0
- package/dist/fleet/fleet-bus.js +333 -27
- package/dist/fleet/subagent-tail-bus.d.ts +11 -0
- package/dist/fleet/subagent-tail-bus.js +33 -2
- package/dist/fleet-client.d.ts +38 -0
- package/dist/fleet-client.js +44 -9
- package/dist/fleet-lease.d.ts +46 -0
- package/dist/fleet-lease.js +73 -5
- package/dist/hooks/branch-transcript.d.ts +8 -0
- package/dist/hooks/branch-transcript.js +33 -0
- package/dist/hooks/cc-stop-prompt.d.ts +42 -0
- package/dist/hooks/cc-stop-prompt.js +43 -1
- package/dist/hooks/hook-llm.d.ts +15 -0
- package/dist/hooks/hook-llm.js +47 -0
- package/dist/hooks/hook-runner.d.ts +89 -0
- package/dist/hooks/hook-runner.js +327 -18
- package/dist/http/idempotency.d.ts +31 -0
- package/dist/http/idempotency.js +34 -0
- package/dist/http/principal-gate.d.ts +17 -0
- package/dist/http/principal-gate.js +35 -3
- package/dist/http/route-ctx.d.ts +36 -0
- package/dist/http/routes/approvals-assistant.d.ts +25 -0
- package/dist/http/routes/approvals-assistant.js +253 -23
- package/dist/http/routes/attachments.js +22 -4
- package/dist/http/routes/capabilities.d.ts +7 -0
- package/dist/http/routes/capabilities.js +202 -1
- package/dist/http/routes/fleet.d.ts +6 -0
- package/dist/http/routes/fleet.js +94 -5
- package/dist/http/routes/images.d.ts +17 -0
- package/dist/http/routes/images.js +222 -14
- package/dist/http/routes/leader.d.ts +7 -0
- package/dist/http/routes/leader.js +10 -0
- package/dist/http/routes/memory-policy.d.ts +6 -0
- package/dist/http/routes/memory-policy.js +38 -6
- package/dist/http/routes/notify-wake.d.ts +10 -0
- package/dist/http/routes/notify-wake.js +19 -3
- package/dist/http/routes/observability.d.ts +6 -0
- package/dist/http/routes/observability.js +19 -2
- package/dist/http/routes/runs.d.ts +15 -0
- package/dist/http/routes/runs.js +464 -41
- package/dist/http/routes/session-sync.d.ts +15 -0
- package/dist/http/routes/session-sync.js +229 -19
- package/dist/http/routes/sessions-list.d.ts +6 -0
- package/dist/http/routes/sessions-list.js +35 -4
- package/dist/http/routes/sessions.d.ts +14 -0
- package/dist/http/routes/sessions.js +176 -31
- package/dist/http/routes/side-query.d.ts +8 -0
- package/dist/http/routes/side-query.js +26 -1
- package/dist/http/routes/tasks.d.ts +18 -0
- package/dist/http/routes/tasks.js +501 -38
- package/dist/http/routes/trace-usage.d.ts +10 -0
- package/dist/http/routes/trace-usage.js +66 -14
- package/dist/http/routes/workflows.d.ts +12 -0
- package/dist/http/routes/workflows.js +180 -19
- package/dist/http/run-meta.d.ts +11 -0
- package/dist/http/run-meta.js +6 -0
- package/dist/http/send.d.ts +19 -0
- package/dist/http/send.js +11 -0
- package/dist/http/server.d.ts +255 -0
- package/dist/http/server.js +921 -75
- package/dist/http/sse-log.d.ts +15 -0
- package/dist/http/sse-log.js +16 -0
- package/dist/http/tar.d.ts +9 -0
- package/dist/http/tar.js +21 -5
- package/dist/http/wire-gate.d.ts +9 -0
- package/dist/http/wire-gate.js +9 -0
- package/dist/http/wire-types.d.ts +187 -0
- package/dist/http/workspace-content.d.ts +8 -0
- package/dist/http/workspace-content.js +10 -0
- package/dist/images/bake-validate.d.ts +63 -0
- package/dist/images/bake-validate.js +71 -1
- package/dist/images/manifest.d.ts +19 -0
- package/dist/images/manifest.js +6 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.js +10 -5
- package/dist/key-resolver.d.ts +23 -0
- package/dist/key-resolver.js +27 -2
- package/dist/leader/diffout.d.ts +23 -0
- package/dist/leader/diffout.js +8 -1
- package/dist/leader/diffup.d.ts +32 -0
- package/dist/leader/diffup.js +43 -0
- package/dist/leader/endpoint.d.ts +20 -0
- package/dist/leader/endpoint.js +25 -2
- package/dist/leader/fanout.d.ts +60 -0
- package/dist/leader/fanout.js +29 -3
- package/dist/leader/grader-env-factory.d.ts +74 -0
- package/dist/leader/grader-env-factory.js +62 -3
- package/dist/leader/leader.d.ts +97 -0
- package/dist/leader/leader.js +151 -4
- package/dist/leader/merge.d.ts +94 -0
- package/dist/leader/merge.js +112 -9
- package/dist/leader/planner.d.ts +52 -0
- package/dist/leader/planner.js +47 -3
- package/dist/leader/repair-oracle.d.ts +61 -0
- package/dist/leader/repair-oracle.js +59 -1
- package/dist/leader/repair-wire.d.ts +78 -0
- package/dist/leader/repair-wire.js +79 -2
- package/dist/leader/wire.d.ts +79 -0
- package/dist/leader/wire.js +255 -8
- package/dist/lsp/e2b-bridge.d.ts +16 -0
- package/dist/lsp/e2b-bridge.js +32 -4
- package/dist/lsp/e2b-manager.d.ts +10 -0
- package/dist/lsp/e2b-manager.js +18 -2
- package/dist/lsp/lsp-frames.d.ts +12 -0
- package/dist/lsp/lsp-frames.js +12 -0
- package/dist/lsp/manager.d.ts +12 -0
- package/dist/lsp/manager.js +35 -2
- package/dist/lsp/types.d.ts +10 -0
- package/dist/lsp/ws-transport.d.ts +12 -0
- package/dist/lsp/ws-transport.js +53 -5
- package/dist/lsp-evict.d.ts +14 -0
- package/dist/lsp-evict.js +15 -1
- package/dist/main.js +530 -2129
- package/dist/memory-export.d.ts +1 -0
- package/dist/memory-export.js +4 -0
- package/dist/memory-scope.d.ts +49 -0
- package/dist/memory-scope.js +87 -4
- package/dist/memory-sync-client.d.ts +24 -0
- package/dist/memory-sync-client.js +41 -2
- package/dist/memory-sync.d.ts +23 -0
- package/dist/memory-sync.js +70 -1
- package/dist/model-select.d.ts +39 -0
- package/dist/model-select.js +42 -1
- package/dist/observability/cost-quota.d.ts +20 -0
- package/dist/observability/cost-quota.js +4 -0
- package/dist/observability/cost-taxonomy.d.ts +45 -0
- package/dist/observability/cost-taxonomy.js +34 -0
- package/dist/observability/logger.d.ts +1 -0
- package/dist/observability/logger.js +6 -0
- package/dist/observability/metrics.d.ts +13 -0
- package/dist/observability/metrics.js +63 -0
- package/dist/observability/otel-exporter.d.ts +9 -0
- package/dist/observability/otel-exporter.js +11 -3
- package/dist/observability/principal-context.d.ts +9 -0
- package/dist/observability/principal-context.js +9 -0
- package/dist/observability/prompt-manifest.d.ts +37 -0
- package/dist/observability/prompt-manifest.js +35 -1
- package/dist/observability/rate-limit.d.ts +10 -0
- package/dist/observability/rate-limit.js +1 -0
- package/dist/observability/tool-trace.d.ts +24 -0
- package/dist/observability/tool-trace.js +41 -1
- package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
- package/dist/orchestration/hardened-vm-runner.js +119 -4
- package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
- package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
- package/dist/orchestration/hardened-vm-worker.js +27 -0
- package/dist/orchestration/subagent-steer.d.ts +44 -0
- package/dist/orchestration/subagent-steer.js +45 -1
- package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
- package/dist/orchestration/workflow-agent-steer.js +80 -1
- package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
- package/dist/orchestration/workflow-completion-inbox.js +283 -32
- package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
- package/dist/orchestration/workflow-notify-journal.js +187 -13
- package/dist/parked-decide.d.ts +61 -0
- package/dist/parked-decide.js +58 -1
- package/dist/per-task-image.d.ts +54 -0
- package/dist/per-task-image.js +41 -0
- package/dist/plan-cache-probe.d.ts +35 -0
- package/dist/plan-cache-probe.js +18 -3
- package/dist/plugins/approval-exemption-store.d.ts +7 -0
- package/dist/plugins/approval-exemption-store.js +28 -2
- package/dist/plugins/approval-store-sql.d.ts +64 -0
- package/dist/plugins/approval-store-sql.js +32 -0
- package/dist/plugins/background-agent-store-sql.d.ts +55 -0
- package/dist/plugins/background-agent-store-sql.js +91 -4
- package/dist/plugins/background-shell-support.d.ts +98 -0
- package/dist/plugins/background-shell-support.js +117 -13
- package/dist/plugins/blob-backend.d.ts +104 -0
- package/dist/plugins/blob-backend.js +163 -6
- package/dist/plugins/breaker-state-sql.d.ts +65 -1
- package/dist/plugins/breaker-state-sql.js +46 -8
- package/dist/plugins/caching-session-store.d.ts +42 -0
- package/dist/plugins/caching-session-store.js +63 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
- package/dist/plugins/checkpoint-store-sql.js +239 -11
- package/dist/plugins/file-outcome-sink.d.ts +4 -0
- package/dist/plugins/file-outcome-sink.js +9 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
- package/dist/plugins/file-resume-anchor-store.js +43 -4
- package/dist/plugins/file-run-store.d.ts +90 -0
- package/dist/plugins/file-run-store.js +145 -13
- package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
- package/dist/plugins/file-snapshot-store-sql.js +178 -10
- package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
- package/dist/plugins/file-workflow-journal-store.js +10 -0
- package/dist/plugins/fork-routing-session-store.d.ts +47 -0
- package/dist/plugins/fork-routing-session-store.js +51 -3
- package/dist/plugins/host-platform.d.ts +69 -0
- package/dist/plugins/host-platform.js +81 -2
- package/dist/plugins/image-bake-store-sql.d.ts +136 -1
- package/dist/plugins/image-bake-store-sql.js +241 -7
- package/dist/plugins/image-index-sql.d.ts +46 -0
- package/dist/plugins/image-index-sql.js +113 -4
- package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
- package/dist/plugins/k8s-bg-scripts.js +88 -4
- package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
- package/dist/plugins/k8s-exec-protocol.js +41 -0
- package/dist/plugins/local-checkpoint-store.d.ts +40 -0
- package/dist/plugins/local-checkpoint-store.js +86 -7
- package/dist/plugins/local-session-store.d.ts +157 -0
- package/dist/plugins/local-session-store.js +289 -34
- package/dist/plugins/local-task-attachment-store.js +14 -2
- package/dist/plugins/mailbox-store-sql.d.ts +38 -0
- package/dist/plugins/mailbox-store-sql.js +39 -6
- package/dist/plugins/memory-engine-pg.d.ts +30 -0
- package/dist/plugins/memory-engine-pg.js +122 -4
- package/dist/plugins/memory-engine-tidb.d.ts +26 -0
- package/dist/plugins/memory-engine-tidb.js +115 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
- package/dist/plugins/memory-engine-vector-util.js +5 -0
- package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
- package/dist/plugins/memory-resume-anchor-store.js +17 -0
- package/dist/plugins/memory-run-store.d.ts +63 -0
- package/dist/plugins/memory-run-store.js +73 -8
- package/dist/plugins/memory-session-policy-store.d.ts +17 -0
- package/dist/plugins/memory-session-policy-store.js +17 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
- package/dist/plugins/memory-sync-store-pg.js +45 -2
- package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
- package/dist/plugins/memory-sync-store-tidb.js +31 -1
- package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
- package/dist/plugins/outcome-ledger-sql.js +97 -3
- package/dist/plugins/pg-approval-store.d.ts +7 -0
- package/dist/plugins/pg-approval-store.js +7 -0
- package/dist/plugins/pg-breaker-state.d.ts +6 -0
- package/dist/plugins/pg-breaker-state.js +6 -0
- package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
- package/dist/plugins/pg-checkpoint-store.js +8 -0
- package/dist/plugins/pg-cost-quota.d.ts +17 -0
- package/dist/plugins/pg-cost-quota.js +6 -0
- package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/pg-file-snapshot-store.js +6 -0
- package/dist/plugins/pg-image-bake.d.ts +9 -0
- package/dist/plugins/pg-image-bake.js +9 -0
- package/dist/plugins/pg-image-index.d.ts +9 -0
- package/dist/plugins/pg-image-index.js +9 -0
- package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
- package/dist/plugins/pg-outcome-ledger.js +9 -0
- package/dist/plugins/pg-pool.d.ts +50 -0
- package/dist/plugins/pg-pool.js +78 -0
- package/dist/plugins/pg-query.d.ts +11 -0
- package/dist/plugins/pg-rate-limiter.d.ts +23 -0
- package/dist/plugins/pg-rate-limiter.js +12 -2
- package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/pg-resume-anchor-store.js +5 -0
- package/dist/plugins/pg-run-store.d.ts +7 -0
- package/dist/plugins/pg-run-store.js +7 -0
- package/dist/plugins/pg-safe-json.d.ts +13 -0
- package/dist/plugins/pg-safe-json.js +40 -4
- package/dist/plugins/pg-session-policy-store.d.ts +5 -0
- package/dist/plugins/pg-session-policy-store.js +5 -0
- package/dist/plugins/pg-session-storage.d.ts +72 -0
- package/dist/plugins/pg-session-storage.js +152 -20
- package/dist/plugins/pg-session-store.d.ts +10 -0
- package/dist/plugins/pg-session-store.js +10 -0
- package/dist/plugins/pg-tool-result-store.d.ts +7 -0
- package/dist/plugins/pg-tool-result-store.js +7 -0
- package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-journal-store.js +7 -0
- package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-run-store.js +7 -0
- package/dist/plugins/posix-shell-fs.d.ts +3 -0
- package/dist/plugins/posix-shell-fs.js +31 -1
- package/dist/plugins/remote-env-adb.d.ts +25 -0
- package/dist/plugins/remote-env-adb.js +95 -12
- package/dist/plugins/remote-env-e2b.d.ts +146 -0
- package/dist/plugins/remote-env-e2b.js +371 -40
- package/dist/plugins/remote-env-file-error.d.ts +34 -0
- package/dist/plugins/remote-env-file-error.js +37 -0
- package/dist/plugins/remote-env-host.d.ts +151 -0
- package/dist/plugins/remote-env-host.js +468 -70
- package/dist/plugins/remote-env-k8s.d.ts +135 -0
- package/dist/plugins/remote-env-k8s.js +318 -32
- package/dist/plugins/remote-env-local-docker.d.ts +65 -0
- package/dist/plugins/remote-env-local-docker.js +149 -24
- package/dist/plugins/remote-env-ssh.d.ts +35 -0
- package/dist/plugins/remote-env-ssh.js +117 -20
- package/dist/plugins/remote-scratchpad.d.ts +38 -0
- package/dist/plugins/remote-scratchpad.js +32 -2
- package/dist/plugins/remote-shell.d.ts +22 -0
- package/dist/plugins/remote-shell.js +27 -1
- package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
- package/dist/plugins/resume-anchor-store-sql.js +11 -0
- package/dist/plugins/roster-store-sql.d.ts +27 -0
- package/dist/plugins/roster-store-sql.js +54 -0
- package/dist/plugins/run-store-sql.d.ts +194 -0
- package/dist/plugins/run-store-sql.js +210 -9
- package/dist/plugins/s3-presign.d.ts +20 -0
- package/dist/plugins/s3-presign.js +49 -2
- package/dist/plugins/scheduler-support.d.ts +12 -0
- package/dist/plugins/scheduler-support.js +75 -3
- package/dist/plugins/send-file-ledger.d.ts +15 -0
- package/dist/plugins/send-file-ledger.js +53 -4
- package/dist/plugins/send-user-file.d.ts +77 -0
- package/dist/plugins/send-user-file.js +92 -4
- package/dist/plugins/session-policy-store-sql.d.ts +15 -0
- package/dist/plugins/session-policy-store-sql.js +75 -3
- package/dist/plugins/session-store.d.ts +45 -0
- package/dist/plugins/session-store.js +51 -0
- package/dist/plugins/sql-driver.d.ts +77 -0
- package/dist/plugins/sql-driver.js +4 -0
- package/dist/plugins/sql-escape.d.ts +8 -0
- package/dist/plugins/sql-escape.js +8 -0
- package/dist/plugins/sql-row-helpers.d.ts +25 -0
- package/dist/plugins/sql-row-helpers.js +25 -0
- package/dist/plugins/store-backend.d.ts +103 -0
- package/dist/plugins/store-backend.js +120 -27
- package/dist/plugins/store-contracts.d.ts +62 -0
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +60 -0
- package/dist/plugins/task-attachment-store.js +49 -3
- package/dist/plugins/task-list-store-sql.d.ts +25 -0
- package/dist/plugins/task-list-store-sql.js +21 -0
- package/dist/plugins/tidb-approval-store.d.ts +6 -0
- package/dist/plugins/tidb-approval-store.js +6 -0
- package/dist/plugins/tidb-breaker-state.d.ts +5 -0
- package/dist/plugins/tidb-breaker-state.js +5 -0
- package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
- package/dist/plugins/tidb-checkpoint-store.js +7 -0
- package/dist/plugins/tidb-cost-quota.d.ts +6 -0
- package/dist/plugins/tidb-cost-quota.js +1 -0
- package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/tidb-file-snapshot-store.js +6 -0
- package/dist/plugins/tidb-image-bake.d.ts +8 -0
- package/dist/plugins/tidb-image-bake.js +8 -0
- package/dist/plugins/tidb-image-index.d.ts +8 -0
- package/dist/plugins/tidb-image-index.js +8 -0
- package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
- package/dist/plugins/tidb-outcome-ledger.js +10 -0
- package/dist/plugins/tidb-pool.d.ts +56 -0
- package/dist/plugins/tidb-pool.js +181 -1
- package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
- package/dist/plugins/tidb-rate-limiter.js +5 -2
- package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/tidb-resume-anchor-store.js +5 -0
- package/dist/plugins/tidb-run-store.d.ts +7 -0
- package/dist/plugins/tidb-run-store.js +7 -0
- package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
- package/dist/plugins/tidb-session-policy-store.js +5 -0
- package/dist/plugins/tidb-session-storage.d.ts +43 -0
- package/dist/plugins/tidb-session-storage.js +62 -3
- package/dist/plugins/tidb-session-store.d.ts +142 -0
- package/dist/plugins/tidb-session-store.js +264 -16
- package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
- package/dist/plugins/tidb-tool-result-store.js +7 -0
- package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/tidb-workflow-journal-store.js +7 -0
- package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
- package/dist/plugins/tidb-workflow-run-store.js +8 -0
- package/dist/plugins/tool-result-store-sql.d.ts +58 -0
- package/dist/plugins/tool-result-store-sql.js +38 -2
- package/dist/plugins/web-search.d.ts +35 -0
- package/dist/plugins/web-search.js +45 -9
- package/dist/plugins/workflow-journal-limits.d.ts +10 -0
- package/dist/plugins/workflow-journal-limits.js +10 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
- package/dist/plugins/workflow-journal-store-sql.js +58 -2
- package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
- package/dist/plugins/workflow-run-store-sql.js +84 -6
- package/dist/plugins/worktree-isolation.d.ts +45 -0
- package/dist/plugins/worktree-isolation.js +105 -6
- package/dist/plugins/write-behind-counter.d.ts +56 -1
- package/dist/plugins/write-behind-counter.js +53 -14
- package/dist/principal-jwt.d.ts +24 -0
- package/dist/principal-jwt.js +60 -5
- package/dist/project-memory.d.ts +22 -0
- package/dist/project-memory.js +144 -15
- package/dist/prompts-domain-validate.d.ts +28 -0
- package/dist/prompts-domain-validate.js +42 -1
- package/dist/question.d.ts +33 -0
- package/dist/question.js +69 -2
- package/dist/resource-suspend.d.ts +21 -0
- package/dist/resource-suspend.js +16 -0
- package/dist/router/route-orchestration.d.ts +78 -0
- package/dist/router/route-orchestration.js +77 -0
- package/dist/run-local.d.ts +32 -0
- package/dist/run-local.js +172 -8
- package/dist/runs.d.ts +189 -4
- package/dist/runs.js +465 -24
- package/dist/runtime-caps-resolver.d.ts +106 -0
- package/dist/runtime-caps-resolver.js +89 -4
- package/dist/runtime-governance.d.ts +84 -0
- package/dist/runtime-governance.js +127 -1
- package/dist/sandbox-pkg-source.d.ts +23 -0
- package/dist/sandbox-pkg-source.js +37 -0
- package/dist/sealed-key.d.ts +54 -0
- package/dist/sealed-key.js +68 -3
- package/dist/security.d.ts +108 -0
- package/dist/security.js +145 -6
- package/dist/sema-registry.d.ts +21 -0
- package/dist/sema-registry.js +21 -0
- package/dist/session-leaf-bus.d.ts +31 -0
- package/dist/session-leaf-bus.js +32 -0
- package/dist/session-sync-content.d.ts +46 -0
- package/dist/session-sync-content.js +47 -1
- package/dist/session-sync-kernel.d.ts +89 -0
- package/dist/session-sync-kernel.js +48 -3
- package/dist/session-sync.d.ts +125 -0
- package/dist/session-sync.js +181 -8
- package/dist/session-titler.d.ts +26 -0
- package/dist/session-titler.js +41 -7
- package/dist/session-watch.d.ts +53 -1
- package/dist/session-watch.js +73 -9
- package/dist/sighup-idle.d.ts +30 -0
- package/dist/sighup-idle.js +12 -1
- package/dist/spec-fields.d.ts +91 -3
- package/dist/spec-fields.js +116 -4
- package/dist/task-cwd.d.ts +50 -0
- package/dist/task-cwd.js +68 -1
- package/dist/task-mcp.d.ts +47 -0
- package/dist/task-mcp.js +29 -0
- package/dist/task-settings.d.ts +175 -0
- package/dist/task-settings.js +226 -11
- package/dist/task-workflow.d.ts +67 -0
- package/dist/task-workflow.js +75 -8
- package/dist/tool-approval.d.ts +91 -0
- package/dist/tool-approval.js +215 -0
- package/dist/trace/artifacts.d.ts +9 -0
- package/dist/trace/artifacts.js +30 -5
- package/dist/trace/core-keyset-guard.d.ts +18 -0
- package/dist/trace/ledger-sink.d.ts +43 -0
- package/dist/trace/ledger-sink.js +64 -5
- package/dist/trace/project.d.ts +133 -0
- package/dist/trace/project.js +189 -2
- package/dist/trace/redact.d.ts +19 -0
- package/dist/trace/redact.js +66 -9
- package/dist/usage-analytics.d.ts +26 -0
- package/dist/usage-analytics.js +26 -3
- 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
|
-
|
|
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
|
-
|
|
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) {
|