@sema-agent/server 7.130.0 → 7.132.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 +396 -0
- package/README.md +13 -6
- package/README.zh-CN.md +9 -5
- package/USAGE.md +187 -50
- package/dist/approval-card.d.ts +3 -1
- package/dist/approval-card.js +2 -3
- package/dist/audit.d.ts +7 -10
- package/dist/audit.js +4 -1
- package/dist/auth-bridge.d.ts +4 -0
- package/dist/auth-bridge.js +10 -11
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/deployment-mods.d.ts +31 -0
- package/dist/boot/deployment-mods.js +26 -0
- package/dist/boot/execution-env.d.ts +1 -1
- package/dist/boot/execution-env.js +19 -6
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
- package/dist/boot/read-deny-anchor-audit.js +36 -0
- package/dist/boot/resolve-spec.d.ts +1 -5
- package/dist/boot/resolve-spec.js +56 -61
- package/dist/boot/runner-deps.d.ts +19 -8
- package/dist/boot/runner-deps.js +14 -2
- package/dist/boot/session-faces.d.ts +1 -7
- package/dist/boot/stage-02-web-admin-pull.js +9 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -4
- package/dist/boot/stage-05-execution-env.js +7 -10
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +5 -4
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +3 -3
- package/dist/boot/stage-08-reapers.d.ts +2 -2
- package/dist/boot/stage-09-leader.d.ts +3 -9
- package/dist/boot/stage-10-http-server.d.ts +3 -9
- package/dist/boot/stage-10-http-server.js +12 -10
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +5 -6
- package/dist/boot/web-admin.d.ts +0 -3
- package/dist/boot/web-admin.js +35 -35
- package/dist/brain.js +17 -22
- package/dist/budget.js +5 -4
- package/dist/caller-text-echo.d.ts +20 -0
- package/dist/caller-text-echo.js +24 -0
- package/dist/capabilities/brand-identity.d.ts +26 -0
- package/dist/capabilities/brand-identity.js +27 -0
- package/dist/capabilities/builtin-tools.js +2 -1
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/center-prompts.d.ts +32 -59
- package/dist/capabilities/center-prompts.js +18 -32
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/engine-capabilities.js +2 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +3 -3
- package/dist/capabilities/host-tool-names.d.ts +17 -0
- package/dist/capabilities/host-tool-names.js +15 -0
- package/dist/capabilities/mcp-probe.d.ts +14 -1
- package/dist/capabilities/mcp-probe.js +22 -133
- package/dist/capabilities/repo-tools.js +4 -3
- package/dist/capabilities/scenarios.d.ts +42 -47
- package/dist/capabilities/scenarios.js +19 -35
- package/dist/capabilities/select-environment-tool.js +2 -1
- package/dist/capabilities/send-user-file-tool.d.ts +0 -1
- package/dist/capabilities/send-user-file-tool.js +2 -2
- package/dist/capabilities/skills.d.ts +19 -4
- package/dist/capabilities/skills.js +30 -10
- package/dist/capabilities/tool-defer.js +2 -1
- package/dist/config-catalog.js +22 -11
- package/dist/config-invariants.js +2 -2
- package/dist/config-lkg.d.ts +9 -9
- package/dist/config-lkg.js +20 -4
- package/dist/config-provider.d.ts +3 -1
- package/dist/config-provider.js +14 -6
- package/dist/config-types.d.ts +13 -9
- package/dist/config.d.ts +2 -2
- package/dist/config.js +34 -20
- package/dist/device-ws-protocol.d.ts +1 -1
- package/dist/device-ws-protocol.js +1 -0
- package/dist/elicitation.d.ts +7 -0
- package/dist/elicitation.js +20 -5
- package/dist/env-name-allowlist-knobs.d.ts +12 -2
- package/dist/env-name-allowlist-knobs.js +5 -4
- package/dist/exec-bounds.d.ts +15 -3
- package/dist/exec-bounds.js +4 -0
- package/dist/execution-lane-caps.d.ts +9 -2
- package/dist/execution-lane-caps.js +11 -0
- package/dist/hooks/classic-stations.d.ts +86 -0
- package/dist/hooks/classic-stations.js +27 -0
- package/dist/hooks/hook-llm.d.ts +8 -5
- package/dist/hooks/hook-llm.js +80 -37
- package/dist/hooks/hook-runner.d.ts +273 -75
- package/dist/hooks/hook-runner.js +293 -317
- package/dist/http/admission.d.ts +8 -2
- package/dist/http/admission.js +103 -59
- package/dist/http/approval-sweeps.js +59 -18
- package/dist/http/checkpoint-ctx.d.ts +36 -18
- package/dist/http/checkpoint-ctx.js +14 -10
- package/dist/http/claimed-run.d.ts +13 -0
- package/dist/http/claimed-run.js +15 -0
- package/dist/http/device-placement.js +5 -10
- package/dist/http/dispatch.d.ts +0 -4
- package/dist/http/dispatch.js +6 -10
- package/dist/http/durable-run-leg.d.ts +2 -1
- package/dist/http/durable-run-leg.js +5 -3
- package/dist/http/health-self-report.d.ts +7 -5
- package/dist/http/health-self-report.js +4 -18
- package/dist/http/principal-gate.d.ts +23 -6
- package/dist/http/principal-gate.js +24 -5
- package/dist/http/refusal-body.d.ts +21 -0
- package/dist/http/refusal-body.js +4 -0
- package/dist/http/request-guards.d.ts +27 -0
- package/dist/http/request-guards.js +24 -3
- package/dist/http/resume-legs.d.ts +25 -0
- package/dist/http/resume-legs.js +48 -26
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/capabilities.js +7 -17
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +13 -17
- package/dist/http/routes/session-sync.js +4 -2
- package/dist/http/routes/sessions.js +23 -11
- package/dist/http/routes/shared-memory.js +2 -13
- package/dist/http/routes/side-query.js +2 -3
- package/dist/http/routes/tasks.js +34 -19
- package/dist/http/scenario-read-face.d.ts +14 -0
- package/dist/http/scenario-read-face.js +5 -0
- package/dist/http/send.d.ts +13 -1
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +37 -23
- package/dist/http/server.js +30 -4
- package/dist/http/stored-body-replay.d.ts +10 -3
- package/dist/http/stored-body-replay.js +27 -6
- package/dist/http/submit-ctx.d.ts +25 -0
- package/dist/http/submit-ctx.js +39 -0
- package/dist/http/wire-types.d.ts +18 -5
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/lane-home-probe.d.ts +43 -0
- package/dist/lane-home-probe.js +35 -0
- package/dist/limit-refusal.d.ts +47 -0
- package/dist/limit-refusal.js +12 -0
- package/dist/main.js +1 -1
- package/dist/mcp-assertion-header.d.ts +3 -11
- package/dist/mcp-assertion-header.js +3 -9
- package/dist/mcp-tool-faces.d.ts +5 -0
- package/dist/mcp-tool-faces.js +5 -1
- package/dist/memory-scope.d.ts +1 -1
- package/dist/model-entry-refusal.d.ts +1 -7
- package/dist/model-provider.d.ts +39 -12
- package/dist/model-provider.js +19 -2
- package/dist/model-route-endpoint.d.ts +52 -12
- package/dist/model-route-endpoint.js +48 -26
- package/dist/model-route-gap.d.ts +5 -4
- package/dist/model-route-gap.js +1 -1
- package/dist/model-select.d.ts +58 -0
- package/dist/model-select.js +31 -0
- package/dist/namespaced-rule-name.d.ts +7 -4
- package/dist/namespaced-rule-name.js +4 -3
- package/dist/observability/cost-taxonomy.d.ts +7 -7
- package/dist/observability/fail-open.d.ts +53 -12
- package/dist/observability/fail-open.js +47 -7
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/prompt-manifest.d.ts +47 -82
- package/dist/observability/prompt-manifest.js +119 -17
- package/dist/observability/run-terminal.js +14 -6
- package/dist/observability/secret-env-scrub.d.ts +13 -1
- package/dist/observability/secret-env-scrub.js +13 -1
- package/dist/observability/tool-trace.d.ts +16 -21
- package/dist/observability/tool-trace.js +51 -42
- package/dist/per-task-image.d.ts +7 -1
- package/dist/per-task-image.js +3 -3
- package/dist/plain-string-record.d.ts +25 -0
- package/dist/plain-string-record.js +32 -0
- package/dist/plugins/blob-backend.d.ts +68 -13
- package/dist/plugins/blob-backend.js +53 -10
- package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
- package/dist/plugins/checkpoint-store-sql.js +39 -26
- package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
- package/dist/plugins/k8s-bg-scripts.js +22 -0
- package/dist/plugins/local-checkpoint-store.js +5 -2
- package/dist/plugins/local-session-store.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +147 -91
- package/dist/plugins/one-time-migrations.js +372 -208
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
- package/dist/plugins/permission-rule-store-sql.js +4 -18
- package/dist/plugins/pg-session-storage.d.ts +4 -5
- package/dist/plugins/pg-session-storage.js +7 -17
- package/dist/plugins/remote-env-adb.d.ts +6 -0
- package/dist/plugins/remote-env-adb.js +9 -4
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +1 -1
- package/dist/plugins/remote-env-e2b.js +21 -9
- package/dist/plugins/remote-env-host.d.ts +9 -9
- package/dist/plugins/remote-env-host.js +10 -32
- package/dist/plugins/remote-env-k8s.d.ts +95 -26
- package/dist/plugins/remote-env-k8s.js +265 -90
- package/dist/plugins/remote-env-local-docker.d.ts +20 -1
- package/dist/plugins/remote-env-local-docker.js +77 -9
- package/dist/plugins/remote-env-ssh.d.ts +15 -0
- package/dist/plugins/remote-env-ssh.js +109 -3
- package/dist/plugins/remote-shell.d.ts +0 -1
- package/dist/plugins/remote-shell.js +1 -2
- package/dist/plugins/retention-store-sql.js +6 -2
- package/dist/plugins/schema-consistency.d.ts +54 -0
- package/dist/plugins/schema-consistency.js +52 -0
- package/dist/plugins/session-epoch-anchor.d.ts +13 -0
- package/dist/plugins/session-epoch-anchor.js +23 -0
- package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
- package/dist/plugins/shared-memory-store-sql.js +5 -16
- package/dist/plugins/sql-driver.d.ts +141 -0
- package/dist/plugins/sql-driver.js +115 -1
- package/dist/plugins/sql-errors.d.ts +41 -0
- package/dist/plugins/sql-errors.js +36 -0
- package/dist/plugins/store-backend.js +2 -1
- package/dist/plugins/tidb-session-storage.d.ts +7 -11
- package/dist/plugins/tidb-session-storage.js +6 -16
- package/dist/plugins/tidb-session-store.js +1 -1
- package/dist/prompts-domain-validate.d.ts +17 -61
- package/dist/prompts-domain-validate.js +9 -119
- package/dist/question.d.ts +18 -9
- package/dist/question.js +21 -13
- package/dist/request-key-closure.d.ts +4 -4
- package/dist/request-key-closure.js +18 -13
- package/dist/rule-entry-list.d.ts +134 -24
- package/dist/rule-entry-list.js +162 -30
- package/dist/run-local.d.ts +3 -3
- package/dist/run-local.js +26 -25
- package/dist/runs.d.ts +48 -2
- package/dist/runs.js +61 -2
- package/dist/server-secret-env.d.ts +44 -18
- package/dist/server-secret-env.js +49 -8
- package/dist/session-incarnation.d.ts +15 -5
- package/dist/session-incarnation.js +16 -9
- package/dist/session-titler.js +12 -2
- package/dist/spec-fields.d.ts +34 -18
- package/dist/spec-fields.js +46 -53
- package/dist/sql-engine-posture.d.ts +29 -4
- package/dist/sql-engine-posture.js +36 -1
- package/dist/stored-request-hooks-m20.d.ts +23 -0
- package/dist/stored-request-hooks-m20.js +124 -0
- package/dist/stored-request-tool-axes-m19.d.ts +20 -5
- package/dist/stored-request-tool-axes-m19.js +30 -2
- package/dist/task-a2a.d.ts +9 -1
- package/dist/task-a2a.js +73 -47
- package/dist/task-mcp.d.ts +54 -102
- package/dist/task-mcp.js +194 -92
- package/dist/task-settings.d.ts +47 -25
- package/dist/task-settings.js +46 -61
- package/dist/text-bidi.d.ts +3 -46
- package/dist/text-bidi.js +3 -3
- package/dist/tool-approval.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +24 -4
- package/dist/trace/engine-notice-wire.d.ts +11 -3
- package/dist/trace/engine-notice-wire.js +8 -1
- package/dist/trace/ledger-events.d.ts +3 -1
- package/dist/trace/ledger-sink.d.ts +29 -0
- package/dist/trace/ledger-sink.js +15 -1
- package/dist/trace/model-segment.js +1 -1
- package/dist/trace/project.d.ts +25 -6
- package/dist/trace/project.js +68 -22
- package/dist/trace/wire-projection-faces.d.ts +80 -2
- package/dist/trace/wire-projection-faces.js +23 -1
- package/dist/user-authored-text.d.ts +7 -0
- package/dist/user-authored-text.js +3 -0
- package/dist/web-admin-client/apply-effective.js +10 -5
- package/dist/web-admin-client/candidate-gate.d.ts +6 -6
- package/dist/web-admin-client/candidate-gate.js +3 -3
- package/dist/web-admin-client/http-client.js +0 -6
- package/dist/web-admin-client/read-warnings.d.ts +13 -0
- package/dist/web-admin-client/read-warnings.js +10 -0
- package/dist/web-admin-client/skills-mcp.d.ts +3 -0
- package/dist/web-admin-client/skills-mcp.js +9 -6
- package/dist/web-admin-client/types.d.ts +3 -4
- package/dist/workspace-cache.d.ts +35 -0
- package/dist/workspace-cache.js +36 -0
- package/dist/workspace-space.d.ts +3 -2
- package/dist/workspace-space.js +2 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
- package/package.json +4 -4
- package/scripts/drain-check-local.mjs +37 -3
- package/scripts/drain-check.mysql.sql +30 -2
- package/scripts/drain-check.pg.sql +29 -2
- package/dist/capabilities/memory-notice.d.ts +0 -138
- package/dist/capabilities/memory-notice.js +0 -64
- package/dist/capabilities/prompt.d.ts +0 -17
- package/dist/capabilities/prompt.js +0 -6
- package/dist/mcp-content-origin.d.ts +0 -34
- package/dist/mcp-content-origin.js +0 -10
- package/dist/tool-axis-words.d.ts +0 -27
- package/dist/tool-axis-words.js +0 -9
- package/dist/wire-name-bound.d.ts +0 -14
- package/dist/wire-name-bound.js +0 -13
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { composeHostSubprocessEnv, hostSubprocessEnv } from "../server-secret-env.js";
|
|
2
3
|
import vm from "node:vm";
|
|
3
4
|
import { hostShell, resolveHostShell, adoptHostForegroundChild } from "../plugins/host-platform.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { classifyHooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_ENTRY_TYPES, HOOK_EVENTS, HOOK_EVENT_OWNER, HOOK_EVENT_MATCHER_FIELD, hookMatcherForm, hookMatcherSelects, interpolateHookEnvVars, } from "@sema-agent/settings-schema/hooks";
|
|
6
|
+
import { formatConfigPath, outgoingPath } from "@sema-agent/settings-schema";
|
|
7
|
+
import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
|
|
8
|
+
import { describeToolCatalog } from "@sema-agent/core";
|
|
9
|
+
import { hangClassic } from "./classic-stations.js";
|
|
6
10
|
import { boundedNoticeText } from "../fleet/fleet-bus.js";
|
|
7
11
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
12
|
+
import { LIMIT_REASONS } from "../limit-refusal.js";
|
|
13
|
+
import { echoCallerText } from "../caller-text-echo.js";
|
|
8
14
|
import { redactSecrets, buildDisplaySafeUrl } from "../trace/redact.js";
|
|
9
15
|
import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTPUT_TOKENS } from "./cc-stop-prompt.js";
|
|
10
16
|
import { ccAgentHookSystemFor } from "./cc-agent-hook-prompt.js";
|
|
@@ -25,26 +31,35 @@ const HOOK_ENV_ALLOWLIST = [
|
|
|
25
31
|
"PATH", "HOME", "SHELL", "USER", "LOGNAME", "TERM", "TZ",
|
|
26
32
|
"LANG", "LC_ALL", "LC_CTYPE", "LC_MESSAGES", "TMPDIR",
|
|
27
33
|
];
|
|
28
|
-
function hookEnv(ctx) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
function hookEnv(ctx, spawning) {
|
|
35
|
+
const base = spawning
|
|
36
|
+
? hostSubprocessEnv({ site: "server.hooks.spawn-env", policy: [...HOOK_ENV_ALLOWLIST], allow: [] })
|
|
37
|
+
: composeHostSubprocessEnv({ policy: [...HOOK_ENV_ALLOWLIST], allow: [] });
|
|
38
|
+
return { ...base, CLAUDE_PROJECT_DIR: ctx.cwd, ...ctx.shellEnv };
|
|
39
|
+
}
|
|
40
|
+
export const SETTINGS_HOOK_STATIONS = {
|
|
41
|
+
PreToolUse: ["classic.PreToolUse"],
|
|
42
|
+
PostToolUse: ["classic.PostToolUse"],
|
|
43
|
+
PostToolUseFailure: ["classic.PostToolUseFailure"],
|
|
44
|
+
PostToolBatch: ["classic.PostToolBatch"],
|
|
45
|
+
UserPromptSubmit: ["classic.UserPromptSubmit"],
|
|
46
|
+
Stop: ["classic.Stop", "classic.SubagentStop"],
|
|
47
|
+
StopFailure: ["classic.StopFailure"],
|
|
48
|
+
PreCompact: ["classic.PreCompact"],
|
|
49
|
+
PostCompact: ["classic.PostCompact"],
|
|
50
|
+
};
|
|
51
|
+
function hangTranslator(on, event, translators) {
|
|
52
|
+
const translate = translators[event];
|
|
53
|
+
if (translate === undefined)
|
|
54
|
+
return;
|
|
55
|
+
for (const station of SETTINGS_HOOK_STATIONS[event])
|
|
56
|
+
hangClassic(on, station, translate);
|
|
57
|
+
}
|
|
58
|
+
export const SETTINGS_HOOKS_REGISTRATION = "settings.hooks";
|
|
59
|
+
export const DEFAULT_SETTINGS_HOOKS_BUDGET_MS = MAX_HOOK_TIMEOUT_SECONDS * 1000;
|
|
60
|
+
export function settingsHooksBudgetMs(config) {
|
|
61
|
+
return config.hooksTimeoutMs ?? DEFAULT_SETTINGS_HOOKS_BUDGET_MS;
|
|
36
62
|
}
|
|
37
|
-
const LIT_EVENTS = [
|
|
38
|
-
"PreToolUse",
|
|
39
|
-
"PostToolUse",
|
|
40
|
-
"PostToolUseFailure",
|
|
41
|
-
"PostToolBatch",
|
|
42
|
-
"UserPromptSubmit",
|
|
43
|
-
"Stop",
|
|
44
|
-
"StopFailure",
|
|
45
|
-
"PreCompact",
|
|
46
|
-
"PostCompact",
|
|
47
|
-
];
|
|
48
63
|
const BRAIN_TO_TURN_ERROR = {
|
|
49
64
|
auth: "authentication_failed",
|
|
50
65
|
rate_limit: "rate_limit",
|
|
@@ -57,8 +72,25 @@ function toAssistantTurnError(errorKind) {
|
|
|
57
72
|
return "unknown";
|
|
58
73
|
return Object.prototype.hasOwnProperty.call(BRAIN_TO_TURN_ERROR, errorKind) ? (BRAIN_TO_TURN_ERROR[errorKind] ?? "unknown") : "unknown";
|
|
59
74
|
}
|
|
75
|
+
const HOOK_ENTRY_TYPE_RUNS = {
|
|
76
|
+
command: true,
|
|
77
|
+
http: true,
|
|
78
|
+
prompt: true,
|
|
79
|
+
agent: true,
|
|
80
|
+
mcp_tool: false,
|
|
81
|
+
};
|
|
82
|
+
function isSupportedHookEntryType(t) {
|
|
83
|
+
return HOOK_ENTRY_TYPE_RUNS[t];
|
|
84
|
+
}
|
|
85
|
+
export const SUPPORTED_HOOK_ENTRY_TYPES = HOOK_ENTRY_TYPES.filter(isSupportedHookEntryType);
|
|
86
|
+
function isHookEventName(key) {
|
|
87
|
+
return HOOK_EVENTS.includes(key);
|
|
88
|
+
}
|
|
89
|
+
function isRunnableHookEntry(entry) {
|
|
90
|
+
return isSupportedHookEntryType(entry.type);
|
|
91
|
+
}
|
|
60
92
|
export const MAX_MATCHER_EVAL_CHARS = MAX_HOOK_COMMAND_CHARS;
|
|
61
|
-
export const
|
|
93
|
+
export const ADMISSION_MATCHER_BUDGET = { budgetMs: 1_000, totalMs: 5_000 };
|
|
62
94
|
export const MATCHER_ECHO_CHARS = 200;
|
|
63
95
|
const MATCHER_UNJUDGED_TAG = "server.hooks.matcher-unjudged";
|
|
64
96
|
function matcherReadsWireToolName(event) {
|
|
@@ -112,7 +144,7 @@ const MATCHER_EVALUATION = {
|
|
|
112
144
|
regex: { regex: "budgeted", literal: "not_applicable" },
|
|
113
145
|
invalid: { regex: "unreadable", literal: "not_applicable" },
|
|
114
146
|
};
|
|
115
|
-
function judgeMatcher(matcher, depth,
|
|
147
|
+
function judgeMatcher(matcher, depth, ledger, judgesAlias) {
|
|
116
148
|
if (matcher.length > MAX_MATCHER_EVAL_CHARS)
|
|
117
149
|
return { kind: "unjudged", reason: "oversize" };
|
|
118
150
|
const evaluate = MATCHER_EVALUATION[hookMatcherForm(matcher)][depth];
|
|
@@ -123,14 +155,34 @@ function judgeMatcher(matcher, depth, budgetMs) {
|
|
|
123
155
|
return { kind: "invalid_regex" };
|
|
124
156
|
case "inline":
|
|
125
157
|
case "budgeted":
|
|
158
|
+
if (!judgesAlias)
|
|
159
|
+
return undefined;
|
|
126
160
|
break;
|
|
127
161
|
default: {
|
|
128
162
|
const unreachable = evaluate;
|
|
129
163
|
return unreachable;
|
|
130
164
|
}
|
|
131
165
|
}
|
|
132
|
-
const selects = (name) =>
|
|
133
|
-
|
|
166
|
+
const selects = (name) => {
|
|
167
|
+
if (evaluate === "inline")
|
|
168
|
+
return hookMatcherSelects(matcher, name);
|
|
169
|
+
const startedAt = ledger.now();
|
|
170
|
+
const answer = matcherSelectsWithin(matcher, name, ledger.budgetMs);
|
|
171
|
+
ledger.spentMs += ledger.now() - startedAt;
|
|
172
|
+
if (answer === "over_budget" || answer === "invalid")
|
|
173
|
+
return answer;
|
|
174
|
+
return ledger.spentMs > ledger.totalMs ? "budget_exceeded" : answer;
|
|
175
|
+
};
|
|
176
|
+
const unanswered = (verdict) => {
|
|
177
|
+
switch (verdict) {
|
|
178
|
+
case "over_budget":
|
|
179
|
+
return { kind: "over_budget" };
|
|
180
|
+
case "budget_exceeded":
|
|
181
|
+
return { kind: "budget_exceeded" };
|
|
182
|
+
case "invalid":
|
|
183
|
+
return { kind: "invalid_regex" };
|
|
184
|
+
}
|
|
185
|
+
};
|
|
134
186
|
for (const [alias, canonical] of engineToolAliases()) {
|
|
135
187
|
const selectsAlias = selects(alias);
|
|
136
188
|
if (typeof selectsAlias === "string")
|
|
@@ -145,50 +197,102 @@ function judgeMatcher(matcher, depth, budgetMs) {
|
|
|
145
197
|
}
|
|
146
198
|
return undefined;
|
|
147
199
|
}
|
|
148
|
-
|
|
200
|
+
const HOOKS_CAPS = {
|
|
201
|
+
matcher_groups: { measure: "entries", cap: MAX_HOOK_MATCHER_GROUPS, sentence: (event, count) => `settings.hooks.${event}: ${count} matcher groups exceeds cap ${MAX_HOOK_MATCHER_GROUPS}` },
|
|
202
|
+
event_entries: { measure: "entries", cap: MAX_HOOK_ENTRIES_PER_EVENT, sentence: (event, count) => `settings.hooks.${event}: ${count} entries exceeds cap ${MAX_HOOK_ENTRIES_PER_EVENT}` },
|
|
203
|
+
command: { measure: "characters", cap: MAX_HOOK_COMMAND_CHARS, sentence: (event) => `settings.hooks.${event}: command exceeds ${MAX_HOOK_COMMAND_CHARS} chars` },
|
|
204
|
+
url: { measure: "characters", cap: MAX_HOOK_COMMAND_CHARS, sentence: (event) => `settings.hooks.${event}: url exceeds ${MAX_HOOK_COMMAND_CHARS} chars` },
|
|
205
|
+
headers: { measure: "entries", cap: MAX_HOOK_HTTP_HEADERS, sentence: (event, count) => `settings.hooks.${event}: ${count} headers exceeds cap ${MAX_HOOK_HTTP_HEADERS}` },
|
|
206
|
+
header_chars: { measure: "characters", cap: MAX_HOOK_COMMAND_CHARS, sentence: (event) => `settings.hooks.${event}: headers exceed ${MAX_HOOK_COMMAND_CHARS} chars total` },
|
|
207
|
+
allowed_env_vars: { measure: "entries", cap: MAX_HOOK_HTTP_HEADERS, sentence: (event) => `settings.hooks.${event}: allowedEnvVars exceeds cap ${MAX_HOOK_HTTP_HEADERS}` },
|
|
208
|
+
};
|
|
209
|
+
function hooksCapIssue(limit, event, path, count) {
|
|
210
|
+
const c = HOOKS_CAPS[limit];
|
|
211
|
+
return { reason: LIMIT_REASONS[c.measure], limit, event, path, count, cap: c.cap };
|
|
212
|
+
}
|
|
213
|
+
const matcherPath = (event, groupIndex) => ["hooks", event, groupIndex, "matcher"];
|
|
214
|
+
export function buildHooksRefusal(issue) {
|
|
215
|
+
const field = "settings.hooks";
|
|
149
216
|
switch (issue.reason) {
|
|
150
217
|
case "dead_alias":
|
|
151
218
|
return {
|
|
152
219
|
message: `settings.hooks.${issue.event}: matcher "${issue.alias}" is an alias; tool-event matchers match the tool's wire name (PostToolUse / PostToolUseFailure since 7.107.0) — use "${issue.canonical}"`,
|
|
153
|
-
field
|
|
154
|
-
detail: { reason: issue.reason, event: issue.event, alias: issue.alias, canonical: issue.canonical },
|
|
220
|
+
field,
|
|
221
|
+
detail: { reason: issue.reason, event: issue.event, alias: issue.alias, canonical: issue.canonical, path: matcherPath(issue.event, issue.groupIndex) },
|
|
155
222
|
};
|
|
156
223
|
case "over_budget":
|
|
157
224
|
return {
|
|
158
225
|
message: `settings.hooks.${issue.event}[${issue.groupIndex}].matcher: evaluating the pattern did not finish within ${issue.budgetMs} ms (catastrophic regex backtracking) — rewrite it as a tool-name list or a pattern without nested repetition`,
|
|
159
|
-
field
|
|
160
|
-
detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, budgetMs: issue.budgetMs },
|
|
226
|
+
field,
|
|
227
|
+
detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, budgetMs: issue.budgetMs, path: matcherPath(issue.event, issue.groupIndex) },
|
|
228
|
+
};
|
|
229
|
+
case "budget_exceeded":
|
|
230
|
+
return {
|
|
231
|
+
message: `settings.hooks.${issue.event}[${issue.groupIndex}].matcher: evaluating this configuration's regular-expression matchers took longer than ${issue.totalMs} ms in total (very slow patterns) — write tool names as a list such as "Read|Edit", or simplify the patterns`,
|
|
232
|
+
field,
|
|
233
|
+
detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, totalMs: issue.totalMs, path: matcherPath(issue.event, issue.groupIndex) },
|
|
161
234
|
};
|
|
162
235
|
case "invalid_regex":
|
|
163
236
|
return {
|
|
164
237
|
message: `settings.hooks.${issue.event}[${issue.groupIndex}].matcher: ${JSON.stringify(issue.matcher)} is not a valid regular expression (${issue.compileError}) — fix the pattern, or write the tool names as a list such as "Read|Edit"`,
|
|
165
|
-
field
|
|
166
|
-
detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex },
|
|
238
|
+
field,
|
|
239
|
+
detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, path: matcherPath(issue.event, issue.groupIndex) },
|
|
167
240
|
};
|
|
241
|
+
case "unsupported_entry_type":
|
|
242
|
+
return {
|
|
243
|
+
message: `${hooksCoordinate(issue.path)}: this server does not run hook entries of type ${issue.type}; supported: ${SUPPORTED_HOOK_ENTRY_TYPES.join(" / ")}`,
|
|
244
|
+
field,
|
|
245
|
+
detail: { reason: issue.reason, path: hooksOutgoingPath(issue.path), type: issue.type },
|
|
246
|
+
};
|
|
247
|
+
case "over_cap":
|
|
248
|
+
case "too_long": {
|
|
249
|
+
const cap = issue;
|
|
250
|
+
return { message: HOOKS_CAPS[cap.limit].sentence(cap.event, cap.count), field, detail: { reason: cap.reason, path: hooksOutgoingPath(cap.path), count: cap.count, cap: cap.cap } };
|
|
251
|
+
}
|
|
252
|
+
case "unnamed_refusal":
|
|
253
|
+
return { message: `${hooksCoordinate(issue.path)}: the hooks reader refused this configuration without naming a piece`, field, detail: { reason: issue.reason, path: hooksOutgoingPath(issue.path) } };
|
|
168
254
|
default: {
|
|
169
|
-
const
|
|
170
|
-
|
|
255
|
+
const piece = issue;
|
|
256
|
+
const key = piece.key !== undefined ? hooksOutgoingKey(piece.key) : undefined;
|
|
257
|
+
return {
|
|
258
|
+
message: hooksPieceRefusal(piece),
|
|
259
|
+
field,
|
|
260
|
+
detail: { reason: piece.reason, path: hooksOutgoingPath(piece.path), ...(key !== undefined ? { key } : {}) },
|
|
261
|
+
};
|
|
171
262
|
}
|
|
172
263
|
}
|
|
173
264
|
}
|
|
174
|
-
export function
|
|
175
|
-
const
|
|
265
|
+
export function hooksMatcherIssue(config, depth, budget = {}) {
|
|
266
|
+
const ledger = {
|
|
267
|
+
budgetMs: budget.budgetMs ?? ADMISSION_MATCHER_BUDGET.budgetMs,
|
|
268
|
+
totalMs: budget.totalMs ?? ADMISSION_MATCHER_BUDGET.totalMs,
|
|
269
|
+
now: budget.now ?? (() => performance.now()),
|
|
270
|
+
spentMs: 0,
|
|
271
|
+
};
|
|
272
|
+
const budgetMs = ledger.budgetMs;
|
|
176
273
|
for (const [event, groups] of Object.entries(config)) {
|
|
177
|
-
|
|
178
|
-
continue;
|
|
274
|
+
const judgesAlias = matcherReadsWireToolName(event);
|
|
179
275
|
for (const [groupIndex, group] of (groups ?? []).entries()) {
|
|
180
276
|
if (group.matcher === undefined)
|
|
181
277
|
continue;
|
|
182
|
-
const verdict = judgeMatcher(group.matcher, depth,
|
|
278
|
+
const verdict = judgeMatcher(group.matcher, depth, ledger, judgesAlias);
|
|
183
279
|
if (verdict === undefined)
|
|
184
280
|
continue;
|
|
185
281
|
switch (verdict.kind) {
|
|
186
282
|
case "dead":
|
|
187
|
-
return { reason: "dead_alias", event, alias: verdict.alias, canonical: verdict.canonical };
|
|
283
|
+
return { reason: "dead_alias", event, groupIndex, alias: verdict.alias, canonical: verdict.canonical };
|
|
188
284
|
case "over_budget":
|
|
189
285
|
return { reason: "over_budget", event, groupIndex, budgetMs };
|
|
286
|
+
case "budget_exceeded":
|
|
287
|
+
return { reason: "budget_exceeded", event, groupIndex, totalMs: ledger.totalMs };
|
|
190
288
|
case "invalid_regex":
|
|
191
|
-
return {
|
|
289
|
+
return {
|
|
290
|
+
reason: "invalid_regex",
|
|
291
|
+
event,
|
|
292
|
+
groupIndex,
|
|
293
|
+
matcher: echoCallerText(group.matcher, { maxChars: MATCHER_ECHO_CHARS }),
|
|
294
|
+
compileError: echoCallerText(matcherCompileSentence(group.matcher), { maxChars: MATCHER_ECHO_CHARS }),
|
|
295
|
+
};
|
|
192
296
|
case "unjudged":
|
|
193
297
|
recordFailOpen(MATCHER_UNJUDGED_TAG, `event=${event} reason=${verdict.reason} matcherChars=${group.matcher.length}`);
|
|
194
298
|
break;
|
|
@@ -201,47 +305,95 @@ export function toolMatcherIssue(config, depth, budget = {}) {
|
|
|
201
305
|
}
|
|
202
306
|
return undefined;
|
|
203
307
|
}
|
|
308
|
+
const HOOKS_PIECE_MEANING = {
|
|
309
|
+
"hooks-not-object": "`hooks` must be an object keyed by hook event name",
|
|
310
|
+
"unknown-event": "not a hook event name",
|
|
311
|
+
"event-not-array": "an event's value must be an array of matcher groups",
|
|
312
|
+
"matcher-invalid": "a matcher group must be an object with an optional string `matcher` and an array `hooks`",
|
|
313
|
+
"entry-invalid": "a hook entry must be an object whose `type` and fields are valid for that type",
|
|
314
|
+
"guard-outside-hooks": "a PreToolUse / PermissionRequest hook sits outside `hooks`",
|
|
315
|
+
"hooks-is-matcher": "`hooks` holds a matcher group or an entry directly; it must be an object keyed by hook event name",
|
|
316
|
+
"guard-under-unknown-event": "an unknown event name holds a PreToolUse / PermissionRequest hook",
|
|
317
|
+
"guard-event-not-array": "a PreToolUse / PermissionRequest event's value must be an array of matcher groups",
|
|
318
|
+
"guard-matcher-invalid": "a PreToolUse / PermissionRequest matcher group is not valid",
|
|
319
|
+
"guard-entry-invalid": "a PreToolUse / PermissionRequest hook entry is not valid",
|
|
320
|
+
};
|
|
321
|
+
export function hooksOutgoingPath(path) {
|
|
322
|
+
return outgoingPath(path).map((seg) => (typeof seg === "string" ? echoCallerText(seg) : seg));
|
|
323
|
+
}
|
|
324
|
+
export function hooksCoordinate(path) {
|
|
325
|
+
return `settings.${formatConfigPath(hooksOutgoingPath(path))}`;
|
|
326
|
+
}
|
|
327
|
+
function hooksOutgoingKey(key) {
|
|
328
|
+
return echoCallerText(String(outgoingPath([key])[0] ?? ""));
|
|
329
|
+
}
|
|
330
|
+
function hooksPieceIssue(piece) {
|
|
331
|
+
return piece.field !== undefined ? { reason: piece.code, path: piece.path, key: piece.field } : { reason: piece.code, path: piece.path };
|
|
332
|
+
}
|
|
333
|
+
function hooksPieceRefusal(piece) {
|
|
334
|
+
const field = piece.key !== undefined ? ` (field: ${hooksOutgoingKey(piece.key)})` : "";
|
|
335
|
+
return `${hooksCoordinate(piece.path)}: ${piece.reason} — ${HOOKS_PIECE_MEANING[piece.reason]}${field}`;
|
|
336
|
+
}
|
|
337
|
+
function hooksRefused(issue, pieces) {
|
|
338
|
+
const error = buildHooksRefusal(issue).message;
|
|
339
|
+
return pieces === undefined ? { issue, error } : { issue, error, pieces };
|
|
340
|
+
}
|
|
204
341
|
export function parseHooksConfig(raw) {
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
207
|
-
const first =
|
|
208
|
-
|
|
342
|
+
const classified = classifyHooksConfig({ hooks: raw });
|
|
343
|
+
if (classified.outcome !== "loaded") {
|
|
344
|
+
const first = classified.outcome === "rejected" ? classified.rejections[0] : classified.dropped[0];
|
|
345
|
+
const pieces = classified.outcome === "rejected" ? classified.rejections : classified.dropped;
|
|
346
|
+
return hooksRefused(first !== undefined ? hooksPieceIssue(first) : { reason: "unnamed_refusal", path: ["hooks"] }, pieces);
|
|
209
347
|
}
|
|
210
|
-
const
|
|
211
|
-
|
|
348
|
+
const loaded = classified.hooks;
|
|
349
|
+
if (loaded === undefined) {
|
|
350
|
+
return hooksRefused({ reason: "hooks-not-object", path: ["hooks"] });
|
|
351
|
+
}
|
|
352
|
+
const config = {};
|
|
353
|
+
for (const key of Object.keys(loaded)) {
|
|
354
|
+
if (!isHookEventName(key)) {
|
|
355
|
+
return hooksRefused({ reason: "unknown-event", path: ["hooks", key] });
|
|
356
|
+
}
|
|
357
|
+
const event = key;
|
|
358
|
+
const groups = loaded[event];
|
|
212
359
|
if (!groups)
|
|
213
360
|
continue;
|
|
214
|
-
if (groups.length > MAX_HOOK_MATCHER_GROUPS)
|
|
215
|
-
return
|
|
216
|
-
}
|
|
361
|
+
if (groups.length > MAX_HOOK_MATCHER_GROUPS)
|
|
362
|
+
return hooksRefused(hooksCapIssue("matcher_groups", event, ["hooks", event], groups.length));
|
|
217
363
|
let entries = 0;
|
|
218
|
-
|
|
219
|
-
|
|
364
|
+
let firstPastCap;
|
|
365
|
+
const kept = [];
|
|
366
|
+
for (const [groupIndex, group] of groups.entries()) {
|
|
367
|
+
const hooks = [];
|
|
368
|
+
for (const [entryIndex, entry] of group.hooks.entries()) {
|
|
220
369
|
entries++;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
370
|
+
const at = ["hooks", event, groupIndex, "hooks", entryIndex];
|
|
371
|
+
if (entries === MAX_HOOK_ENTRIES_PER_EVENT + 1)
|
|
372
|
+
firstPastCap = at;
|
|
373
|
+
if (!isRunnableHookEntry(entry))
|
|
374
|
+
return hooksRefused({ reason: "unsupported_entry_type", path: [...at, "type"], type: entry.type });
|
|
375
|
+
if (entry.type === "command" && entry.command.length > MAX_HOOK_COMMAND_CHARS)
|
|
376
|
+
return hooksRefused(hooksCapIssue("command", event, [...at, "command"], entry.command.length));
|
|
224
377
|
if (entry.type === "http") {
|
|
225
|
-
if (entry.url.length > MAX_HOOK_COMMAND_CHARS)
|
|
226
|
-
return
|
|
227
|
-
}
|
|
378
|
+
if (entry.url.length > MAX_HOOK_COMMAND_CHARS)
|
|
379
|
+
return hooksRefused(hooksCapIssue("url", event, [...at, "url"], entry.url.length));
|
|
228
380
|
const headerEntries = Object.entries(entry.headers ?? {});
|
|
229
|
-
if (headerEntries.length > MAX_HOOK_HTTP_HEADERS)
|
|
230
|
-
return
|
|
231
|
-
}
|
|
381
|
+
if (headerEntries.length > MAX_HOOK_HTTP_HEADERS)
|
|
382
|
+
return hooksRefused(hooksCapIssue("headers", event, [...at, "headers"], headerEntries.length));
|
|
232
383
|
const headerChars = headerEntries.reduce((n, [k, v]) => n + k.length + v.length, 0);
|
|
233
|
-
if (headerChars > MAX_HOOK_COMMAND_CHARS)
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
238
|
-
}
|
|
384
|
+
if (headerChars > MAX_HOOK_COMMAND_CHARS)
|
|
385
|
+
return hooksRefused(hooksCapIssue("header_chars", event, [...at, "headers"], headerChars));
|
|
386
|
+
const envVars = entry.allowedEnvVars?.length ?? 0;
|
|
387
|
+
if (envVars > MAX_HOOK_HTTP_HEADERS)
|
|
388
|
+
return hooksRefused(hooksCapIssue("allowed_env_vars", event, [...at, "allowedEnvVars"], envVars));
|
|
239
389
|
}
|
|
390
|
+
hooks.push(entry);
|
|
240
391
|
}
|
|
392
|
+
kept.push(group.matcher !== undefined ? { matcher: group.matcher, hooks } : { hooks });
|
|
241
393
|
}
|
|
242
|
-
if (
|
|
243
|
-
return
|
|
244
|
-
|
|
394
|
+
if (firstPastCap !== undefined)
|
|
395
|
+
return hooksRefused(hooksCapIssue("event_entries", event, firstPastCap, entries));
|
|
396
|
+
config[event] = kept;
|
|
245
397
|
}
|
|
246
398
|
return { config };
|
|
247
399
|
}
|
|
@@ -331,61 +483,40 @@ function noteHookFailure(ctx, args) {
|
|
|
331
483
|
});
|
|
332
484
|
}
|
|
333
485
|
export function buildHookContext(parts) {
|
|
334
|
-
const present = parts.filter((p) => p.length > 0)
|
|
486
|
+
const present = parts.filter((p) => p.length > 0);
|
|
335
487
|
if (present.length === 0)
|
|
336
488
|
return undefined;
|
|
337
489
|
const kept = [];
|
|
338
490
|
let used = 0;
|
|
339
491
|
let truncating = false;
|
|
340
|
-
let messages = 0;
|
|
341
492
|
let omitted = 0;
|
|
342
493
|
let droppedChars = 0;
|
|
343
494
|
for (const part of present) {
|
|
344
|
-
messages += part.messages;
|
|
345
|
-
omitted += part.omitted;
|
|
346
|
-
droppedChars += part.dropped;
|
|
347
495
|
if (!truncating) {
|
|
348
496
|
const sep = kept.length > 0 ? 1 : 0;
|
|
349
497
|
const room = MAX_HOOK_CONTEXT_TOTAL_CHARS - used - sep;
|
|
350
|
-
if (room >= part.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
used += sep + part.body.length;
|
|
354
|
-
}
|
|
498
|
+
if (room >= part.length) {
|
|
499
|
+
kept.push(part);
|
|
500
|
+
used += sep + part.length;
|
|
355
501
|
continue;
|
|
356
502
|
}
|
|
357
503
|
truncating = true;
|
|
358
|
-
if (
|
|
359
|
-
kept.push(part.
|
|
504
|
+
if (room >= MIN_HOOK_CONTEXT_PARTIAL_CHARS) {
|
|
505
|
+
kept.push(part.slice(0, room));
|
|
360
506
|
used += sep + room;
|
|
361
|
-
droppedChars += part.
|
|
507
|
+
droppedChars += part.length - room;
|
|
362
508
|
continue;
|
|
363
509
|
}
|
|
364
510
|
}
|
|
365
|
-
omitted +=
|
|
366
|
-
droppedChars += part.
|
|
511
|
+
omitted += 1;
|
|
512
|
+
droppedChars += part.length;
|
|
367
513
|
}
|
|
368
514
|
const body = kept.join("\n");
|
|
369
515
|
if (droppedChars === 0)
|
|
370
516
|
return body;
|
|
371
|
-
const marker = `[hook context truncated at ${MAX_HOOK_CONTEXT_TOTAL_CHARS} chars: ${omitted} of ${
|
|
517
|
+
const marker = `[hook context truncated at ${MAX_HOOK_CONTEXT_TOTAL_CHARS} chars: ${omitted} of ${present.length} hook messages omitted, ${droppedChars} characters dropped]`;
|
|
372
518
|
return kept.length > 0 ? `${body}\n${marker}` : marker;
|
|
373
519
|
}
|
|
374
|
-
const HOOK_CONTEXT_MARKER_RE = new RegExp(`\\n\\[hook context truncated at ${MAX_HOOK_CONTEXT_TOTAL_CHARS} chars: (\\d+) of (\\d+) hook messages omitted, (\\d+) characters dropped\\]$`);
|
|
375
|
-
const MAX_HOOK_CONTEXT_PARSED_COUNT = 2 ** 42;
|
|
376
|
-
function readHookContextPart(part) {
|
|
377
|
-
const m = HOOK_CONTEXT_MARKER_RE.exec(part);
|
|
378
|
-
if (!m)
|
|
379
|
-
return { body: part, messages: 1, omitted: 0, dropped: 0 };
|
|
380
|
-
const omitted = Number(m[1]);
|
|
381
|
-
const messages = Number(m[2]);
|
|
382
|
-
const dropped = Number(m[3]);
|
|
383
|
-
const sane = (n) => Number.isSafeInteger(n) && n <= MAX_HOOK_CONTEXT_PARSED_COUNT;
|
|
384
|
-
if (!sane(omitted) || !sane(messages) || !sane(dropped) || messages < 1 || omitted > messages || dropped < 1) {
|
|
385
|
-
return { body: part, messages: 1, omitted: 0, dropped: 0 };
|
|
386
|
-
}
|
|
387
|
-
return { body: part.slice(0, part.length - m[0].length), messages, omitted, dropped };
|
|
388
|
-
}
|
|
389
520
|
function buildHookContextField(parts) {
|
|
390
521
|
const text = buildHookContext(parts);
|
|
391
522
|
return text === undefined ? {} : { additionalContext: text };
|
|
@@ -404,7 +535,7 @@ async function runCommandHook(entry, payload, ctx) {
|
|
|
404
535
|
const sh = hostShell();
|
|
405
536
|
child = spawn(sh.shell, [...sh.args, entry.command], {
|
|
406
537
|
cwd: ctx.cwd,
|
|
407
|
-
env: hookEnv(ctx),
|
|
538
|
+
env: hookEnv(ctx, true),
|
|
408
539
|
stdio: ["pipe", "pipe", "pipe"],
|
|
409
540
|
detached: true,
|
|
410
541
|
});
|
|
@@ -467,7 +598,7 @@ async function fireAsyncCommandHook(entry, payload, ctx, event) {
|
|
|
467
598
|
const sh = hostShell();
|
|
468
599
|
const child = spawn(sh.shell, [...sh.args, entry.command], {
|
|
469
600
|
cwd: ctx.cwd,
|
|
470
|
-
env: hookEnv(ctx),
|
|
601
|
+
env: hookEnv(ctx, true),
|
|
471
602
|
stdio: ["pipe", "ignore", rewake ? "pipe" : "ignore"],
|
|
472
603
|
detached: true,
|
|
473
604
|
});
|
|
@@ -600,7 +731,7 @@ async function runLlmHook(entry, payload, ctx, extra) {
|
|
|
600
731
|
const MAX_BLANKED_REFERENCES_LOGGED = 16;
|
|
601
732
|
async function runHttpHook(entry, payload, ctx, event) {
|
|
602
733
|
const timeoutMs = Math.min(entry.timeout ?? DEFAULT_HOOK_TIMEOUT_SECONDS, MAX_HOOK_TIMEOUT_SECONDS) * 1000;
|
|
603
|
-
const interpolated = interpolateHookEnvVars(entry.headers ?? {}, entry.allowedEnvVars ?? [], hookEnv(ctx));
|
|
734
|
+
const interpolated = interpolateHookEnvVars(entry.headers ?? {}, entry.allowedEnvVars ?? [], hookEnv(ctx, false));
|
|
604
735
|
if (interpolated.blanked.length > 0) {
|
|
605
736
|
ctx.logger.warn("hook_http_header_env_blanked", {
|
|
606
737
|
event,
|
|
@@ -712,6 +843,23 @@ function parseHookStdout(stdout) {
|
|
|
712
843
|
return undefined;
|
|
713
844
|
}
|
|
714
845
|
}
|
|
846
|
+
function runRunnableEntry(entry, payload, ctx, event, llmExtra) {
|
|
847
|
+
switch (entry.type) {
|
|
848
|
+
case "http":
|
|
849
|
+
return runHttpHook(entry, payload, ctx, event);
|
|
850
|
+
case "prompt":
|
|
851
|
+
return runLlmHook(entry, payload, ctx, llmExtra);
|
|
852
|
+
case "agent":
|
|
853
|
+
return runLlmHook(entry, payload, ctx, { system: ccAgentHookSystemFor(event), event });
|
|
854
|
+
case "command":
|
|
855
|
+
return runCommandHook(entry, payload, ctx);
|
|
856
|
+
default: {
|
|
857
|
+
const unreachable = entry;
|
|
858
|
+
void unreachable;
|
|
859
|
+
throw new Error(`hook entry of an unsupported type reached the runner although parseHooksConfig admits only ${SUPPORTED_HOOK_ENTRY_TYPES.join(" / ")}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
715
863
|
async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceFired, deadlineAt, keyBase, llmExtra) {
|
|
716
864
|
const results = [];
|
|
717
865
|
let llmEntries = 0;
|
|
@@ -720,7 +868,7 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
720
868
|
gi++;
|
|
721
869
|
const m = hookMatcherMatches(group.matcher, matchValue);
|
|
722
870
|
if (m === "invalid") {
|
|
723
|
-
ctx.logger.warn("hook_matcher_invalid", { event, matcher:
|
|
871
|
+
ctx.logger.warn("hook_matcher_invalid", { event, matcher: echoCallerText(group.matcher ?? "", { maxChars: MATCHER_ECHO_CHARS }) });
|
|
724
872
|
continue;
|
|
725
873
|
}
|
|
726
874
|
if (!m)
|
|
@@ -731,10 +879,6 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
731
879
|
const onceKey = `${keyBase ?? event}:${gi}:${ei}`;
|
|
732
880
|
if (entry.once && onceFired.has(onceKey))
|
|
733
881
|
continue;
|
|
734
|
-
if (entry.type !== "command" && entry.type !== "http" && entry.type !== "prompt" && entry.type !== "agent") {
|
|
735
|
-
ctx.logger.warn("hook_entries_skipped", { event, type: entry.type, reason: "type_not_supported" });
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
882
|
if (entry.type === "prompt" || entry.type === "agent") {
|
|
739
883
|
const carrier = entry.type === "prompt" ? ctx.hookLlm : ctx.hookAgent;
|
|
740
884
|
if (!carrier) {
|
|
@@ -771,9 +915,7 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
|
|
|
771
915
|
onceFired.add(onceKey);
|
|
772
916
|
if (entry.type === "prompt" || entry.type === "agent")
|
|
773
917
|
llmEntries++;
|
|
774
|
-
const run =
|
|
775
|
-
: entry.type === "prompt" || entry.type === "agent" ? await runLlmHook(entry, payload, ctx, entry.type === "prompt" ? llmExtra : { system: ccAgentHookSystemFor(event), event })
|
|
776
|
-
: await runCommandHook(entry, payload, ctx);
|
|
918
|
+
const run = await runRunnableEntry(entry, payload, ctx, event, llmExtra);
|
|
777
919
|
if (run.spawnError) {
|
|
778
920
|
ctx.logger.warn(entry.type === "http" ? "hook_http_request_failed" : entry.type === "prompt" || entry.type === "agent" ? "hook_llm_failed" : "hook_command_spawn_failed", { event, type: entry.type, error: clip(run.spawnError, 300) });
|
|
779
921
|
if (!run.noticed)
|
|
@@ -828,12 +970,8 @@ async function runObserveOnlyEvent(event, groups, matchValue, payload, ctx, once
|
|
|
828
970
|
}
|
|
829
971
|
}
|
|
830
972
|
}
|
|
831
|
-
export function
|
|
973
|
+
export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
832
974
|
const configured = Object.keys(config).filter((e) => (config[e]?.length ?? 0) > 0);
|
|
833
|
-
const dark = configured.filter((e) => HOOK_EVENT_OWNER[e] === "engine" && !LIT_EVENTS.includes(e));
|
|
834
|
-
if (dark.length > 0) {
|
|
835
|
-
ctx.logger.warn("hook_events_not_fired", { events: dark, phase: 2 });
|
|
836
|
-
}
|
|
837
975
|
const shellOwned = configured.filter((e) => HOOK_EVENT_OWNER[e] === "shell");
|
|
838
976
|
if (shellOwned.length > 0) {
|
|
839
977
|
ctx.logger.debug("hook_events_shell_owned", { events: shellOwned });
|
|
@@ -855,9 +993,11 @@ export function createTaskHooks(config, ctx) {
|
|
|
855
993
|
return undefined;
|
|
856
994
|
}
|
|
857
995
|
const onceFired = new Set();
|
|
858
|
-
const
|
|
996
|
+
const translators = {};
|
|
859
997
|
if (pre) {
|
|
860
|
-
|
|
998
|
+
translators.PreToolUse = async (e) => {
|
|
999
|
+
const toolName = e.tool;
|
|
1000
|
+
const input = e.input;
|
|
861
1001
|
let currentInput = input;
|
|
862
1002
|
let rewritten = false;
|
|
863
1003
|
let asked;
|
|
@@ -870,18 +1010,18 @@ export function createTaskHooks(config, ctx) {
|
|
|
870
1010
|
gi++;
|
|
871
1011
|
const m = hookMatcherMatches(group.matcher, toolName);
|
|
872
1012
|
if (m === "invalid") {
|
|
873
|
-
ctx.logger.warn("hook_matcher_invalid", { event: "PreToolUse", matcher:
|
|
1013
|
+
ctx.logger.warn("hook_matcher_invalid", { event: "PreToolUse", matcher: echoCallerText(group.matcher ?? "", { maxChars: MATCHER_ECHO_CHARS }) });
|
|
874
1014
|
continue;
|
|
875
1015
|
}
|
|
876
1016
|
if (!m)
|
|
877
1017
|
continue;
|
|
878
1018
|
const groupInput = currentInput;
|
|
879
1019
|
const payload = {
|
|
880
|
-
...basePayload(ctx,
|
|
1020
|
+
...basePayload(ctx, e.permissionMode),
|
|
881
1021
|
hook_event_name: "PreToolUse",
|
|
882
1022
|
tool_name: toolName,
|
|
883
1023
|
tool_input: groupInput,
|
|
884
|
-
tool_use_id:
|
|
1024
|
+
tool_use_id: e.tool_use_id,
|
|
885
1025
|
};
|
|
886
1026
|
const singles = await runMatchingEntries("PreToolUse", [{ ...group, matcher: undefined }], toolName, payload, ctx, onceFired, deadlineAt, `PreToolUse:${gi}`);
|
|
887
1027
|
for (const { entry, run } of singles) {
|
|
@@ -964,21 +1104,23 @@ export function createTaskHooks(config, ctx) {
|
|
|
964
1104
|
return carried;
|
|
965
1105
|
return undefined;
|
|
966
1106
|
};
|
|
967
|
-
hooks.preToolUse = preToolUseHook;
|
|
968
1107
|
}
|
|
969
1108
|
if (post) {
|
|
970
|
-
|
|
1109
|
+
translators.PostToolUse = async (e) => {
|
|
1110
|
+
const toolName = e.tool;
|
|
1111
|
+
const input = e.input;
|
|
1112
|
+
const output = e.output;
|
|
971
1113
|
const contexts = [];
|
|
972
1114
|
const responseText = output.content
|
|
973
1115
|
.map((c) => (c && typeof c === "object" && typeof c.text === "string" ? c.text : ""))
|
|
974
1116
|
.join("");
|
|
975
1117
|
const payload = {
|
|
976
|
-
...basePayload(ctx,
|
|
1118
|
+
...basePayload(ctx, e.permissionMode),
|
|
977
1119
|
hook_event_name: "PostToolUse",
|
|
978
1120
|
tool_name: toolName,
|
|
979
1121
|
tool_input: input,
|
|
980
1122
|
tool_response: { isError: output.isError, text: clip(responseText, MAX_TOOL_RESPONSE_CHARS) },
|
|
981
|
-
tool_use_id:
|
|
1123
|
+
tool_use_id: e.tool_use_id,
|
|
982
1124
|
};
|
|
983
1125
|
const singles = await runMatchingEntries("PostToolUse", post, toolName, payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
|
|
984
1126
|
for (const { entry, run } of singles) {
|
|
@@ -1016,17 +1158,18 @@ export function createTaskHooks(config, ctx) {
|
|
|
1016
1158
|
const additionalContext = buildHookContext(contexts);
|
|
1017
1159
|
return additionalContext === undefined ? undefined : { additionalContext };
|
|
1018
1160
|
};
|
|
1019
|
-
hooks.postToolUse = postToolUseHook;
|
|
1020
1161
|
}
|
|
1021
1162
|
if (postFailure) {
|
|
1022
|
-
|
|
1163
|
+
translators.PostToolUseFailure = async (e) => {
|
|
1164
|
+
const toolName = e.tool;
|
|
1165
|
+
const f = e.failure;
|
|
1023
1166
|
const contexts = [];
|
|
1024
1167
|
const payload = {
|
|
1025
|
-
...basePayload(ctx,
|
|
1168
|
+
...basePayload(ctx, e.permissionMode),
|
|
1026
1169
|
hook_event_name: "PostToolUseFailure",
|
|
1027
1170
|
tool_name: toolName,
|
|
1028
|
-
tool_input: input,
|
|
1029
|
-
tool_use_id:
|
|
1171
|
+
tool_input: e.input,
|
|
1172
|
+
tool_use_id: e.tool_use_id,
|
|
1030
1173
|
error: clip(f.error, MAX_TOOL_RESPONSE_CHARS),
|
|
1031
1174
|
is_interrupt: f.isInterrupt,
|
|
1032
1175
|
};
|
|
@@ -1062,15 +1205,14 @@ export function createTaskHooks(config, ctx) {
|
|
|
1062
1205
|
const additionalContext = buildHookContext(contexts);
|
|
1063
1206
|
return additionalContext === undefined ? undefined : { additionalContext };
|
|
1064
1207
|
};
|
|
1065
|
-
hooks.postToolUseFailure = postToolUseFailureHook;
|
|
1066
1208
|
}
|
|
1067
1209
|
if (postBatch) {
|
|
1068
|
-
|
|
1210
|
+
translators.PostToolBatch = async (e) => {
|
|
1069
1211
|
const contexts = [];
|
|
1070
1212
|
const payload = {
|
|
1071
|
-
...basePayload(ctx,
|
|
1213
|
+
...basePayload(ctx, e.permissionMode),
|
|
1072
1214
|
hook_event_name: "PostToolBatch",
|
|
1073
|
-
tool_calls:
|
|
1215
|
+
tool_calls: e.batch.map((c) => ({
|
|
1074
1216
|
tool_name: c.toolName,
|
|
1075
1217
|
tool_input: boundedInputValue(c.input),
|
|
1076
1218
|
tool_use_id: c.toolCallId,
|
|
@@ -1105,12 +1247,11 @@ export function createTaskHooks(config, ctx) {
|
|
|
1105
1247
|
const additionalContext = buildHookContext(contexts);
|
|
1106
1248
|
return additionalContext === undefined ? undefined : { additionalContext };
|
|
1107
1249
|
};
|
|
1108
|
-
hooks.postToolBatch = postToolBatchHook;
|
|
1109
1250
|
}
|
|
1110
1251
|
if (promptSubmit) {
|
|
1111
|
-
|
|
1252
|
+
translators.UserPromptSubmit = async (e) => {
|
|
1112
1253
|
const contexts = [];
|
|
1113
|
-
const payload = { ...basePayload(ctx,
|
|
1254
|
+
const payload = { ...basePayload(ctx, e.permissionMode), hook_event_name: "UserPromptSubmit", prompt: clip(e.prompt, MAX_TOOL_INPUT_CHARS) };
|
|
1114
1255
|
const singles = await runMatchingEntries("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, onceFired, Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
|
|
1115
1256
|
for (const { entry, run } of singles) {
|
|
1116
1257
|
if (run.code === 2) {
|
|
@@ -1145,10 +1286,9 @@ export function createTaskHooks(config, ctx) {
|
|
|
1145
1286
|
const additionalContext = buildHookContext(contexts);
|
|
1146
1287
|
return additionalContext === undefined ? undefined : { additionalContext };
|
|
1147
1288
|
};
|
|
1148
|
-
hooks.userPromptSubmit = userPromptSubmitHook;
|
|
1149
1289
|
}
|
|
1150
1290
|
if (stopEntries) {
|
|
1151
|
-
|
|
1291
|
+
translators.Stop = async (sctx) => {
|
|
1152
1292
|
const payload = { ...basePayload(ctx, sctx.permissionMode), hook_event_name: "Stop", stop_hook_active: sctx.stopHookActive };
|
|
1153
1293
|
let llmExtra;
|
|
1154
1294
|
const wantsPrompt = stopEntries.some((g) => g.hooks.some((h) => h.type === "prompt"));
|
|
@@ -1237,10 +1377,9 @@ export function createTaskHooks(config, ctx) {
|
|
|
1237
1377
|
...buildHookContextField(contexts),
|
|
1238
1378
|
};
|
|
1239
1379
|
};
|
|
1240
|
-
hooks.stop = stopHook;
|
|
1241
1380
|
}
|
|
1242
1381
|
if (stopFailureEntries) {
|
|
1243
|
-
|
|
1382
|
+
translators.StopFailure = async (fctx) => {
|
|
1244
1383
|
const turnError = toAssistantTurnError(fctx.errorKind);
|
|
1245
1384
|
const payload = {
|
|
1246
1385
|
...basePayload(ctx, undefined),
|
|
@@ -1250,10 +1389,9 @@ export function createTaskHooks(config, ctx) {
|
|
|
1250
1389
|
};
|
|
1251
1390
|
await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, onceFired);
|
|
1252
1391
|
};
|
|
1253
|
-
hooks.stopFailure = stopFailureHook;
|
|
1254
1392
|
}
|
|
1255
1393
|
if (preCompactEntries) {
|
|
1256
|
-
|
|
1394
|
+
translators.PreCompact = async (pctx) => {
|
|
1257
1395
|
const payload = {
|
|
1258
1396
|
...basePayload(ctx, undefined),
|
|
1259
1397
|
hook_event_name: "PreCompact",
|
|
@@ -1284,10 +1422,9 @@ export function createTaskHooks(config, ctx) {
|
|
|
1284
1422
|
}
|
|
1285
1423
|
return undefined;
|
|
1286
1424
|
};
|
|
1287
|
-
hooks.preCompact = preCompactHook;
|
|
1288
1425
|
}
|
|
1289
1426
|
if (postCompactEntries) {
|
|
1290
|
-
|
|
1427
|
+
translators.PostCompact = async (cctx) => {
|
|
1291
1428
|
const payload = {
|
|
1292
1429
|
...basePayload(ctx, undefined),
|
|
1293
1430
|
hook_event_name: "PostCompact",
|
|
@@ -1296,177 +1433,16 @@ export function createTaskHooks(config, ctx) {
|
|
|
1296
1433
|
};
|
|
1297
1434
|
await runObserveOnlyEvent("PostCompact", postCompactEntries, cctx.trigger, payload, ctx, onceFired);
|
|
1298
1435
|
};
|
|
1299
|
-
hooks.postCompact = postCompactHook;
|
|
1300
|
-
}
|
|
1301
|
-
return hooks;
|
|
1302
|
-
}
|
|
1303
|
-
const HOOK_SLOT_FOLD = {
|
|
1304
|
-
preToolUse: "custom",
|
|
1305
|
-
preToolUseObservational: "custom",
|
|
1306
|
-
postToolUse: "custom",
|
|
1307
|
-
postToolUseFailure: "custom",
|
|
1308
|
-
postToolBatch: "custom",
|
|
1309
|
-
userPromptSubmit: "custom",
|
|
1310
|
-
stop: "custom",
|
|
1311
|
-
preCompact: "custom",
|
|
1312
|
-
timeoutMs: "scalar",
|
|
1313
|
-
postCompact: "observe-void",
|
|
1314
|
-
stopFailure: "observe-void",
|
|
1315
|
-
permissionDenied: "observe-void",
|
|
1316
|
-
};
|
|
1317
|
-
const OBSERVE_VOID_HOOK_SLOTS = Object.keys(HOOK_SLOT_FOLD).filter((k) => HOOK_SLOT_FOLD[k] === "observe-void");
|
|
1318
|
-
export function composeHooks(deployment, task) {
|
|
1319
|
-
if (!deployment)
|
|
1320
|
-
return task;
|
|
1321
|
-
const d = deployment;
|
|
1322
|
-
const t = task;
|
|
1323
|
-
const out = {};
|
|
1324
|
-
const timeoutMs = d.timeoutMs ?? t.timeoutMs;
|
|
1325
|
-
if (timeoutMs !== undefined)
|
|
1326
|
-
out.timeoutMs = timeoutMs;
|
|
1327
|
-
if (d.preToolUse || t.preToolUse) {
|
|
1328
|
-
const bothObservational = (d.preToolUse === undefined || d.preToolUseObservational === true) &&
|
|
1329
|
-
(t.preToolUse === undefined || t.preToolUseObservational === true);
|
|
1330
|
-
if (bothObservational)
|
|
1331
|
-
out.preToolUseObservational = true;
|
|
1332
|
-
out.preToolUse = async (toolName, input, ctx) => {
|
|
1333
|
-
let current = input;
|
|
1334
|
-
const contexts = [];
|
|
1335
|
-
let asked;
|
|
1336
|
-
let allowedFor;
|
|
1337
|
-
for (const slot of [d.preToolUse?.bind(d), t.preToolUse?.bind(t)]) {
|
|
1338
|
-
if (!slot)
|
|
1339
|
-
continue;
|
|
1340
|
-
const r = receiveHookResult(await slot(toolName, current, ctx));
|
|
1341
|
-
if (r === undefined || r === null || r === false)
|
|
1342
|
-
continue;
|
|
1343
|
-
if (isEngineRefusal(r))
|
|
1344
|
-
return r;
|
|
1345
|
-
if (r.additionalContext)
|
|
1346
|
-
contexts.push(r.additionalContext);
|
|
1347
|
-
const action = r.action;
|
|
1348
|
-
const updatedInput = r.updatedInput;
|
|
1349
|
-
switch (action) {
|
|
1350
|
-
case "deny":
|
|
1351
|
-
return { ...r, ...buildHookContextField(contexts) };
|
|
1352
|
-
case "ask":
|
|
1353
|
-
asked = r;
|
|
1354
|
-
break;
|
|
1355
|
-
case "allow":
|
|
1356
|
-
if (updatedInput !== undefined)
|
|
1357
|
-
current = updatedInput;
|
|
1358
|
-
allowedFor = { input: current };
|
|
1359
|
-
break;
|
|
1360
|
-
case undefined:
|
|
1361
|
-
if (updatedInput !== undefined)
|
|
1362
|
-
current = updatedInput;
|
|
1363
|
-
break;
|
|
1364
|
-
default: {
|
|
1365
|
-
const unread = action;
|
|
1366
|
-
void unread;
|
|
1367
|
-
return r;
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
if (asked) {
|
|
1372
|
-
const askedUpdated = asked.updatedInput;
|
|
1373
|
-
return {
|
|
1374
|
-
...asked,
|
|
1375
|
-
...(askedUpdated === undefined && current !== input ? { updatedInput: current } : {}),
|
|
1376
|
-
...buildHookContextField(contexts),
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
const carried = { ...(current !== input ? { updatedInput: current } : {}), ...buildHookContextField(contexts) };
|
|
1380
|
-
if (allowedFor !== undefined && allowedFor.input === current)
|
|
1381
|
-
return { action: "allow", ...carried };
|
|
1382
|
-
if (current !== input || contexts.length > 0)
|
|
1383
|
-
return carried;
|
|
1384
|
-
return undefined;
|
|
1385
|
-
};
|
|
1386
1436
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
...buildHookContextField(contexts),
|
|
1398
|
-
};
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
if (d.postToolUseFailure || t.postToolUseFailure) {
|
|
1402
|
-
out.postToolUseFailure = async (toolName, input, failure, ctx) => {
|
|
1403
|
-
const rd = d.postToolUseFailure ? await d.postToolUseFailure(toolName, input, failure, ctx) : undefined;
|
|
1404
|
-
const rt = t.postToolUseFailure ? await t.postToolUseFailure(toolName, input, failure, ctx) : undefined;
|
|
1405
|
-
const contexts = [rd?.additionalContext, rt?.additionalContext].filter((c) => typeof c === "string" && c.length > 0);
|
|
1406
|
-
const additionalContext = buildHookContext(contexts);
|
|
1407
|
-
return additionalContext === undefined ? undefined : { additionalContext };
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
|
-
if (d.postToolBatch || t.postToolBatch) {
|
|
1411
|
-
out.postToolBatch = async (calls, meta, bctx) => {
|
|
1412
|
-
const rd = d.postToolBatch ? await d.postToolBatch(calls, meta, bctx) : undefined;
|
|
1413
|
-
const rt = t.postToolBatch ? await t.postToolBatch(calls, meta, bctx) : undefined;
|
|
1414
|
-
const contexts = [rd?.additionalContext, rt?.additionalContext].filter((c) => typeof c === "string" && c.length > 0);
|
|
1415
|
-
const additionalContext = buildHookContext(contexts);
|
|
1416
|
-
return additionalContext === undefined ? undefined : { additionalContext };
|
|
1417
|
-
};
|
|
1418
|
-
}
|
|
1419
|
-
if (d.userPromptSubmit || t.userPromptSubmit) {
|
|
1420
|
-
out.userPromptSubmit = async (prompt, uctx) => {
|
|
1421
|
-
const rd = d.userPromptSubmit ? await d.userPromptSubmit(prompt, uctx) : undefined;
|
|
1422
|
-
if (rd?.block)
|
|
1423
|
-
return { ...rd, ...buildHookContextField(rd.additionalContext ? [rd.additionalContext] : []) };
|
|
1424
|
-
const rt = t.userPromptSubmit ? await t.userPromptSubmit(prompt, uctx) : undefined;
|
|
1425
|
-
if (!rd && !rt)
|
|
1426
|
-
return undefined;
|
|
1427
|
-
const contexts = [rd?.additionalContext, rt?.additionalContext].filter((c) => typeof c === "string" && c.length > 0);
|
|
1428
|
-
return { ...(rt?.block ? { block: rt.block } : {}), ...buildHookContextField(contexts) };
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
if (d.stop || t.stop) {
|
|
1432
|
-
out.stop = async (sctx) => {
|
|
1433
|
-
const rd = d.stop ? await d.stop(sctx) : undefined;
|
|
1434
|
-
if (rd?.block)
|
|
1435
|
-
return { ...rd, ...buildHookContextField(rd.additionalContext ? [rd.additionalContext] : []) };
|
|
1436
|
-
const rt = t.stop ? await t.stop(sctx) : undefined;
|
|
1437
|
-
if (!rd && !rt)
|
|
1438
|
-
return undefined;
|
|
1439
|
-
const contexts = [rd?.additionalContext, rt?.additionalContext].filter((c) => typeof c === "string" && c.length > 0);
|
|
1440
|
-
return { ...(rt?.block ? { block: rt.block } : {}), ...buildHookContextField(contexts) };
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
if (d.preCompact || t.preCompact) {
|
|
1444
|
-
out.preCompact = async (pctx) => {
|
|
1445
|
-
const rd = d.preCompact ? await d.preCompact(pctx) : undefined;
|
|
1446
|
-
const rt = t.preCompact ? await t.preCompact(pctx) : undefined;
|
|
1447
|
-
if (!rd && !rt)
|
|
1448
|
-
return undefined;
|
|
1449
|
-
const block = rd?.block ?? rt?.block;
|
|
1450
|
-
const instructions = [rd?.additionalInstructions, rt?.additionalInstructions].filter((c) => typeof c === "string" && c.length > 0);
|
|
1451
|
-
return {
|
|
1452
|
-
...(block !== undefined ? { block } : {}),
|
|
1453
|
-
...(instructions.length ? { additionalInstructions: instructions.join("\n") } : {}),
|
|
1454
|
-
};
|
|
1455
|
-
};
|
|
1456
|
-
}
|
|
1457
|
-
for (const slot of OBSERVE_VOID_HOOK_SLOTS) {
|
|
1458
|
-
const ds = d[slot];
|
|
1459
|
-
const ts = t[slot];
|
|
1460
|
-
if (ds && ts) {
|
|
1461
|
-
out[slot] = async (...args) => {
|
|
1462
|
-
await ds(...args);
|
|
1463
|
-
await ts(...args);
|
|
1464
|
-
};
|
|
1465
|
-
}
|
|
1466
|
-
else if (ds || ts) {
|
|
1467
|
-
out[slot] = (ds ?? ts);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
return out;
|
|
1437
|
+
const events = Object.keys(translators);
|
|
1438
|
+
return {
|
|
1439
|
+
name: SETTINGS_HOOKS_REGISTRATION,
|
|
1440
|
+
uses: { events: events.flatMap((event) => SETTINGS_HOOK_STATIONS[event]), calls: [] },
|
|
1441
|
+
budgetMs,
|
|
1442
|
+
register(on) {
|
|
1443
|
+
for (const event of events)
|
|
1444
|
+
hangTranslator(on, event, translators);
|
|
1445
|
+
},
|
|
1446
|
+
};
|
|
1471
1447
|
}
|
|
1472
1448
|
//# sourceMappingURL=hook-runner.js.map
|