@sema-agent/server 7.145.0 → 7.146.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +492 -0
- package/USAGE.md +218 -47
- package/deploy/facts/CHANGES.json +25 -0
- package/deploy/facts/README.md +57 -0
- package/deploy/facts/lanes.json +54 -0
- package/deploy/facts/lanes.schema.json +57 -0
- package/deploy/facts/sandbox-rbac.json +113 -0
- package/deploy/facts/sandbox-rbac.schema.json +74 -0
- package/deploy/facts/sealed-key.json +9 -0
- package/deploy/facts/sealed-key.schema.json +52 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
- package/deploy/sema-up/chart/templates/server.yaml +4 -0
- package/deploy/sema-up/chart/values.yaml +7 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
- package/dist/adoption/import-limits.d.ts +6 -0
- package/dist/adoption/import-limits.js +7 -0
- package/dist/adoption/import.d.ts +62 -0
- package/dist/adoption/import.js +107 -0
- package/dist/adoption/wire.d.ts +52 -0
- package/dist/adoption/wire.js +9 -0
- package/dist/approval-ask-machine.d.ts +2 -2
- package/dist/approval-card.d.ts +3 -12
- package/dist/approval-card.js +0 -4
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/approval-gate-currency.js +1 -1
- package/dist/approval-reconciler.d.ts +1 -1
- package/dist/boot/deployment-mods.d.ts +11 -1
- package/dist/boot/deployment-mods.js +15 -2
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/engine-lease.d.ts +193 -21
- package/dist/boot/engine-lease.js +146 -20
- package/dist/boot/execution-env.d.ts +4 -0
- package/dist/boot/execution-env.js +12 -0
- package/dist/boot/exit-boundary.d.ts +50 -0
- package/dist/boot/exit-boundary.js +32 -0
- package/dist/boot/exit-last.d.ts +20 -4
- package/dist/boot/exit-last.js +21 -4
- package/dist/boot/governance-seams.js +1 -0
- package/dist/boot/memory-consolidation.d.ts +29 -17
- package/dist/boot/memory-consolidation.js +26 -15
- package/dist/boot/reapers.d.ts +12 -4
- package/dist/boot/reapers.js +39 -15
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +38 -15
- package/dist/boot/runner-deps.js +1 -1
- package/dist/boot/session-faces.js +3 -1
- package/dist/boot/shutdown.js +27 -9
- package/dist/boot/stage-01-config.js +1 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +3 -0
- package/dist/boot/stage-05-execution-env.js +3 -3
- package/dist/boot/stage-06-runners.d.ts +3 -0
- package/dist/boot/stage-06-runners.js +11 -10
- package/dist/boot/stage-07-capability-layer.d.ts +3 -0
- package/dist/boot/stage-07-capability-layer.js +7 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -0
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +3 -0
- package/dist/boot/stage-10-http-server.d.ts +3 -0
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +35 -19
- package/dist/boot/workflow-orchestration.d.ts +1 -1
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/capabilities/composite-members.d.ts +3 -1
- package/dist/capabilities/composite-members.js +3 -1
- package/dist/capabilities/scenarios.d.ts +2 -2
- package/dist/capability-facts.d.ts +18 -1
- package/dist/capability-facts.js +25 -7
- package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
- package/dist/config-catalog.js +13 -4
- package/dist/config-provider.d.ts +2 -2
- package/dist/config-types.d.ts +43 -4
- package/dist/config.d.ts +6 -1
- package/dist/config.js +67 -11
- package/dist/deleted-session-ids.d.ts +19 -0
- package/dist/deleted-session-ids.js +26 -0
- package/dist/deploy-facts.d.ts +155 -0
- package/dist/deploy-facts.js +94 -0
- package/dist/device-ws-hub.d.ts +48 -3
- package/dist/device-ws-hub.js +48 -2
- package/dist/error-code-family.d.ts +35 -0
- package/dist/error-code-family.js +26 -0
- package/dist/fleet/drain-entry.d.ts +39 -3
- package/dist/fleet/drain-entry.js +6 -0
- package/dist/fleet/fleet-bus.d.ts +2 -1
- package/dist/hooks/branch-transcript.d.ts +1 -1
- package/dist/hooks/branch-transcript.js +1 -0
- package/dist/hooks/classic-stations.d.ts +25 -8
- package/dist/hooks/classic-stations.js +29 -3
- package/dist/hooks/hook-runner.d.ts +33 -16
- package/dist/hooks/hook-runner.js +166 -73
- package/dist/hooks/hook-source.d.ts +14 -2
- package/dist/hooks/hook-source.js +8 -0
- package/dist/http/active-run-conflict.d.ts +1 -1
- package/dist/http/active-run-conflict.js +1 -1
- package/dist/http/admission.js +11 -1
- package/dist/http/checkpoint-ctx.d.ts +59 -71
- package/dist/http/checkpoint-ctx.js +34 -40
- package/dist/http/dispatch.js +18 -2
- package/dist/http/durable-run-leg.d.ts +5 -0
- package/dist/http/durable-run-leg.js +1 -0
- package/dist/http/execution-context.d.ts +4 -0
- package/dist/http/execution-context.js +9 -0
- package/dist/http/health-self-report.d.ts +6 -2
- package/dist/http/health-self-report.js +3 -2
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.js +2 -15
- package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
- package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
- package/dist/http/principal-gate.d.ts +35 -12
- package/dist/http/principal-gate.js +16 -12
- package/dist/http/request-guards.d.ts +1 -1
- package/dist/http/request-guards.js +5 -5
- package/dist/http/resume-legs.d.ts +22 -0
- package/dist/http/resume-legs.js +26 -17
- package/dist/http/route-ctx.d.ts +11 -10
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.d.ts +11 -0
- package/dist/http/routes/adoption.js +80 -6
- package/dist/http/routes/agents-roster.js +1 -0
- package/dist/http/routes/approvals-assistant.js +2 -1
- package/dist/http/routes/background.js +1 -0
- package/dist/http/routes/capabilities.js +4 -1
- package/dist/http/routes/diagnostics.d.ts +9 -14
- package/dist/http/routes/diagnostics.js +10 -20
- package/dist/http/routes/engine.d.ts +31 -0
- package/dist/http/routes/engine.js +81 -0
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-consolidation.js +3 -5
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/memory-origin.js +2 -4
- package/dist/http/routes/memory-policy.js +5 -1
- package/dist/http/routes/observability.js +3 -6
- package/dist/http/routes/runs.js +7 -3
- package/dist/http/routes/session-sync.js +12 -25
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions.js +4 -0
- package/dist/http/routes/tasks.js +23 -5
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/run-concurrency-cap.d.ts +86 -0
- package/dist/http/run-concurrency-cap.js +54 -0
- package/dist/http/server.d.ts +15 -12
- package/dist/http/server.js +18 -3
- package/dist/http/stored-body-replay.d.ts +9 -6
- package/dist/http/stored-body-replay.js +19 -5
- package/dist/http/wire-types.d.ts +8 -3
- package/dist/json-body-limit.d.ts +8 -0
- package/dist/json-body-limit.js +2 -0
- package/dist/leader/diffout.d.ts +1 -1
- package/dist/local-data-root.d.ts +2 -2
- package/dist/memory-operator-faces.d.ts +9 -4
- package/dist/memory-scope-stores.d.ts +9 -7
- package/dist/memory-scope-stores.js +3 -14
- package/dist/memory-scope.d.ts +36 -0
- package/dist/memory-scope.js +32 -1
- package/dist/net-address.d.ts +53 -0
- package/dist/net-address.js +111 -0
- package/dist/observability/fail-open.d.ts +14 -10
- package/dist/observability/fail-open.js +14 -10
- package/dist/orchestration/workflow-notify-journal.js +2 -3
- package/dist/org-delivery-gate.js +1 -1
- package/dist/peer-directory.d.ts +6 -12
- package/dist/peer-directory.js +5 -1
- package/dist/plugins/caching-session-store.d.ts +3 -1
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-face.d.ts +426 -0
- package/dist/plugins/checkpoint-store-face.js +152 -0
- package/dist/plugins/file-run-store.d.ts +0 -6
- package/dist/plugins/file-run-store.js +1 -12
- package/dist/plugins/fork-routing-session-store.d.ts +6 -2
- package/dist/plugins/fork-routing-session-store.js +2 -2
- package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
- package/dist/plugins/local-checkpoint-store.d.ts +2 -2
- package/dist/plugins/local-checkpoint-store.js +2 -1
- package/dist/plugins/local-session-store.d.ts +28 -5
- package/dist/plugins/local-session-store.js +32 -8
- package/dist/plugins/memory-engine-pg.d.ts +29 -6
- package/dist/plugins/memory-engine-pg.js +96 -214
- package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
- package/dist/plugins/memory-engine-sql-apply.js +358 -0
- package/dist/plugins/memory-engine-tidb.d.ts +35 -9
- package/dist/plugins/memory-engine-tidb.js +97 -227
- package/dist/plugins/memory-key-guards.d.ts +1 -1
- package/dist/plugins/memory-origin-law.d.ts +3 -3
- package/dist/plugins/one-time-migrations.d.ts +32 -56
- package/dist/plugins/one-time-migrations.js +25 -117
- package/dist/plugins/permission-rule-store-file.d.ts +17 -3
- package/dist/plugins/permission-rule-store-file.js +8 -1
- package/dist/plugins/pg-pool.js +1 -1
- package/dist/plugins/pg-session-storage.d.ts +17 -4
- package/dist/plugins/pg-session-storage.js +8 -4
- package/dist/plugins/run-store-sql.js +2 -2
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/session-fork.d.ts +31 -9
- package/dist/plugins/session-fork.js +3 -2
- package/dist/plugins/session-placement.d.ts +4 -2
- package/dist/plugins/sql-json-column.d.ts +5 -1
- package/dist/plugins/store-backend.d.ts +11 -8
- package/dist/plugins/store-backend.js +8 -7
- package/dist/plugins/store-contracts.d.ts +1 -11
- package/dist/plugins/store-contracts.js +0 -9
- package/dist/plugins/store-sweep.d.ts +63 -0
- package/dist/plugins/store-sweep.js +48 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/tidb-session-store.d.ts +16 -3
- package/dist/plugins/tidb-session-store.js +8 -4
- package/dist/prompt-screen.d.ts +6 -9
- package/dist/prompt-screen.js +2 -5
- package/dist/rules-consent.d.ts +20 -27
- package/dist/rules-consent.js +82 -6
- package/dist/run-cancel-context.d.ts +4 -1
- package/dist/run-cancel-context.js +2 -1
- package/dist/runs.js +1 -1
- package/dist/sandbox-egress-policy.js +6 -11
- package/dist/sandbox-rbac-rules.d.ts +88 -0
- package/dist/sandbox-rbac-rules.js +80 -0
- package/dist/sealed-key-layout.d.ts +17 -0
- package/dist/sealed-key-layout.js +4 -0
- package/dist/sealed-key.js +5 -5
- package/dist/security.d.ts +38 -6
- package/dist/security.js +9 -2
- package/dist/session-notify-door.d.ts +5 -3
- package/dist/session-resident-background.d.ts +57 -0
- package/dist/session-resident-background.js +20 -0
- package/dist/session-sync-kernel.d.ts +53 -14
- package/dist/session-sync-kernel.js +70 -1
- package/dist/session-sync.d.ts +5 -3
- package/dist/session-workspace.d.ts +1 -0
- package/dist/session-workspace.js +1 -0
- package/dist/sighup-idle.d.ts +9 -7
- package/dist/sighup-idle.js +4 -5
- package/dist/spec-fields.d.ts +16 -0
- package/dist/spec-fields.js +7 -0
- package/dist/stored-request-env-credentials-m38.d.ts +3 -0
- package/dist/stored-request-env-credentials-m38.js +8 -0
- package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +13 -427
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +33 -194
- package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
- package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
- package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
- package/dist/task-settings.d.ts +59 -4
- package/dist/task-settings.js +50 -4
- package/dist/tool-approval.d.ts +2 -2
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +6 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/project.js +9 -0
- package/dist/trace/redact.d.ts +13 -2
- package/dist/trace/redact.js +305 -24
- package/dist/trace/self-minted-wire.d.ts +326 -8
- package/dist/trace/self-minted-wire.js +125 -7
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/usage-analytics.d.ts +4 -0
- package/package.json +12 -4
- package/scripts/drain-check-local.mjs +8 -29
- package/scripts/drain-check.mysql.sql +3 -24
- package/scripts/drain-check.pg.sql +3 -24
- package/skills/update-config.md +1 -1
- package/wire-changes/server@7.146.0.json +3221 -0
- package/dist/approval-batch-census.d.ts +0 -27
- package/dist/approval-batch-census.js +0 -27
|
@@ -7,7 +7,7 @@ import { findUrlCredential, formatConfigPath, outgoingPath } from "@sema-agent/s
|
|
|
7
7
|
import { httpHookEnvVarsAllowed, httpHookUrlVerdict } from "@sema-agent/settings-schema";
|
|
8
8
|
import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
|
|
9
9
|
import { describeToolCatalog } from "@sema-agent/core";
|
|
10
|
-
import { hangClassic } from "./classic-stations.js";
|
|
10
|
+
import { hangClassic, wrapClassic } from "./classic-stations.js";
|
|
11
11
|
import { MAX_HOOK_NOTICE_TEXT_CHARS, boundedNoticeText } from "../fleet/fleet-bus.js";
|
|
12
12
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
13
13
|
import { LIMIT_REASONS } from "../limit-refusal.js";
|
|
@@ -19,7 +19,7 @@ import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTP
|
|
|
19
19
|
import { ccAgentHookSystemFor } from "./cc-agent-hook-prompt.js";
|
|
20
20
|
import { renderBranchTranscript } from "./branch-transcript.js";
|
|
21
21
|
import { buildRequestUrlCredentialRefusal, urlCredentialHeaderExit } from "../url-credential-refusal.js";
|
|
22
|
-
import { HOOK_PHASES, HOOK_SOURCE_VOCABULARY_SENTENCE, hookPhaseOf, readHookGroupSource } from "./hook-source.js";
|
|
22
|
+
import { HOOK_PHASES, HOOK_SOURCE_VOCABULARY_SENTENCE, hookLaneOf, hookPhaseOf, readHookGroupSource } from "./hook-source.js";
|
|
23
23
|
import { commandLineChars, composeHookEnvLayer, hookPluginChars, planCommandSpawn, readHookArgs, readHookPlugin } from "./command-spawn.js";
|
|
24
24
|
export const MAX_HOOK_ENTRIES_PER_EVENT = 32;
|
|
25
25
|
export const MAX_HOOK_COMMAND_CHARS = 8_192;
|
|
@@ -52,14 +52,27 @@ export const SETTINGS_HOOK_STATIONS = {
|
|
|
52
52
|
PreCompact: ["classic.PreCompact"],
|
|
53
53
|
PostCompact: ["classic.PostCompact"],
|
|
54
54
|
};
|
|
55
|
-
function
|
|
55
|
+
function chainsBelowTaskOn(patterns, station) {
|
|
56
|
+
return patterns.some((p) => p === station || (p.endsWith(".*") && station.startsWith(p.slice(0, -1))));
|
|
57
|
+
}
|
|
58
|
+
function managedTightenOnlyOn(lane, station, belowTaskChainPatterns) {
|
|
59
|
+
return lane === "managed" && chainsBelowTaskOn(belowTaskChainPatterns, station);
|
|
60
|
+
}
|
|
61
|
+
const MANAGED_REWRITE_UNSHOWN_REASON = "a managed PreToolUse hook rewrote this call's input, but this deployment runs its own handler for this call after the request's hooks and that handler never sees a rewrite made above it, so the rewrite cannot be honored and the call was not executed";
|
|
62
|
+
const MANAGED_ALLOW_UNJUDGED_REASON = "a managed PreToolUse hook allowed this call, but this deployment runs its own handler for this call after the request's hooks, and a request hook's allow does not stand in for that handler's judgment, so the call was not executed (the same outcome as the same hook without a managed source)";
|
|
63
|
+
function hangTranslator(on, event, translators, lane) {
|
|
56
64
|
const translate = translators[event];
|
|
57
65
|
if (translate === undefined)
|
|
58
66
|
return;
|
|
67
|
+
const hang = lane === "managed" ? wrapClassic : hangClassic;
|
|
59
68
|
for (const station of SETTINGS_HOOK_STATIONS[event])
|
|
60
|
-
|
|
69
|
+
hang(on, station, translate);
|
|
61
70
|
}
|
|
62
|
-
export const
|
|
71
|
+
export const SETTINGS_HOOKS_REGISTRATIONS = {
|
|
72
|
+
managed: "settings.hooks.managed",
|
|
73
|
+
unmanaged: "settings.hooks",
|
|
74
|
+
};
|
|
75
|
+
const LANE_CHAIN_ORDER = ["managed", "unmanaged"];
|
|
63
76
|
export const DEFAULT_SETTINGS_HOOKS_BUDGET_MS = MAX_HOOK_TIMEOUT_SECONDS * 1000;
|
|
64
77
|
export function settingsHooksBudgetMs(config) {
|
|
65
78
|
return config.hooksTimeoutMs ?? DEFAULT_SETTINGS_HOOKS_BUDGET_MS;
|
|
@@ -650,7 +663,7 @@ function noteVerdictUntrusted(ctx, args) {
|
|
|
650
663
|
kind: "hook_verdict_untrusted",
|
|
651
664
|
event: args.event,
|
|
652
665
|
reason: args.reason,
|
|
653
|
-
lane:
|
|
666
|
+
lane: hookLaneOf(args.source),
|
|
654
667
|
hookName: boundedNoticeText(hookDisplayName(args.entry)),
|
|
655
668
|
entryType: args.entry.type,
|
|
656
669
|
...(args.toolName !== undefined ? { toolName: args.toolName } : {}),
|
|
@@ -692,15 +705,22 @@ const VERDICT_GAP_CAUSE = {
|
|
|
692
705
|
};
|
|
693
706
|
function createUnrunLedger(ctx, event, toolName) {
|
|
694
707
|
let managedVerdictGap;
|
|
695
|
-
|
|
708
|
+
let haltNoted = false;
|
|
709
|
+
const ledger = {
|
|
710
|
+
halted(source, entry) {
|
|
711
|
+
if (haltNoted)
|
|
712
|
+
return;
|
|
713
|
+
haltNoted = true;
|
|
714
|
+
ledger.unrun(source, entry, "budget_exhausted", ENGINE_BUDGET_SPENT_DETAIL);
|
|
715
|
+
},
|
|
696
716
|
untrusted(source, entry, reason) {
|
|
697
|
-
if (
|
|
717
|
+
if (hookLaneOf(source) === "unmanaged")
|
|
698
718
|
return;
|
|
699
719
|
managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
|
|
700
720
|
noteVerdictUntrusted(ctx, { event, reason, entry, source, toolName });
|
|
701
721
|
},
|
|
702
722
|
unrun(source, entry, reason, detail) {
|
|
703
|
-
if (source === undefined ||
|
|
723
|
+
if (source === undefined || hookLaneOf(source) === "unmanaged")
|
|
704
724
|
return;
|
|
705
725
|
managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
|
|
706
726
|
ctx.logger.warn("hook_managed_unrun", { event, reason, source: echoCallerText(source), type: entry.type, ...(toolName !== undefined ? { tool: toolName } : {}) });
|
|
@@ -721,6 +741,7 @@ function createUnrunLedger(ctx, event, toolName) {
|
|
|
721
741
|
return managedVerdictGap;
|
|
722
742
|
},
|
|
723
743
|
};
|
|
744
|
+
return ledger;
|
|
724
745
|
}
|
|
725
746
|
function noteExitNonzero(ctx, ledger, event, ran, toolName, detail) {
|
|
726
747
|
logHookCommandFailed(ctx, event, ran.run);
|
|
@@ -766,6 +787,36 @@ function buildHookContextField(parts) {
|
|
|
766
787
|
const text = buildHookContext(parts);
|
|
767
788
|
return text === undefined ? {} : { additionalContext: text };
|
|
768
789
|
}
|
|
790
|
+
function belowString(below, key) {
|
|
791
|
+
if (typeof below !== "object" || below === null || !Object.hasOwn(below, key))
|
|
792
|
+
return undefined;
|
|
793
|
+
const value = Reflect.get(below, key);
|
|
794
|
+
return typeof value === "string" ? value : undefined;
|
|
795
|
+
}
|
|
796
|
+
function belowMembers(below) {
|
|
797
|
+
return typeof below === "object" && below !== null ? { ...below } : {};
|
|
798
|
+
}
|
|
799
|
+
function withBelowContext(parts, below) {
|
|
800
|
+
const tail = belowString(below, "additionalContext");
|
|
801
|
+
return tail === undefined ? [...parts] : [...parts, tail];
|
|
802
|
+
}
|
|
803
|
+
function readBelowPreToolUse(below) {
|
|
804
|
+
const action = belowString(below, "action");
|
|
805
|
+
const message = belowString(below, "message") ?? "a PreToolUse hook requires approval";
|
|
806
|
+
const cause = belowString(below, "questionCause");
|
|
807
|
+
const rewrite = typeof below === "object" && below !== null && Object.hasOwn(below, "updatedInput") ? Reflect.get(below, "updatedInput") : undefined;
|
|
808
|
+
const additionalContext = belowString(below, "additionalContext");
|
|
809
|
+
const unreadableCause = cause === "verdict_unfinished" ? "verdict_unfinished" : "verdict_unreadable";
|
|
810
|
+
return {
|
|
811
|
+
denied: action === "deny",
|
|
812
|
+
allowed: action === "allow",
|
|
813
|
+
...(action === "ask" && (cause === undefined || cause === "asked") ? { asked: message } : {}),
|
|
814
|
+
...(action === "ask" && cause !== undefined && cause !== "asked" ? { unreadable: { message, cause: unreadableCause } } : {}),
|
|
815
|
+
realApproval: action === "ask" && typeof below === "object" && below !== null && Reflect.get(below, "requiresRealApproval") === true,
|
|
816
|
+
...(rewrite !== null && typeof rewrite === "object" ? { updatedInput: rewrite } : {}),
|
|
817
|
+
...(additionalContext !== undefined ? { additionalContext } : {}),
|
|
818
|
+
};
|
|
819
|
+
}
|
|
769
820
|
function spawnPlannedCommand(plan, ctx, stdio) {
|
|
770
821
|
const env = hookChildEnv(plan.env, true);
|
|
771
822
|
if (plan.kind === "exec")
|
|
@@ -1190,7 +1241,7 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
1190
1241
|
const inputTruncated = boundedToolInputPayload(payload) !== payload;
|
|
1191
1242
|
const truncatedFor = (entry) => (entry.type === "prompt" || entry.type === "agent" ? llmHookArguments(payload).truncated : inputTruncated);
|
|
1192
1243
|
const settle = async (entry, onceKey) => {
|
|
1193
|
-
if (halt.aborted)
|
|
1244
|
+
if (halt.signal.aborted)
|
|
1194
1245
|
return { kind: "halted" };
|
|
1195
1246
|
if (entry.once && onceFired.has(onceKey))
|
|
1196
1247
|
return { kind: "passed" };
|
|
@@ -1234,7 +1285,7 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
1234
1285
|
if (entry.type === "command" && (entry.async || entry.asyncRewake)) {
|
|
1235
1286
|
if (entry.once)
|
|
1236
1287
|
onceFired.add(onceKey);
|
|
1237
|
-
void fireAsyncCommandHook(entry, payload, ctx, event, halt);
|
|
1288
|
+
void fireAsyncCommandHook(entry, payload, ctx, event, halt.signal);
|
|
1238
1289
|
return { kind: "passed" };
|
|
1239
1290
|
}
|
|
1240
1291
|
if (Date.now() > deadlineAt) {
|
|
@@ -1245,12 +1296,14 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
1245
1296
|
onceFired.add(onceKey);
|
|
1246
1297
|
if (entry.type === "prompt" || entry.type === "agent")
|
|
1247
1298
|
llmEntries++;
|
|
1248
|
-
const run = await runRunnableEntry(entry, payload, ctx, event, llmExtra, halt);
|
|
1299
|
+
const run = await runRunnableEntry(entry, payload, ctx, event, llmExtra, halt.signal);
|
|
1249
1300
|
if (run.aborted !== undefined) {
|
|
1250
1301
|
if (run.aborted.signal !== undefined)
|
|
1251
1302
|
ctx.logger.warn("hook_command_killed", { event, type: entry.type, killed: true, signal: run.aborted.signal });
|
|
1252
1303
|
return { kind: "halted" };
|
|
1253
1304
|
}
|
|
1305
|
+
if (halt.signal.aborted)
|
|
1306
|
+
return { kind: "halted" };
|
|
1254
1307
|
if (run.withheld !== undefined) {
|
|
1255
1308
|
const { list, rule, sources } = run.withheld;
|
|
1256
1309
|
ctx.logger.warn("hook_http_withheld", { event, url: entry.type === "http" ? buildDisplaySafeUrl(entry.url) : null, list, rule, sources: [...sources] });
|
|
@@ -1307,6 +1360,8 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
1307
1360
|
case "passed":
|
|
1308
1361
|
break;
|
|
1309
1362
|
case "halted":
|
|
1363
|
+
if (isSyncEntry(entry) && halt.budgetSpent())
|
|
1364
|
+
ledger.halted(group.source, entry);
|
|
1310
1365
|
return results;
|
|
1311
1366
|
default: {
|
|
1312
1367
|
const unreachable = fate;
|
|
@@ -1317,14 +1372,12 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
1317
1372
|
}
|
|
1318
1373
|
return results;
|
|
1319
1374
|
}
|
|
1320
|
-
function
|
|
1321
|
-
|
|
1322
|
-
return (
|
|
1323
|
-
const lane = phase === "unmanaged" ? "unmanaged" : "managed";
|
|
1324
|
-
return (startedAt[lane] ??= Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
|
|
1325
|
-
};
|
|
1375
|
+
function createEventClock() {
|
|
1376
|
+
let deadlineAt;
|
|
1377
|
+
return () => (deadlineAt ??= Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
|
|
1326
1378
|
}
|
|
1327
|
-
|
|
1379
|
+
const ENGINE_BUDGET_SPENT_DETAIL = "the managed lane's handler ran past its engine budget (HOOKS_TIMEOUT_MS); the managed entries after this one did not run on this event";
|
|
1380
|
+
function hookPhasesOf(groups) {
|
|
1328
1381
|
const indexed = groups.map((group, index) => ({ group, index, phase: hookPhaseOf(group.source) }));
|
|
1329
1382
|
return HOOK_PHASES.map((phase) => ({ phase, groups: indexed.filter((g) => g.phase === phase).map(({ group, index }) => ({ group, index })) })).filter((l) => l.groups.length > 0);
|
|
1330
1383
|
}
|
|
@@ -1332,12 +1385,10 @@ function trustRank(phase) {
|
|
|
1332
1385
|
return HOOK_PHASES.length - 1 - HOOK_PHASES.indexOf(phase);
|
|
1333
1386
|
}
|
|
1334
1387
|
async function runEventLanes(event, groups, matchValue, payload, ctx, onceFired, halt, ledger, llmExtra) {
|
|
1335
|
-
const
|
|
1388
|
+
const deadline = createEventClock();
|
|
1336
1389
|
const ran = [];
|
|
1337
|
-
for (const lane of
|
|
1338
|
-
|
|
1339
|
-
break;
|
|
1340
|
-
for (const r of await runMatchingEntries(event, lane.groups, matchValue, payload, ctx, onceFired, halt, deadlineFor(lane.phase), ledger, llmExtra))
|
|
1390
|
+
for (const lane of hookPhasesOf(groups)) {
|
|
1391
|
+
for (const r of await runMatchingEntries(event, lane.groups, matchValue, payload, ctx, onceFired, halt, deadline(), ledger, llmExtra))
|
|
1341
1392
|
ran.push({ ...r, phase: lane.phase });
|
|
1342
1393
|
}
|
|
1343
1394
|
return ran.sort((a, b) => trustRank(a.phase) - trustRank(b.phase)).map(({ entry, run, source }) => ({ entry, run, source }));
|
|
@@ -1372,12 +1423,26 @@ async function runObserveOnlyEvent(event, groups, matchValue, payload, ctx, once
|
|
|
1372
1423
|
}
|
|
1373
1424
|
}
|
|
1374
1425
|
}
|
|
1375
|
-
export function
|
|
1376
|
-
const
|
|
1377
|
-
const shellOwned = configured.filter((e) => HOOK_EVENT_OWNER[e] === "shell");
|
|
1426
|
+
export function createSettingsHooksRegistrations(config, ctx, budgetMs) {
|
|
1427
|
+
const shellOwned = Object.keys(config).filter((e) => (config[e]?.length ?? 0) > 0 && HOOK_EVENT_OWNER[e] === "shell");
|
|
1378
1428
|
if (shellOwned.length > 0) {
|
|
1379
1429
|
ctx.logger.debug("hook_events_shell_owned", { events: shellOwned });
|
|
1380
1430
|
}
|
|
1431
|
+
return LANE_CHAIN_ORDER.flatMap((lane) => {
|
|
1432
|
+
const registration = createLaneRegistration(lane, laneConfigOf(config, lane), ctx, budgetMs);
|
|
1433
|
+
return registration === undefined ? [] : [registration];
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
function laneConfigOf(config, lane) {
|
|
1437
|
+
const out = {};
|
|
1438
|
+
for (const event of Object.keys(config)) {
|
|
1439
|
+
const groups = config[event]?.filter((group) => hookLaneOf(group.source) === lane);
|
|
1440
|
+
if (groups !== undefined && groups.length > 0)
|
|
1441
|
+
out[event] = groups;
|
|
1442
|
+
}
|
|
1443
|
+
return out;
|
|
1444
|
+
}
|
|
1445
|
+
function createLaneRegistration(lane, config, ctx, budgetMs) {
|
|
1381
1446
|
const lit = (e) => {
|
|
1382
1447
|
const groups = config[e];
|
|
1383
1448
|
return groups && groups.length > 0 ? groups : undefined;
|
|
@@ -1397,17 +1462,23 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1397
1462
|
const onceFired = new Set();
|
|
1398
1463
|
const translators = {};
|
|
1399
1464
|
if (pre) {
|
|
1400
|
-
|
|
1465
|
+
const tightenOnly = managedTightenOnlyOn(lane, "classic.PreToolUse", ctx.belowTaskChainPatterns);
|
|
1466
|
+
translators.PreToolUse = async (e, halt, below) => {
|
|
1467
|
+
const prior = readBelowPreToolUse(below);
|
|
1468
|
+
const realApproval = prior.realApproval ? { requiresRealApproval: true } : {};
|
|
1469
|
+
if (prior.denied)
|
|
1470
|
+
return undefined;
|
|
1401
1471
|
const toolName = e.tool;
|
|
1402
1472
|
const ledger = createUnrunLedger(ctx, "PreToolUse", toolName);
|
|
1403
|
-
const
|
|
1404
|
-
let currentInput = e.input;
|
|
1405
|
-
let rewritten =
|
|
1406
|
-
let asked;
|
|
1407
|
-
let
|
|
1473
|
+
const deadline = createEventClock();
|
|
1474
|
+
let currentInput = prior.updatedInput ?? e.input;
|
|
1475
|
+
let rewritten = prior.updatedInput !== undefined;
|
|
1476
|
+
let asked = prior.asked;
|
|
1477
|
+
let spoke = false;
|
|
1478
|
+
let allowedFor = prior.allowed ? { input: currentInput } : undefined;
|
|
1408
1479
|
let unknownDecision = false;
|
|
1409
1480
|
const contexts = { unmanaged: [], managedPlugin: [], managedPolicy: [] };
|
|
1410
|
-
const contextField = () => buildHookContextField([...HOOK_PHASES].sort((a, b) => trustRank(a) - trustRank(b)).flatMap((p) => contexts[p]));
|
|
1481
|
+
const contextField = () => buildHookContextField([...[...HOOK_PHASES].sort((a, b) => trustRank(a) - trustRank(b)).flatMap((p) => contexts[p]), ...(prior.additionalContext !== undefined ? [prior.additionalContext] : [])]);
|
|
1411
1482
|
const runPass = async (phase, groups, once) => {
|
|
1412
1483
|
let rewroteBy;
|
|
1413
1484
|
for (const laneGroup of groups) {
|
|
@@ -1419,7 +1490,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1419
1490
|
tool_input: groupInput,
|
|
1420
1491
|
tool_use_id: e.tool_use_id,
|
|
1421
1492
|
};
|
|
1422
|
-
const singles = await runMatchingEntries("PreToolUse", [laneGroup], toolName, payload, ctx, once,
|
|
1493
|
+
const singles = await runMatchingEntries("PreToolUse", [laneGroup], toolName, payload, ctx, once, halt, deadline(), ledger);
|
|
1423
1494
|
for (const ran of singles) {
|
|
1424
1495
|
const { entry, run } = ran;
|
|
1425
1496
|
if (run.code === 2) {
|
|
@@ -1446,20 +1517,26 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1446
1517
|
const hso = out.hookSpecificOutput;
|
|
1447
1518
|
if (hso !== null && typeof hso === "object" && hso.hookEventName === "PreToolUse") {
|
|
1448
1519
|
const h = hso;
|
|
1449
|
-
if (typeof h.additionalContext === "string")
|
|
1520
|
+
if (typeof h.additionalContext === "string") {
|
|
1450
1521
|
contexts[phase].push(clipDataText(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
|
|
1522
|
+
spoke = true;
|
|
1523
|
+
}
|
|
1451
1524
|
const decision = h.permissionDecision;
|
|
1452
1525
|
if (decision === "deny") {
|
|
1453
1526
|
const reason = clipDataText(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "denied by a PreToolUse hook", MAX_HOOK_FEEDBACK_CHARS);
|
|
1454
1527
|
return { deny: { action: "deny", message: reason, ...contextField() } };
|
|
1455
1528
|
}
|
|
1529
|
+
const proposed = h.updatedInput !== null && typeof h.updatedInput === "object" ? h.updatedInput : undefined;
|
|
1530
|
+
const proposesRewrite = proposed !== undefined && canonicalJsonStringify(proposed) !== canonicalJsonStringify(groupInput);
|
|
1531
|
+
if (tightenOnly && ran.distrust === undefined && (decision === "allow" || (proposesRewrite && (decision === "ask" || decision === undefined)))) {
|
|
1532
|
+
return { deny: { action: "deny", message: proposesRewrite ? MANAGED_REWRITE_UNSHOWN_REASON : MANAGED_ALLOW_UNJUDGED_REASON, ...contextField() } };
|
|
1533
|
+
}
|
|
1456
1534
|
if (decision === "ask") {
|
|
1457
1535
|
asked = clipDataText(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "a PreToolUse hook requires approval", MAX_HOOK_FEEDBACK_CHARS);
|
|
1536
|
+
spoke = true;
|
|
1458
1537
|
}
|
|
1459
1538
|
if (decision === "allow" || decision === undefined) {
|
|
1460
|
-
|
|
1461
|
-
const proposesRewrite = proposed !== undefined && canonicalJsonStringify(proposed) !== canonicalJsonStringify(groupInput);
|
|
1462
|
-
if (ran.distrust !== undefined && hookPhaseOf(ran.source) === "unmanaged" && (decision === "allow" || proposesRewrite)) {
|
|
1539
|
+
if (ran.distrust !== undefined && hookLaneOf(ran.source) === "unmanaged" && (decision === "allow" || proposesRewrite)) {
|
|
1463
1540
|
noteVerdictUntrusted(ctx, { event: "PreToolUse", reason: ran.distrust, entry, source: ran.source, toolName });
|
|
1464
1541
|
}
|
|
1465
1542
|
const offered = ran.distrust === undefined ? proposed : undefined;
|
|
@@ -1468,12 +1545,16 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1468
1545
|
currentInput = offered;
|
|
1469
1546
|
rewritten = true;
|
|
1470
1547
|
rewroteBy = ran;
|
|
1548
|
+
spoke = true;
|
|
1471
1549
|
}
|
|
1472
|
-
if (decision === "allow" && ran.distrust === undefined)
|
|
1550
|
+
if (decision === "allow" && ran.distrust === undefined) {
|
|
1473
1551
|
allowedFor = { input: rewrites ? offered : groupInput };
|
|
1552
|
+
spoke = true;
|
|
1553
|
+
}
|
|
1474
1554
|
}
|
|
1475
1555
|
else if (decision !== "deny" && decision !== "ask") {
|
|
1476
1556
|
unknownDecision = true;
|
|
1557
|
+
spoke = true;
|
|
1477
1558
|
ctx.logger.warn("hook_permission_decision_unknown", {
|
|
1478
1559
|
event: "PreToolUse",
|
|
1479
1560
|
decision: seatText("operator-log-text", typeof decision === "string" ? decision : typeof decision),
|
|
@@ -1487,7 +1568,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1487
1568
|
}
|
|
1488
1569
|
return { rewroteBy };
|
|
1489
1570
|
};
|
|
1490
|
-
for (const lane of
|
|
1571
|
+
for (const lane of hookPhasesOf(pre)) {
|
|
1491
1572
|
if (lane.phase === "unmanaged") {
|
|
1492
1573
|
const pass = await runPass(lane.phase, lane.groups, onceFired);
|
|
1493
1574
|
if (pass.deny !== undefined)
|
|
@@ -1511,38 +1592,45 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1511
1592
|
ledger.unrun(second.rewroteBy.source, second.rewroteBy.entry, "rewrite_unsettled", "the managed hooks of this phase rewrote the tool input again after being re-run on their own rewrite; approval required");
|
|
1512
1593
|
}
|
|
1513
1594
|
}
|
|
1595
|
+
const managedGap = ledger.managedVerdictGap;
|
|
1596
|
+
if (below !== undefined && !spoke && managedGap === undefined)
|
|
1597
|
+
return undefined;
|
|
1514
1598
|
if (asked !== undefined) {
|
|
1515
|
-
return { action: "ask", message: asked, questionCause: "asked", ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
|
|
1599
|
+
return { action: "ask", message: asked, questionCause: "asked", ...(rewritten ? { updatedInput: currentInput } : {}), ...realApproval, ...contextField() };
|
|
1516
1600
|
}
|
|
1517
|
-
const managedGap = ledger.managedVerdictGap;
|
|
1518
1601
|
if (managedGap !== undefined) {
|
|
1519
1602
|
return {
|
|
1520
1603
|
action: "ask",
|
|
1521
1604
|
message: "a managed PreToolUse hook did not run to a verdict — approval required",
|
|
1522
1605
|
questionCause: managedGap,
|
|
1523
1606
|
...(rewritten ? { updatedInput: currentInput } : {}),
|
|
1607
|
+
...realApproval,
|
|
1524
1608
|
...contextField(),
|
|
1525
1609
|
};
|
|
1526
1610
|
}
|
|
1611
|
+
if (prior.unreadable !== undefined) {
|
|
1612
|
+
return { action: "ask", message: prior.unreadable.message, questionCause: prior.unreadable.cause, ...(rewritten ? { updatedInput: currentInput } : {}), ...realApproval, ...contextField() };
|
|
1613
|
+
}
|
|
1527
1614
|
if (unknownDecision) {
|
|
1528
1615
|
return {
|
|
1529
1616
|
action: "ask",
|
|
1530
1617
|
message: "a PreToolUse hook returned an unrecognized permissionDecision — approval required",
|
|
1531
1618
|
questionCause: "verdict_unreadable",
|
|
1532
1619
|
...(rewritten ? { updatedInput: currentInput } : {}),
|
|
1620
|
+
...realApproval,
|
|
1533
1621
|
...contextField(),
|
|
1534
1622
|
};
|
|
1535
1623
|
}
|
|
1536
1624
|
const carried = { ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
|
|
1537
1625
|
if (allowedFor !== undefined && allowedFor.input === currentInput)
|
|
1538
1626
|
return { action: "allow", ...carried };
|
|
1539
|
-
if (rewritten || HOOK_PHASES.some((p) => contexts[p].length > 0))
|
|
1627
|
+
if (rewritten || HOOK_PHASES.some((p) => contexts[p].length > 0) || prior.additionalContext !== undefined)
|
|
1540
1628
|
return carried;
|
|
1541
1629
|
return undefined;
|
|
1542
1630
|
};
|
|
1543
1631
|
}
|
|
1544
1632
|
if (post) {
|
|
1545
|
-
translators.PostToolUse = async (e,
|
|
1633
|
+
translators.PostToolUse = async (e, halt, below) => {
|
|
1546
1634
|
const toolName = e.tool;
|
|
1547
1635
|
const input = e.input;
|
|
1548
1636
|
const output = e.output;
|
|
@@ -1559,7 +1647,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1559
1647
|
tool_use_id: e.tool_use_id,
|
|
1560
1648
|
};
|
|
1561
1649
|
const ledger = createUnrunLedger(ctx, "PostToolUse", toolName);
|
|
1562
|
-
const singles = await runEventLanes("PostToolUse", post, toolName, payload, ctx, onceFired,
|
|
1650
|
+
const singles = await runEventLanes("PostToolUse", post, toolName, payload, ctx, onceFired, halt, ledger);
|
|
1563
1651
|
for (const ran of singles) {
|
|
1564
1652
|
const { entry, run } = ran;
|
|
1565
1653
|
if (run.code === 2) {
|
|
@@ -1592,12 +1680,13 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1592
1680
|
}
|
|
1593
1681
|
}
|
|
1594
1682
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1683
|
+
if (contexts.length === 0)
|
|
1684
|
+
return undefined;
|
|
1685
|
+
return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
|
|
1597
1686
|
};
|
|
1598
1687
|
}
|
|
1599
1688
|
if (postFailure) {
|
|
1600
|
-
translators.PostToolUseFailure = async (e,
|
|
1689
|
+
translators.PostToolUseFailure = async (e, halt, below) => {
|
|
1601
1690
|
const toolName = e.tool;
|
|
1602
1691
|
const f = e.failure;
|
|
1603
1692
|
const contexts = [];
|
|
@@ -1611,7 +1700,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1611
1700
|
is_interrupt: f.isInterrupt,
|
|
1612
1701
|
};
|
|
1613
1702
|
const ledger = createUnrunLedger(ctx, "PostToolUseFailure", toolName);
|
|
1614
|
-
const singles = await runEventLanes("PostToolUseFailure", postFailure, toolName, payload, ctx, onceFired,
|
|
1703
|
+
const singles = await runEventLanes("PostToolUseFailure", postFailure, toolName, payload, ctx, onceFired, halt, ledger);
|
|
1615
1704
|
for (const ran of singles) {
|
|
1616
1705
|
const { entry, run } = ran;
|
|
1617
1706
|
if (run.code === 2) {
|
|
@@ -1640,12 +1729,13 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1640
1729
|
contexts.push(clipDataText(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
|
|
1641
1730
|
}
|
|
1642
1731
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1732
|
+
if (contexts.length === 0)
|
|
1733
|
+
return undefined;
|
|
1734
|
+
return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
|
|
1645
1735
|
};
|
|
1646
1736
|
}
|
|
1647
1737
|
if (postBatch) {
|
|
1648
|
-
translators.PostToolBatch = async (e,
|
|
1738
|
+
translators.PostToolBatch = async (e, halt, below) => {
|
|
1649
1739
|
const contexts = [];
|
|
1650
1740
|
const payload = {
|
|
1651
1741
|
...basePayload(ctx, e.permissionMode),
|
|
@@ -1658,7 +1748,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1658
1748
|
})),
|
|
1659
1749
|
};
|
|
1660
1750
|
const ledger = createUnrunLedger(ctx, "PostToolBatch", undefined);
|
|
1661
|
-
const singles = await runEventLanes("PostToolBatch", ignoreMatchers(postBatch), "", payload, ctx, onceFired,
|
|
1751
|
+
const singles = await runEventLanes("PostToolBatch", ignoreMatchers(postBatch), "", payload, ctx, onceFired, halt, ledger);
|
|
1662
1752
|
for (const ran of singles) {
|
|
1663
1753
|
const { entry, run } = ran;
|
|
1664
1754
|
if (run.code === 2) {
|
|
@@ -1683,16 +1773,17 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1683
1773
|
if (out.hookSpecificOutput !== undefined)
|
|
1684
1774
|
ctx.logger.warn("hook_result_unsupported", { event: "PostToolBatch" });
|
|
1685
1775
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1776
|
+
if (contexts.length === 0)
|
|
1777
|
+
return undefined;
|
|
1778
|
+
return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
|
|
1688
1779
|
};
|
|
1689
1780
|
}
|
|
1690
1781
|
if (promptSubmit) {
|
|
1691
|
-
translators.UserPromptSubmit = async (e,
|
|
1782
|
+
translators.UserPromptSubmit = async (e, halt, below) => {
|
|
1692
1783
|
const contexts = [];
|
|
1693
1784
|
const payload = { ...basePayload(ctx, e.permissionMode), hook_event_name: "UserPromptSubmit", prompt: clipDataText(e.prompt, MAX_TOOL_INPUT_CHARS) };
|
|
1694
1785
|
const ledger = createUnrunLedger(ctx, "UserPromptSubmit", undefined);
|
|
1695
|
-
const singles = await runEventLanes("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, onceFired,
|
|
1786
|
+
const singles = await runEventLanes("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, onceFired, halt, ledger);
|
|
1696
1787
|
for (const ran of singles) {
|
|
1697
1788
|
const { entry, run } = ran;
|
|
1698
1789
|
if (run.code === 2) {
|
|
@@ -1723,12 +1814,13 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1723
1814
|
contexts.push(clipDataText(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
|
|
1724
1815
|
}
|
|
1725
1816
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1817
|
+
if (contexts.length === 0)
|
|
1818
|
+
return undefined;
|
|
1819
|
+
return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
|
|
1728
1820
|
};
|
|
1729
1821
|
}
|
|
1730
1822
|
if (stopEntries) {
|
|
1731
|
-
translators.Stop = async (sctx,
|
|
1823
|
+
translators.Stop = async (sctx, halt, below) => {
|
|
1732
1824
|
const payload = { ...basePayload(ctx, sctx.permissionMode), hook_event_name: "Stop", stop_hook_active: sctx.stopHookActive };
|
|
1733
1825
|
let llmExtra;
|
|
1734
1826
|
const wantsPrompt = stopEntries.some((g) => g.hooks.some((h) => h.type === "prompt"));
|
|
@@ -1766,7 +1858,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1766
1858
|
const effectiveGroups = llmExtra || !wantsPrompt
|
|
1767
1859
|
? stopGroups
|
|
1768
1860
|
: stopGroups.map((g) => ({ ...g, hooks: g.hooks.filter((h) => h.type !== "prompt") })).filter((g) => g.hooks.length > 0);
|
|
1769
|
-
const singles = await runEventLanes("Stop", effectiveGroups, "", payload, ctx, onceFired,
|
|
1861
|
+
const singles = await runEventLanes("Stop", effectiveGroups, "", payload, ctx, onceFired, halt, ledger, llmExtra);
|
|
1770
1862
|
let block;
|
|
1771
1863
|
let sawContinueFalse = false;
|
|
1772
1864
|
const contexts = [];
|
|
@@ -1821,13 +1913,14 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1821
1913
|
if (block === undefined && contexts.length === 0)
|
|
1822
1914
|
return undefined;
|
|
1823
1915
|
return {
|
|
1916
|
+
...belowMembers(below),
|
|
1824
1917
|
...(block !== undefined ? { block } : {}),
|
|
1825
|
-
...buildHookContextField(contexts),
|
|
1918
|
+
...buildHookContextField(withBelowContext(contexts, below)),
|
|
1826
1919
|
};
|
|
1827
1920
|
};
|
|
1828
1921
|
}
|
|
1829
1922
|
if (stopFailureEntries) {
|
|
1830
|
-
translators.StopFailure = async (fctx,
|
|
1923
|
+
translators.StopFailure = async (fctx, halt) => {
|
|
1831
1924
|
const turnError = toAssistantTurnError(fctx.errorKind);
|
|
1832
1925
|
const payload = {
|
|
1833
1926
|
...basePayload(ctx, undefined),
|
|
@@ -1835,11 +1928,11 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1835
1928
|
error: turnError,
|
|
1836
1929
|
...(fctx.error ? { error_details: clipDataText(redactSecrets(fctx.error), MAX_HOOK_FEEDBACK_CHARS) } : {}),
|
|
1837
1930
|
};
|
|
1838
|
-
await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, onceFired,
|
|
1931
|
+
await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, onceFired, halt);
|
|
1839
1932
|
};
|
|
1840
1933
|
}
|
|
1841
1934
|
if (preCompactEntries) {
|
|
1842
|
-
translators.PreCompact = async (pctx,
|
|
1935
|
+
translators.PreCompact = async (pctx, halt, below) => {
|
|
1843
1936
|
const payload = {
|
|
1844
1937
|
...basePayload(ctx, undefined),
|
|
1845
1938
|
hook_event_name: "PreCompact",
|
|
@@ -1847,11 +1940,11 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1847
1940
|
custom_instructions: pctx.customInstructions ?? null,
|
|
1848
1941
|
};
|
|
1849
1942
|
const ledger = createUnrunLedger(ctx, "PreCompact", undefined);
|
|
1850
|
-
const singles = await runEventLanes("PreCompact", preCompactEntries, pctx.trigger, payload, ctx, onceFired,
|
|
1943
|
+
const singles = await runEventLanes("PreCompact", preCompactEntries, pctx.trigger, payload, ctx, onceFired, halt, ledger);
|
|
1851
1944
|
for (const ran of singles) {
|
|
1852
1945
|
const { entry, run } = ran;
|
|
1853
1946
|
if (run.code === 2) {
|
|
1854
|
-
return { block: clipDataText(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreCompact hook" };
|
|
1947
|
+
return { ...belowMembers(below), block: clipDataText(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreCompact hook" };
|
|
1855
1948
|
}
|
|
1856
1949
|
if (run.code !== 0) {
|
|
1857
1950
|
noteExitNonzero(ctx, ledger, "PreCompact", ran);
|
|
@@ -1866,31 +1959,31 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1866
1959
|
ctx.logger.warn("hook_continue_false_unsupported", { event: "PreCompact" });
|
|
1867
1960
|
}
|
|
1868
1961
|
if (out.decision === "block") {
|
|
1869
|
-
return { block: clipDataText(typeof out.reason === "string" ? out.reason : "blocked by a PreCompact hook", MAX_HOOK_FEEDBACK_CHARS) };
|
|
1962
|
+
return { ...belowMembers(below), block: clipDataText(typeof out.reason === "string" ? out.reason : "blocked by a PreCompact hook", MAX_HOOK_FEEDBACK_CHARS) };
|
|
1870
1963
|
}
|
|
1871
1964
|
}
|
|
1872
1965
|
return undefined;
|
|
1873
1966
|
};
|
|
1874
1967
|
}
|
|
1875
1968
|
if (postCompactEntries) {
|
|
1876
|
-
translators.PostCompact = async (cctx,
|
|
1969
|
+
translators.PostCompact = async (cctx, halt) => {
|
|
1877
1970
|
const payload = {
|
|
1878
1971
|
...basePayload(ctx, undefined),
|
|
1879
1972
|
hook_event_name: "PostCompact",
|
|
1880
1973
|
trigger: cctx.trigger,
|
|
1881
1974
|
compact_summary: clipDataText(cctx.summary, MAX_TOOL_RESPONSE_CHARS),
|
|
1882
1975
|
};
|
|
1883
|
-
await runObserveOnlyEvent("PostCompact", postCompactEntries, cctx.trigger, payload, ctx, onceFired,
|
|
1976
|
+
await runObserveOnlyEvent("PostCompact", postCompactEntries, cctx.trigger, payload, ctx, onceFired, halt);
|
|
1884
1977
|
};
|
|
1885
1978
|
}
|
|
1886
1979
|
const events = Object.keys(translators);
|
|
1887
1980
|
return {
|
|
1888
|
-
name:
|
|
1981
|
+
name: SETTINGS_HOOKS_REGISTRATIONS[lane],
|
|
1889
1982
|
uses: { events: events.flatMap((event) => SETTINGS_HOOK_STATIONS[event]), calls: [] },
|
|
1890
1983
|
budgetMs,
|
|
1891
1984
|
register(on) {
|
|
1892
1985
|
for (const event of events)
|
|
1893
|
-
hangTranslator(on, event, translators);
|
|
1986
|
+
hangTranslator(on, event, translators, lane);
|
|
1894
1987
|
},
|
|
1895
1988
|
};
|
|
1896
1989
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 请求面**来源**闭集 —— 一个 hook 组(`settings.hooks[event][i].source`)是谁写的。词表与「托管与否」的映射**只在这里**;
|
|
3
|
-
* hooks
|
|
3
|
+
* hooks 执行器按它分相、分车道(`hook-runner.ts` 的三相 / 两张注册),将来请求面别处的来源键(MCP 服务器条目)读同一张词表,不另立第二份。
|
|
4
4
|
*
|
|
5
5
|
* 词表(闭集;集外词 = 形错,受理面整份拒,fail-closed;缺席 = 非托管):
|
|
6
6
|
* · `managedPolicy` —— 组织策略(托管设置)。**托管**,且是终判相。
|
|
@@ -28,7 +28,7 @@ type HookSourcePrefix = keyof typeof HOOK_SOURCE_PREFIX_PHASE;
|
|
|
28
28
|
export type HookGroupSource = HookSourceWord | `${HookSourcePrefix}:${string}`;
|
|
29
29
|
/**
|
|
30
30
|
* 执行相 —— **固定次序**就是这张数组的次序:非托管 → 托管插件 → 托管策略(终判)。后一相在前一相的最终输入上跑;结果按信任序(倒序)读。
|
|
31
|
-
* 托管 =
|
|
31
|
+
* 托管 = 后两相;相按 {@link hookLaneOf} 归两条车道(每道一张引擎注册,见 `hook-runner.ts`)。
|
|
32
32
|
*/
|
|
33
33
|
export declare const HOOK_PHASES: readonly ["unmanaged", "managedPlugin", "managedPolicy"];
|
|
34
34
|
export type HookPhase = (typeof HOOK_PHASES)[number];
|
|
@@ -36,6 +36,18 @@ export type HookPhase = (typeof HOOK_PHASES)[number];
|
|
|
36
36
|
export declare const MAX_HOOK_SOURCE_NAME_CHARS = 128;
|
|
37
37
|
/** 受理面的读法:合形 ⇒ 来源;否则 `undefined`(调用方铸拒)。只认 own 词(`Object.hasOwn`:原型上的名字不是词表成员)。 */
|
|
38
38
|
export declare function readHookGroupSource(raw: unknown): HookGroupSource | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* 车道 —— **一条车道 = 一张引擎注册**(`hook-runner.ts` 的 `SETTINGS_HOOKS_REGISTRATIONS`):非托管相一道;托管两相(插件相 + 策略相)同一道。
|
|
41
|
+
* 每条车道自带引擎的 handler 预算与本服务的事件墙钟 / LLM 条数 / 账本,两道之间不共用任何一只钟。相 → 道只在这张表(`satisfies`:加一个相不写道 = 编译红)。
|
|
42
|
+
*/
|
|
43
|
+
declare const HOOK_PHASE_LANE: {
|
|
44
|
+
readonly unmanaged: "unmanaged";
|
|
45
|
+
readonly managedPlugin: "managed";
|
|
46
|
+
readonly managedPolicy: "managed";
|
|
47
|
+
};
|
|
48
|
+
export type HookLane = (typeof HOOK_PHASE_LANE)[HookPhase];
|
|
49
|
+
/** 来源 → 车道(经相;缺席 ⇒ 非托管)。帧上的 `lane` 位、车道注册的分组都读这一只。 */
|
|
50
|
+
export declare function hookLaneOf(source: HookGroupSource | undefined): HookLane;
|
|
39
51
|
/** 来源 → 相。缺席 ⇒ 非托管(老包不带 `source`:行为与此前逐字同)。 */
|
|
40
52
|
export declare function hookPhaseOf(source: HookGroupSource | undefined): HookPhase;
|
|
41
53
|
/** 拒句里的词表(由两张表派生,不另写一份)。 */
|
|
@@ -27,6 +27,14 @@ export function readHookGroupSource(raw) {
|
|
|
27
27
|
return undefined;
|
|
28
28
|
return raw;
|
|
29
29
|
}
|
|
30
|
+
const HOOK_PHASE_LANE = {
|
|
31
|
+
unmanaged: "unmanaged",
|
|
32
|
+
managedPlugin: "managed",
|
|
33
|
+
managedPolicy: "managed",
|
|
34
|
+
};
|
|
35
|
+
export function hookLaneOf(source) {
|
|
36
|
+
return HOOK_PHASE_LANE[hookPhaseOf(source)];
|
|
37
|
+
}
|
|
30
38
|
export function hookPhaseOf(source) {
|
|
31
39
|
if (source === undefined)
|
|
32
40
|
return "unmanaged";
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { type GovernancePosture } from "../runtime-governance.js";
|
|
17
17
|
import type { ShellGateDoctrine } from "@sema-agent/core";
|
|
18
18
|
import { type RunRecord } from "../plugins/store-contracts.js";
|
|
19
|
-
import { type PendingRowBinding } from "../plugins/checkpoint-store-
|
|
19
|
+
import { type PendingRowBinding } from "../plugins/checkpoint-store-face.js";
|
|
20
20
|
/** 与 runs.ts/tasks.ts 三个 409 位共享的旧文案(byte-frozen:api-error-text-freeze 门认这句)。 */
|
|
21
21
|
export declare const ACTIVE_RUN_CONFLICT_BASE_TEXT = "session already has an active run \u2014 POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)";
|
|
22
22
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { governanceOriginOf } from "../runtime-governance.js";
|
|
2
2
|
import { isParkedRunStatus } from "../plugins/store-contracts.js";
|
|
3
|
-
import { LIVE_RUN_GATE_KINDS } from "../plugins/checkpoint-store-
|
|
3
|
+
import { LIVE_RUN_GATE_KINDS } from "../plugins/checkpoint-store-face.js";
|
|
4
4
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
5
5
|
import { refusalBody } from "./refusal-body.js";
|
|
6
6
|
import { trustedCheckpointId } from "../checkpoint-identity.js";
|