@qvac/harness 0.7.0 → 1.0.0-rc.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/CHANGELOG.md +42 -1
- package/README.md +239 -2
- package/dist-lib/index.d.ts +58 -3
- package/dist-lib/index.js +736 -239
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
- package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
- package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
- package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
- package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
- package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
- package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
- package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
- package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
- package/dist-lib/lib/capabilities/completion.d.ts +18 -335
- package/dist-lib/lib/capabilities/completion.js +20 -1301
- package/dist-lib/lib/capabilities/download.d.ts +1 -1
- package/dist-lib/lib/capabilities/download.js +7 -2
- package/dist-lib/lib/capabilities/invoke.js +5 -3
- package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
- package/dist-lib/lib/capabilities/tool-plan.js +66 -0
- package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
- package/dist-lib/lib/capabilities/tool-turns.js +30 -2
- package/dist-lib/lib/capabilities/types.d.ts +287 -0
- package/dist-lib/lib/capabilities/types.js +1 -0
- package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
- package/dist-lib/lib/completion-lifecycle.js +126 -0
- package/dist-lib/lib/connect.d.ts +10 -3
- package/dist-lib/lib/connect.js +329 -23
- package/dist-lib/lib/contract-types.d.ts +19 -2
- package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
- package/dist-lib/lib/harness-lifecycle.js +129 -0
- package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
- package/dist-lib/lib/helpers/detach-json.js +146 -0
- package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
- package/dist-lib/lib/helpers/markup-scanner.js +156 -0
- package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
- package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
- package/dist-lib/lib/helpers/race-abort.js +2 -7
- package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
- package/dist-lib/lib/helpers/recovery-required.js +1 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
- package/dist-lib/lib/helpers/run-outcome.js +8 -5
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
- package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
- package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
- package/dist-lib/lib/hooks/pipeline.js +24 -2
- package/dist-lib/lib/hooks/run.d.ts +2 -1
- package/dist-lib/lib/hooks/run.js +19 -7
- package/dist-lib/lib/hooks/types.d.ts +17 -6
- package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
- package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
- package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
- package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
- package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
- package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
- package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
- package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
- package/dist-lib/lib/machine/completion/context.d.ts +9 -0
- package/dist-lib/lib/machine/completion/context.js +44 -0
- package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
- package/dist-lib/lib/machine/completion/contract.js +1 -0
- package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
- package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
- package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
- package/dist-lib/lib/machine/completion/implementations.js +152 -0
- package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
- package/dist-lib/lib/machine/completion/machine.js +511 -0
- package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
- package/dist-lib/lib/machine/completion/ports.js +224 -0
- package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
- package/dist-lib/lib/machine/completion/rules.js +115 -0
- package/dist-lib/lib/machine/completion/types.d.ts +493 -0
- package/dist-lib/lib/machine/completion/types.js +1 -0
- package/dist-lib/lib/machine/engine.d.ts +93 -0
- package/dist-lib/lib/machine/engine.js +483 -0
- package/dist-lib/lib/machine/errors.d.ts +44 -0
- package/dist-lib/lib/machine/errors.js +373 -0
- package/dist-lib/lib/machine/guards.d.ts +2 -0
- package/dist-lib/lib/machine/guards.js +6 -0
- package/dist-lib/lib/machine/index.d.ts +20 -0
- package/dist-lib/lib/machine/index.js +11 -0
- package/dist-lib/lib/machine/replay-output.d.ts +24 -0
- package/dist-lib/lib/machine/replay-output.js +50 -0
- package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
- package/dist-lib/lib/machine/stable-stringify.js +11 -0
- package/dist-lib/lib/models/model-handle.d.ts +5 -1
- package/dist-lib/lib/models/model-handle.js +8 -3
- package/dist-lib/lib/models/model-list.d.ts +5 -3
- package/dist-lib/lib/models/model-pool.d.ts +18 -7
- package/dist-lib/lib/models/model-pool.js +126 -38
- package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
- package/dist-lib/lib/run-log/history-codec.js +973 -0
- package/dist-lib/lib/run-log/patch.d.ts +6 -0
- package/dist-lib/lib/run-log/patch.js +93 -0
- package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
- package/dist-lib/lib/run-log/persisted-actor.js +218 -0
- package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
- package/dist-lib/lib/run-log/protocol-types.js +1 -0
- package/dist-lib/lib/run-log/protocol.d.ts +27 -0
- package/dist-lib/lib/run-log/protocol.js +1056 -0
- package/dist-lib/lib/run-log/replay.d.ts +25 -0
- package/dist-lib/lib/run-log/replay.js +129 -0
- package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
- package/dist-lib/lib/run-log/run-journal.js +783 -0
- package/dist-lib/lib/run-log-inspect.d.ts +25 -0
- package/dist-lib/lib/run-log-inspect.js +627 -0
- package/dist-lib/lib/run-log-protocol.d.ts +2 -0
- package/dist-lib/lib/run-log-protocol.js +1 -0
- package/dist-lib/lib/run-log-redact.d.ts +27 -0
- package/dist-lib/lib/run-log-redact.js +1107 -0
- package/dist-lib/lib/serve.js +308 -12
- package/dist-lib/lib/spawn.js +3 -2
- package/dist-lib/lib/tool-call-display.d.ts +1 -21
- package/dist-lib/lib/tools/exec.d.ts +2 -1
- package/dist-lib/lib/tools/exec.js +57 -2
- package/dist-lib/lib/tools/generate-image.js +1 -1
- package/dist-lib/lib/tools/generate-music.js +1 -1
- package/dist-lib/lib/tools/http-request.d.ts +2 -1
- package/dist-lib/lib/tools/http-request.js +2 -1
- package/dist-lib/lib/tools/mcp-call.js +8 -1
- package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
- package/dist-lib/lib/tools/operations/builders.js +1 -0
- package/dist-lib/lib/tools/operations/compile.js +11 -1
- package/dist-lib/lib/tools/operations/manifest.js +1 -0
- package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
- package/dist-lib/lib/tools/outbound-guard.js +25 -8
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +35 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
- package/dist-lib/lib/wire.d.ts +5 -0
- package/dist-lib/lib/wire.js +128 -5
- package/dist-lib/schema/harness.js +22 -6
- package/dist-lib/spec/hrpc/messages.js +100 -18
- package/dist-lib/spec/hyperschema/index.js +100 -18
- package/dist-lib/spec/hyperschema/schema.json +74 -1
- package/dist-lib/spec/hyperschema/types.d.ts +11 -3
- package/package.json +19 -1
- package/patches/@qvac%2Finference@0.20.0.patch +67 -0
- package/patches/bare-stow@0.1.5.patch +68 -0
- package/patches/hypercore-blob-server@1.15.0.patch +15 -0
- package/patches/localwatch@1.4.3.patch +13 -0
- package/patches/pyodide@314.0.2.patch +144 -0
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
super('aborted');
|
|
4
|
-
this.name = 'AbortedError';
|
|
5
|
-
Error.captureStackTrace?.(this, AbortedError);
|
|
6
|
-
}
|
|
7
|
-
}
|
|
1
|
+
import { AbortedError } from '../machine/index.js';
|
|
2
|
+
export { AbortedError };
|
|
8
3
|
export function raceAbort(promise, signal) {
|
|
9
4
|
if (!signal)
|
|
10
5
|
return promise;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RecoveryRequired, type Operation } from '../machine/index.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RecoveryRequired } from '../machine/index.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { RunOutcome } from '../machine/index.ts';
|
|
1
2
|
import type { ToolExecutionOutcome } from '../tool-call-display.ts';
|
|
2
|
-
export type RunOutcome
|
|
3
|
+
export type { RunOutcome };
|
|
3
4
|
export declare const DEGENERATE_ANSWER_FALLBACK = "I couldn't complete that step and don't have a useful result to share.";
|
|
4
5
|
export declare const ROUND_CAP_ANSWER = "I ran out of steps before I could finish that. Ask me to continue and I'll pick up where I left off.";
|
|
5
6
|
export declare const SEARCH_CAPPED_ANSWER = "I searched the web but couldn't find the answer in the results I got.";
|
|
@@ -19,9 +20,16 @@ export interface RunOutcomeTracker {
|
|
|
19
20
|
record(result: unknown, outcome: ToolExecutionOutcome): void;
|
|
20
21
|
finishRound(): void;
|
|
21
22
|
confirmWrite(result: unknown): void;
|
|
23
|
+
snapshot(): RunOutcomeState;
|
|
22
24
|
facts(): RunOutcomeFacts;
|
|
23
25
|
}
|
|
24
|
-
export
|
|
26
|
+
export interface RunOutcomeState {
|
|
27
|
+
confirmedWrites: string[];
|
|
28
|
+
roundFailure: string | null;
|
|
29
|
+
latestFailure: string | null;
|
|
30
|
+
hasRoundResult: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare function createRunOutcomeTracker(state?: RunOutcomeState): RunOutcomeTracker;
|
|
25
33
|
export declare function normalizeToolExecutionOutcome(result: unknown, outcome: ToolExecutionOutcome): ToolExecutionOutcome;
|
|
26
34
|
export declare function failureFromToolExecution(result: unknown, outcome: ToolExecutionOutcome): string | null;
|
|
27
35
|
export declare function degenerateAnswer(outcome: Exclude<RunOutcome, 'answered'>, history: ReadonlyArray<HistoryTurn>, facts?: RunOutcomeFacts): string;
|
|
@@ -13,11 +13,11 @@ const TOOL_RESULT_HEADER_RE = /^\[Tool: [^(\]]+ \([^)]+\)\]\n/;
|
|
|
13
13
|
const WEB_SEARCH_TURN_RE = /^\[Tool: web_search /;
|
|
14
14
|
const RESULT_ENTRY_RE = /\[\d+\] ([^\n]+)\nSource: (\S+)/g;
|
|
15
15
|
const WRITE_ID_RE = /[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}/gi;
|
|
16
|
-
export function createRunOutcomeTracker() {
|
|
17
|
-
const confirmedWrites = [];
|
|
18
|
-
let roundFailure = null;
|
|
19
|
-
let latestFailure = null;
|
|
20
|
-
let hasRoundResult = false;
|
|
16
|
+
export function createRunOutcomeTracker(state) {
|
|
17
|
+
const confirmedWrites = [...(state?.confirmedWrites ?? [])];
|
|
18
|
+
let roundFailure = state?.roundFailure ?? null;
|
|
19
|
+
let latestFailure = state?.latestFailure ?? null;
|
|
20
|
+
let hasRoundResult = state?.hasRoundResult ?? false;
|
|
21
21
|
return {
|
|
22
22
|
confirmedWrites,
|
|
23
23
|
beginRound() {
|
|
@@ -42,6 +42,9 @@ export function createRunOutcomeTracker() {
|
|
|
42
42
|
confirmedWrites.push(write);
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
+
snapshot() {
|
|
46
|
+
return { confirmedWrites: [...confirmedWrites], roundFailure, latestFailure, hasRoundResult };
|
|
47
|
+
},
|
|
45
48
|
facts() {
|
|
46
49
|
return { latestFailure, confirmedWrites };
|
|
47
50
|
}
|
|
@@ -10,6 +10,8 @@ export interface KnownTool {
|
|
|
10
10
|
readonly name: string;
|
|
11
11
|
}
|
|
12
12
|
export declare function recoverMarkupToolCalls(text: string, knownTools: ReadonlyArray<KnownTool>): RecoveredToolCall[];
|
|
13
|
+
export declare function redactToolCallMarkup(text: string, redactArgs: (name: string, args: Record<string, unknown>) => Record<string, unknown>): string;
|
|
14
|
+
export declare function redactBareParameterMarkup(text: string, isSensitiveName: (name: string) => boolean): string;
|
|
13
15
|
export declare function stripToolCallMarkup(text: string): string;
|
|
14
16
|
export declare function stripToolResponseBlocks(text: string): string;
|
|
15
17
|
export interface MarkupToolCall {
|
|
@@ -1,47 +1,8 @@
|
|
|
1
1
|
import { isRecord } from '../guards.js';
|
|
2
|
-
|
|
3
|
-
const FUNCTION_CLOSE = '</function>';
|
|
2
|
+
import { namedMarkupBlocks, scanNamedMarkup } from './markup-scanner.js';
|
|
4
3
|
const TOOL_RESPONSE = { open: '<tool_response>', close: '</tool_response>' };
|
|
5
4
|
const TOOL_CALL = { open: '<tool_call>', close: '</tool_call>' };
|
|
6
|
-
const PARAMETER_OPEN = '<parameter=';
|
|
7
|
-
const PARAMETER_CLOSE = '</parameter>';
|
|
8
5
|
const MAX_NAME_LENGTH = 64;
|
|
9
|
-
function* markupBlocks(text, open, close) {
|
|
10
|
-
let cursor = 0;
|
|
11
|
-
for (;;) {
|
|
12
|
-
const start = text.indexOf(open, cursor);
|
|
13
|
-
if (start === -1) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const nameStart = start + open.length;
|
|
17
|
-
cursor = nameStart;
|
|
18
|
-
const name = nameOf(text, nameStart);
|
|
19
|
-
if (name === null) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
const bodyStart = nameStart + name.length + 1;
|
|
23
|
-
const bodyEnd = text.indexOf(close, bodyStart);
|
|
24
|
-
if (bodyEnd === -1) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
yield {
|
|
28
|
-
name: name.trim(),
|
|
29
|
-
body: text.slice(bodyStart, bodyEnd),
|
|
30
|
-
start,
|
|
31
|
-
end: bodyEnd + close.length
|
|
32
|
-
};
|
|
33
|
-
cursor = bodyEnd + close.length;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function nameOf(text, nameStart) {
|
|
37
|
-
const window = text.slice(nameStart, nameStart + MAX_NAME_LENGTH + 1);
|
|
38
|
-
const nameEnd = window.indexOf('>');
|
|
39
|
-
if (nameEnd === -1) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
const name = window.slice(0, nameEnd);
|
|
43
|
-
return name.includes('\n') ? null : name;
|
|
44
|
-
}
|
|
45
6
|
export const MAX_RAW_CALL_CHARS = 4096;
|
|
46
7
|
export function clipRawCall(raw) {
|
|
47
8
|
return raw.length > MAX_RAW_CALL_CHARS ? raw.slice(0, MAX_RAW_CALL_CHARS) : raw;
|
|
@@ -49,34 +10,172 @@ export function clipRawCall(raw) {
|
|
|
49
10
|
export function recoverMarkupToolCalls(text, knownTools) {
|
|
50
11
|
const known = new Set(knownTools.map((tool) => tool.name));
|
|
51
12
|
const calls = [];
|
|
52
|
-
for (const block of
|
|
53
|
-
if (
|
|
13
|
+
for (const block of namedMarkupBlocks(text, 'function', MAX_NAME_LENGTH)) {
|
|
14
|
+
if (block.nested) {
|
|
15
|
+
calls.push(...recoverLegacyCalls(text.slice(block.start, block.end), known));
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (!block.name || !known.has(block.name)) {
|
|
54
19
|
continue;
|
|
20
|
+
}
|
|
55
21
|
calls.push({
|
|
56
|
-
id:
|
|
22
|
+
id: '',
|
|
57
23
|
name: block.name,
|
|
58
|
-
arguments: decodeArguments(block.body),
|
|
24
|
+
arguments: decodeArguments(block.body).args,
|
|
59
25
|
raw: clipRawCall(text.slice(block.start, block.end))
|
|
60
26
|
});
|
|
61
27
|
}
|
|
62
|
-
return calls;
|
|
28
|
+
return calls.map((call, index) => ({ ...call, id: `recovered-markup-${index}` }));
|
|
29
|
+
}
|
|
30
|
+
function recoverLegacyCalls(text, known) {
|
|
31
|
+
const calls = [];
|
|
32
|
+
let cursor = 0;
|
|
33
|
+
for (;;) {
|
|
34
|
+
const start = text.indexOf('<function=', cursor);
|
|
35
|
+
if (start === -1) {
|
|
36
|
+
return calls;
|
|
37
|
+
}
|
|
38
|
+
const nameStart = start + '<function='.length;
|
|
39
|
+
const nameEnd = text.indexOf('>', nameStart);
|
|
40
|
+
const close = nameEnd === -1 ? -1 : text.indexOf('</function>', nameEnd + 1);
|
|
41
|
+
if (nameEnd === -1 || nameEnd - nameStart > MAX_NAME_LENGTH || close === -1) {
|
|
42
|
+
return calls;
|
|
43
|
+
}
|
|
44
|
+
const name = text.slice(nameStart, nameEnd).trim();
|
|
45
|
+
const end = close + '</function>'.length;
|
|
46
|
+
if (name && known.has(name)) {
|
|
47
|
+
calls.push({
|
|
48
|
+
id: '',
|
|
49
|
+
name,
|
|
50
|
+
arguments: decodeLegacyArguments(text.slice(nameEnd + 1, close), 64),
|
|
51
|
+
raw: clipRawCall(text.slice(start, end))
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
cursor = end;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function redactToolCallMarkup(text, redactArgs) {
|
|
58
|
+
let out = '';
|
|
59
|
+
let cursor = 0;
|
|
60
|
+
let changed = false;
|
|
61
|
+
for (const block of namedMarkupBlocks(text, 'function', Infinity)) {
|
|
62
|
+
const replacement = redactedMarkupBlock(block, redactArgs);
|
|
63
|
+
if (replacement === null) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
out += text.slice(cursor, block.start) + replacement;
|
|
67
|
+
cursor = block.end;
|
|
68
|
+
changed = true;
|
|
69
|
+
}
|
|
70
|
+
out += text.slice(cursor);
|
|
71
|
+
return changed ? out : text;
|
|
72
|
+
}
|
|
73
|
+
function redactedMarkupBlock(block, redactArgs) {
|
|
74
|
+
if (block.nested) {
|
|
75
|
+
return '<function=redacted></function>';
|
|
76
|
+
}
|
|
77
|
+
const { args: decoded, ambiguous } = decodeArguments(block.body, Infinity);
|
|
78
|
+
if (ambiguous) {
|
|
79
|
+
return `<function=${block.name}></function>`;
|
|
80
|
+
}
|
|
81
|
+
const redacted = redactArgs(block.name, decoded);
|
|
82
|
+
const replacement = deepEqual(decoded, redacted)
|
|
83
|
+
? null
|
|
84
|
+
: markupForCall({ name: block.name, arguments: redacted });
|
|
85
|
+
return replacement;
|
|
86
|
+
}
|
|
87
|
+
export function redactBareParameterMarkup(text, isSensitiveName) {
|
|
88
|
+
if (!/<parameter/i.test(text)) {
|
|
89
|
+
return text;
|
|
90
|
+
}
|
|
91
|
+
let out = '';
|
|
92
|
+
let cursor = 0;
|
|
93
|
+
let changed = false;
|
|
94
|
+
for (;;) {
|
|
95
|
+
const open = scanNamedMarkup(text, 'parameter', { from: cursor, maxNameLength: Infinity }).token;
|
|
96
|
+
if (!open) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
if (open.kind === 'close' || !isSensitiveName(open.name ?? '')) {
|
|
100
|
+
cursor = open.end;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const close = scanNamedMarkup(text, 'parameter', {
|
|
104
|
+
from: open.end,
|
|
105
|
+
maxNameLength: Infinity
|
|
106
|
+
}).token;
|
|
107
|
+
out += text.slice(cursor, open.end);
|
|
108
|
+
changed = true;
|
|
109
|
+
if (close?.kind === 'close') {
|
|
110
|
+
out += `[redacted]${text.slice(close.start, close.end)}`;
|
|
111
|
+
cursor = close.end;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
out += '[redacted]';
|
|
115
|
+
cursor = text.length;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
out += text.slice(cursor);
|
|
120
|
+
return redactRunOnParameterOpeners(changed ? out : text, isSensitiveName);
|
|
121
|
+
}
|
|
122
|
+
function redactRunOnParameterOpeners(text, isSensitiveName) {
|
|
123
|
+
const opener = /<parameter\s*=\s*([^\s<>]+)\s+(?=[^\s>])/gi;
|
|
124
|
+
const closer = /<\/parameter>/gi;
|
|
125
|
+
let out = '';
|
|
126
|
+
let cursor = 0;
|
|
127
|
+
for (let match = opener.exec(text); match; match = opener.exec(text)) {
|
|
128
|
+
if (!isSensitiveName(match[1])) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const valueStart = match.index + match[0].length;
|
|
132
|
+
closer.lastIndex = valueStart;
|
|
133
|
+
const close = closer.exec(text);
|
|
134
|
+
out += `${text.slice(cursor, valueStart)}[redacted]`;
|
|
135
|
+
if (!close) {
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
cursor = close.index;
|
|
139
|
+
opener.lastIndex = close.index;
|
|
140
|
+
}
|
|
141
|
+
return cursor === 0 ? text : out + text.slice(cursor);
|
|
142
|
+
}
|
|
143
|
+
function deepEqual(a, b) {
|
|
144
|
+
if (a === b)
|
|
145
|
+
return true;
|
|
146
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
147
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return a.every((value, index) => deepEqual(value, b[index]));
|
|
151
|
+
}
|
|
152
|
+
if (!isRecord(a) || !isRecord(b)) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const aKeys = Object.keys(a);
|
|
156
|
+
const bKeys = Object.keys(b);
|
|
157
|
+
if (aKeys.length !== bKeys.length) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return aKeys.every((key) => Object.hasOwn(b, key) && deepEqual(a[key], b[key]));
|
|
63
161
|
}
|
|
64
162
|
export function stripToolCallMarkup(text) {
|
|
65
163
|
let out = '';
|
|
66
164
|
let cursor = 0;
|
|
67
|
-
for (const block of
|
|
165
|
+
for (const block of namedMarkupBlocks(text, 'function', MAX_NAME_LENGTH)) {
|
|
68
166
|
out += text.slice(cursor, block.start);
|
|
69
167
|
cursor = block.end;
|
|
70
168
|
}
|
|
71
169
|
out += text.slice(cursor);
|
|
72
|
-
const
|
|
73
|
-
const withoutCalls = dangling === -1 ? out : out.slice(0, dangling);
|
|
74
|
-
const withoutSpans = stripSpans(stripToolResponseBlocks(withoutCalls), TOOL_CALL);
|
|
170
|
+
const withoutSpans = stripSpans(stripToolResponseBlocks(out), TOOL_CALL);
|
|
75
171
|
return withoutSpans
|
|
76
172
|
.replace(/<\/?(tool_call|tool_response)>/gi, '')
|
|
77
173
|
.replace(/\n{3,}/g, '\n\n')
|
|
78
174
|
.trim();
|
|
79
175
|
}
|
|
176
|
+
function lowerAscii(char) {
|
|
177
|
+
return char.toLowerCase();
|
|
178
|
+
}
|
|
80
179
|
function stripSpans(text, span) {
|
|
81
180
|
const hay = text.replace(/[A-Z]/g, lowerAscii);
|
|
82
181
|
let out = '';
|
|
@@ -95,26 +194,9 @@ function stripSpans(text, span) {
|
|
|
95
194
|
}
|
|
96
195
|
return out + text.slice(cursor);
|
|
97
196
|
}
|
|
98
|
-
function lowerAscii(char) {
|
|
99
|
-
return char.toLowerCase();
|
|
100
|
-
}
|
|
101
197
|
export function stripToolResponseBlocks(text) {
|
|
102
198
|
return stripSpans(text, TOOL_RESPONSE);
|
|
103
199
|
}
|
|
104
|
-
function danglingIndex(text) {
|
|
105
|
-
let cursor = 0;
|
|
106
|
-
for (;;) {
|
|
107
|
-
const start = text.indexOf(FUNCTION_OPEN, cursor);
|
|
108
|
-
if (start === -1) {
|
|
109
|
-
return -1;
|
|
110
|
-
}
|
|
111
|
-
const nameStart = start + FUNCTION_OPEN.length;
|
|
112
|
-
if (nameOf(text, nameStart) !== null) {
|
|
113
|
-
return start;
|
|
114
|
-
}
|
|
115
|
-
cursor = nameStart;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
200
|
export function toolCallMarkup(calls) {
|
|
119
201
|
return calls.map(markupForCall).join('\n');
|
|
120
202
|
}
|
|
@@ -156,24 +238,55 @@ export function normalizeToolArgs(args, declaredKeys) {
|
|
|
156
238
|
}
|
|
157
239
|
return args;
|
|
158
240
|
}
|
|
159
|
-
function decodeArguments(block) {
|
|
241
|
+
function decodeArguments(block, maxNameLength = MAX_NAME_LENGTH) {
|
|
160
242
|
const args = {};
|
|
161
|
-
|
|
243
|
+
let ambiguous = false;
|
|
244
|
+
for (const param of namedMarkupBlocks(block, 'parameter', maxNameLength)) {
|
|
162
245
|
if (!param.name)
|
|
163
246
|
continue;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
247
|
+
ambiguous ||= param.nested;
|
|
248
|
+
defineArgument(args, param.name, param.body);
|
|
249
|
+
}
|
|
250
|
+
return { args: ambiguous ? decodeLegacyArguments(block, maxNameLength) : args, ambiguous };
|
|
251
|
+
}
|
|
252
|
+
function decodeLegacyArguments(block, maxNameLength) {
|
|
253
|
+
const args = {};
|
|
254
|
+
let cursor = 0;
|
|
255
|
+
for (;;) {
|
|
256
|
+
const start = block.indexOf('<parameter=', cursor);
|
|
257
|
+
if (start === -1) {
|
|
258
|
+
return args;
|
|
167
259
|
}
|
|
168
|
-
|
|
169
|
-
|
|
260
|
+
const nameStart = start + '<parameter='.length;
|
|
261
|
+
const nameEnd = block.indexOf('>', nameStart);
|
|
262
|
+
if (nameEnd === -1 || nameEnd - nameStart > maxNameLength) {
|
|
263
|
+
return args;
|
|
264
|
+
}
|
|
265
|
+
const end = block.indexOf('</parameter>', nameEnd + 1);
|
|
266
|
+
if (end === -1) {
|
|
267
|
+
return args;
|
|
268
|
+
}
|
|
269
|
+
const name = block.slice(nameStart, nameEnd).trim();
|
|
270
|
+
if (name) {
|
|
271
|
+
defineArgument(args, name, block.slice(nameEnd + 1, end));
|
|
170
272
|
}
|
|
273
|
+
cursor = end + '</parameter>'.length;
|
|
171
274
|
}
|
|
172
|
-
|
|
275
|
+
}
|
|
276
|
+
function defineArgument(args, name, body) {
|
|
277
|
+
const raw = body.trim();
|
|
278
|
+
let value;
|
|
279
|
+
try {
|
|
280
|
+
value = JSON.parse(raw);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
value = raw;
|
|
284
|
+
}
|
|
285
|
+
Object.defineProperty(args, name, { value, enumerable: true, configurable: true, writable: true });
|
|
173
286
|
}
|
|
174
287
|
export function recoverSkillNamedCall(text, skillNames, skillToolName) {
|
|
175
288
|
const known = new Set(skillNames);
|
|
176
|
-
for (const block of
|
|
289
|
+
for (const block of namedMarkupBlocks(text, 'function', MAX_NAME_LENGTH)) {
|
|
177
290
|
if (!known.has(block.name))
|
|
178
291
|
continue;
|
|
179
292
|
return [
|
|
@@ -190,13 +303,7 @@ export function recoverSkillNamedCall(text, skillNames, skillToolName) {
|
|
|
190
303
|
const SKILL_LOAD_KEYS = new Set(['name', 'skill', 'skill_name', 'file', 'path', 'filename']);
|
|
191
304
|
export function recoverPrintedSkillLoad(text, skillToolName) {
|
|
192
305
|
for (const match of text.matchAll(/\{[^{}]*\}/g)) {
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
candidate = JSON.parse(match[0]);
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
306
|
+
const candidate = parsePrintedCandidate(match[0]);
|
|
200
307
|
if (!isPlainObject(candidate)) {
|
|
201
308
|
continue;
|
|
202
309
|
}
|
|
@@ -223,3 +330,11 @@ export function recoverPrintedSkillLoad(text, skillToolName) {
|
|
|
223
330
|
}
|
|
224
331
|
return [];
|
|
225
332
|
}
|
|
333
|
+
function parsePrintedCandidate(text) {
|
|
334
|
+
try {
|
|
335
|
+
return JSON.parse(text);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -3,6 +3,7 @@ import { isRecord } from '../../guards.js';
|
|
|
3
3
|
import { hlog } from '../../log.js';
|
|
4
4
|
const STRUCTURED_TYPES = new Set(['object', 'array']);
|
|
5
5
|
export class ArgRepairHook {
|
|
6
|
+
name = 'arg-repair';
|
|
6
7
|
phase = 'preToolCall';
|
|
7
8
|
run(ctx) {
|
|
8
9
|
if (ctx.phase !== 'preToolCall' || !ctx.parameters)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mcpTargetUrl } from '../../tools/mcp-call.js';
|
|
2
2
|
import { checkOutboundHost } from '../../tools/url-guard.js';
|
|
3
3
|
export class EgressGuardHook {
|
|
4
|
+
name = 'egress-guard';
|
|
4
5
|
phase = 'preToolCall';
|
|
5
6
|
run(ctx) {
|
|
6
7
|
if (ctx.phase !== 'preToolCall')
|
|
@@ -24,7 +25,7 @@ export class EgressGuardHook {
|
|
|
24
25
|
}
|
|
25
26
|
const blocked = checkOutboundHost(url);
|
|
26
27
|
if (blocked) {
|
|
27
|
-
return { error: blocked, guidance: true };
|
|
28
|
+
return { kind: 'hook-rejection', error: blocked, guidance: true };
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -26,6 +26,7 @@ const NETWORK_BINS = new Set([
|
|
|
26
26
|
'openssl'
|
|
27
27
|
]);
|
|
28
28
|
export class SandboxHook {
|
|
29
|
+
name = 'sandbox';
|
|
29
30
|
phase = 'preToolCall';
|
|
30
31
|
run(ctx) {
|
|
31
32
|
if (ctx.phase !== 'preToolCall')
|
|
@@ -34,7 +35,11 @@ export class SandboxHook {
|
|
|
34
35
|
return;
|
|
35
36
|
const bin = bareBin(firstCommandToken(String(ctx.args.command ?? '')).replace(/^.*[/\\]/, ''));
|
|
36
37
|
if (bin && NETWORK_BINS.has(bin)) {
|
|
37
|
-
return {
|
|
38
|
+
return {
|
|
39
|
+
kind: 'hook-rejection',
|
|
40
|
+
error: `sandbox mode: ${bin} reaches the network`,
|
|
41
|
+
guidance: true
|
|
42
|
+
};
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
}
|
|
@@ -2,6 +2,7 @@ import type { SkillCatalogEntry } from '../../tools/skills/catalog.ts';
|
|
|
2
2
|
import type { HarnessHook, HookContext, HookResult } from '../types.ts';
|
|
3
3
|
type SkillCatalogInput = readonly SkillCatalogEntry[] | (() => readonly SkillCatalogEntry[] | readonly string[]);
|
|
4
4
|
export declare class SkillCatalogGuardHook implements HarnessHook {
|
|
5
|
+
readonly name = "skill-catalog";
|
|
5
6
|
readonly phase: 'preToolCall';
|
|
6
7
|
private readonly _catalog;
|
|
7
8
|
constructor(catalog?: SkillCatalogInput);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export class SkillCatalogGuardHook {
|
|
2
|
+
name = 'skill-catalog';
|
|
2
3
|
phase = 'preToolCall';
|
|
3
4
|
_catalog;
|
|
4
5
|
constructor(catalog = []) {
|
|
@@ -18,6 +19,6 @@ export class SkillCatalogGuardHook {
|
|
|
18
19
|
const rest = listed
|
|
19
20
|
? `The only skills are: ${listed}. Nothing else needs a skill load — answer or act with the tools you already have.`
|
|
20
21
|
: 'No skill is available this turn — answer or act with the tools you already have.';
|
|
21
|
-
return { error: `skill not found: ${name}. ${rest}`, guidance: true };
|
|
22
|
+
return { kind: 'hook-rejection', error: `skill not found: ${name}. ${rest}`, guidance: true };
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ToolExecutionOutcome } from '../tool-call-display.ts';
|
|
2
|
-
import type { HarnessHook, HookRejection, PostToolCallPayload, PreToolCallPayload, RunEndPayload, RunStartPayload } from './types.ts';
|
|
2
|
+
import type { HarnessHook, HookRejection, HookPhase, PostToolCallPayload, PreToolCallPayload, RunEndPayload, RunLifecyclePayload, RunStartPayload } from './types.ts';
|
|
3
3
|
export type PreToolFold = {
|
|
4
4
|
ok: true;
|
|
5
5
|
args: Record<string, unknown>;
|
|
@@ -11,9 +11,12 @@ export interface PostToolFold {
|
|
|
11
11
|
result: unknown;
|
|
12
12
|
outcome?: ToolExecutionOutcome;
|
|
13
13
|
}
|
|
14
|
+
export declare function snapshotHooks(hooks?: readonly HarnessHook[]): readonly HarnessHook[];
|
|
14
15
|
export declare class HookPipeline {
|
|
15
16
|
private readonly _behaviorHooks;
|
|
16
17
|
constructor(hooks?: readonly HarnessHook[]);
|
|
18
|
+
hasPhase(phase: HookPhase): boolean;
|
|
19
|
+
onRunLifecycle(payload: RunLifecyclePayload): Promise<void>;
|
|
17
20
|
onRunStart(payload: Omit<RunStartPayload, 'phase'>): Promise<void>;
|
|
18
21
|
onRunEnd(payload: Omit<RunEndPayload, 'phase'>): Promise<void>;
|
|
19
22
|
runPreToolFold(payload: Omit<PreToolCallPayload, 'phase'>): Promise<PreToolFold>;
|
|
@@ -2,10 +2,28 @@ import { hlog } from '../log.js';
|
|
|
2
2
|
import { normalizeToolExecutionOutcome } from '../helpers/run-outcome.js';
|
|
3
3
|
import { harnessHooks } from './registry.js';
|
|
4
4
|
import { runHooks } from './run.js';
|
|
5
|
+
export function snapshotHooks(hooks = harnessHooks) {
|
|
6
|
+
const phaseCounts = new Map();
|
|
7
|
+
return Object.freeze(hooks.map((hook) => {
|
|
8
|
+
const count = (phaseCounts.get(hook.phase) ?? 0) + 1;
|
|
9
|
+
phaseCounts.set(hook.phase, count);
|
|
10
|
+
const name = hook.name?.trim() || `${hook.phase}:${count}`;
|
|
11
|
+
return Object.freeze({ phase: hook.phase, name, run: hook.run.bind(hook) });
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
5
14
|
export class HookPipeline {
|
|
6
15
|
_behaviorHooks;
|
|
7
16
|
constructor(hooks = harnessHooks) {
|
|
8
|
-
this._behaviorHooks = hooks;
|
|
17
|
+
this._behaviorHooks = snapshotHooks(hooks);
|
|
18
|
+
}
|
|
19
|
+
hasPhase(phase) {
|
|
20
|
+
return this._behaviorHooks.some((hook) => hook.phase === phase);
|
|
21
|
+
}
|
|
22
|
+
async onRunLifecycle(payload) {
|
|
23
|
+
const folded = await runHooks(this._behaviorHooks, payload);
|
|
24
|
+
if ('guidance' in folded) {
|
|
25
|
+
hlog('run hook failed', { stage: payload.phase, error: folded.error });
|
|
26
|
+
}
|
|
9
27
|
}
|
|
10
28
|
async onRunStart(payload) {
|
|
11
29
|
const folded = await runHooks(this._behaviorHooks, { phase: 'runStart', ...payload });
|
|
@@ -22,7 +40,11 @@ export class HookPipeline {
|
|
|
22
40
|
async runPreToolFold(payload) {
|
|
23
41
|
const folded = await runHooks(this._behaviorHooks, { phase: 'preToolCall', ...payload });
|
|
24
42
|
if ('guidance' in folded) {
|
|
25
|
-
hlog('tool rejected: pre-hook refused the call', {
|
|
43
|
+
hlog('tool rejected: pre-hook refused the call', {
|
|
44
|
+
tool: payload.toolName,
|
|
45
|
+
hook: folded.hook,
|
|
46
|
+
phase: folded.phase
|
|
47
|
+
});
|
|
26
48
|
return { ok: false, rejection: folded };
|
|
27
49
|
}
|
|
28
50
|
return { ok: true, args: folded.args };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { HarnessHook, HookRejection, PostToolCallPayload, PreToolCallPayload, RunEndPayload, RunStartPayload } from './types.ts';
|
|
1
|
+
import type { HarnessHook, HookRejection, PostToolCallPayload, PreToolCallPayload, RunEndPayload, RunLifecyclePayload, RunStartPayload } from './types.ts';
|
|
2
2
|
export declare function runHooks(hooks: readonly HarnessHook[], ctx: PreToolCallPayload): Promise<PreToolCallPayload | HookRejection>;
|
|
3
3
|
export declare function runHooks(hooks: readonly HarnessHook[], ctx: PostToolCallPayload): Promise<PostToolCallPayload | HookRejection>;
|
|
4
4
|
export declare function runHooks(hooks: readonly HarnessHook[], ctx: RunStartPayload): Promise<RunStartPayload | HookRejection>;
|
|
5
5
|
export declare function runHooks(hooks: readonly HarnessHook[], ctx: RunEndPayload): Promise<RunEndPayload | HookRejection>;
|
|
6
|
+
export declare function runHooks(hooks: readonly HarnessHook[], ctx: RunLifecyclePayload): Promise<RunLifecyclePayload | HookRejection>;
|
|
@@ -35,8 +35,8 @@ function freshContext(ctx) {
|
|
|
35
35
|
function isRejection(value) {
|
|
36
36
|
return 'error' in value;
|
|
37
37
|
}
|
|
38
|
-
function rejection(value) {
|
|
39
|
-
return { error: String(value.error), guidance: true };
|
|
38
|
+
function rejection(value, hook, phase) {
|
|
39
|
+
return { kind: 'hook-rejection', hook, phase, error: String(value.error), guidance: true };
|
|
40
40
|
}
|
|
41
41
|
function merge(current, returned) {
|
|
42
42
|
if (current.phase === 'preToolCall' && 'args' in returned) {
|
|
@@ -53,34 +53,46 @@ function merge(current, returned) {
|
|
|
53
53
|
}
|
|
54
54
|
return current;
|
|
55
55
|
}
|
|
56
|
-
function rejectionOf(current, err) {
|
|
56
|
+
function rejectionOf(current, err, hook) {
|
|
57
57
|
const message = errorMessage(err);
|
|
58
|
-
hlog('hook failed', { stage: current.phase, error: message });
|
|
58
|
+
hlog('hook failed', { stage: current.phase, hook, error: message });
|
|
59
59
|
if (current.phase === 'postToolCall') {
|
|
60
60
|
return {
|
|
61
|
+
kind: 'hook-rejection',
|
|
62
|
+
hook,
|
|
63
|
+
phase: current.phase,
|
|
61
64
|
error: `tool executed; output withheld: post-hook failed: ${message}. Do not retry this call.`,
|
|
62
65
|
guidance: true
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
kind: 'hook-rejection',
|
|
70
|
+
hook,
|
|
71
|
+
phase: current.phase,
|
|
72
|
+
error: `hook failed in ${current.phase}: ${message}`,
|
|
73
|
+
guidance: true
|
|
74
|
+
};
|
|
66
75
|
}
|
|
67
76
|
export async function runHooks(hooks, ctx) {
|
|
68
77
|
let current = ctx;
|
|
78
|
+
let phaseIndex = 0;
|
|
69
79
|
for (const hook of hooks) {
|
|
70
80
|
if (hook.phase !== current.phase)
|
|
71
81
|
continue;
|
|
82
|
+
phaseIndex++;
|
|
83
|
+
const hookId = hook.name?.trim() || `${current.phase}:${phaseIndex}`;
|
|
72
84
|
try {
|
|
73
85
|
const returned = await hook.run(freshContext(current));
|
|
74
86
|
if (!returned || typeof returned !== 'object') {
|
|
75
87
|
continue;
|
|
76
88
|
}
|
|
77
89
|
if (isRejection(returned)) {
|
|
78
|
-
return rejection(returned);
|
|
90
|
+
return rejection(returned, hookId, current.phase);
|
|
79
91
|
}
|
|
80
92
|
current = merge(current, returned);
|
|
81
93
|
}
|
|
82
94
|
catch (err) {
|
|
83
|
-
return rejectionOf(current, err);
|
|
95
|
+
return rejectionOf(current, err, hookId);
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
98
|
return current;
|