@basein/runner 0.3.20 → 0.3.22
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/README.md +6 -5
- package/dist/bin/bir-hooks.d.ts +17 -19
- package/dist/bin/bir-hooks.js +17 -19
- package/dist/bin/bir-scenario.js +1 -1
- package/dist/bin/bir.js +1 -21
- package/dist/bin/investigate.d.ts +0 -9
- package/dist/bin/investigate.js +15 -43
- package/dist/bin/scenario-edit.js +13 -44
- package/dist/control/recorder-main.d.ts +0 -15
- package/dist/control/recorder-main.js +22 -42
- package/dist/control/server.d.ts +24 -137
- package/dist/control/server.js +107 -1001
- package/dist/record/recorder.d.ts +11 -142
- package/dist/record/recorder.js +0 -3
- package/dist/record/remote-recorder.d.ts +1 -9
- package/dist/record/remote-recorder.js +3 -49
- package/dist/record/switchable-recorder.js +0 -2
- package/dist/replay/builtins.d.ts +45 -9
- package/dist/replay/builtins.js +131 -59
- package/dist/replay/bundle.d.ts +2 -2
- package/dist/replay/bundle.js +3 -3
- package/dist/replay/controller.d.ts +34 -125
- package/dist/replay/controller.js +171 -528
- package/dist/replay/derive.d.ts +0 -10
- package/dist/replay/derive.js +0 -3
- package/dist/replay/handover.d.ts +2 -37
- package/dist/replay/handover.js +38 -106
- package/dist/replay/logic.d.ts +0 -11
- package/dist/replay/logic.js +0 -24
- package/dist/replay/plan.d.ts +43 -163
- package/dist/replay/plan.js +144 -556
- package/dist/replay/safety.d.ts +4 -5
- package/dist/replay/safety.js +7 -7
- package/dist/replay/source-run.d.ts +15 -24
- package/dist/replay/source-run.js +25 -58
- package/dist/replay/targets.d.ts +11 -21
- package/dist/replay/targets.js +30 -69
- package/dist/replay/types.d.ts +9 -96
- package/dist/replay/types.js +3 -16
- package/dist/util/service-headers.d.ts +1 -2
- package/dist/util/service-headers.js +1 -2
- package/docs/calculatedReplay.md +30 -81
- package/docs/calculatedReplayGuide.md +17 -34
- package/package.json +1 -1
- package/production.md +7 -36
- package/dist/replay/condition-words.d.ts +0 -28
- package/dist/replay/condition-words.js +0 -59
- package/dist/replay/flatten.d.ts +0 -161
- package/dist/replay/flatten.js +0 -222
- package/dist/replay/reads-only.d.ts +0 -29
- package/dist/replay/reads-only.js +0 -261
package/dist/replay/safety.d.ts
CHANGED
|
@@ -36,8 +36,7 @@
|
|
|
36
36
|
* NUL. A file is not a shell, and a quote or a backslash — every Windows path —
|
|
37
37
|
* is text there.
|
|
38
38
|
*/
|
|
39
|
-
import type {
|
|
40
|
-
import type { ParamsSchema } from "./types.js";
|
|
39
|
+
import type { ParamsSchema, SerializedScenarioStep } from "./types.js";
|
|
41
40
|
/**
|
|
42
41
|
* How a step consumes its input, strictest first: `command` is run by a shell
|
|
43
42
|
* or a database, `write` is only written into a file.
|
|
@@ -61,11 +60,11 @@ export declare function sinkOf(toolName: string | null | undefined, host?: HostS
|
|
|
61
60
|
/** Whether this tool's input is run or written: a shell, a file edit, a query. */
|
|
62
61
|
export declare function isCommandSink(toolName: string | null | undefined): boolean;
|
|
63
62
|
/** The strictest sink among the planned steps, or undefined when there is none. */
|
|
64
|
-
export declare function sinkOfPlan(
|
|
63
|
+
export declare function sinkOfPlan(steps: readonly SerializedScenarioStep[], host?: HostSinks): SinkKind | undefined;
|
|
65
64
|
/** Whether any planned step runs its input through a shell. */
|
|
66
|
-
export declare function hasShellStep(
|
|
65
|
+
export declare function hasShellStep(steps: readonly SerializedScenarioStep[], host?: HostSinks): boolean;
|
|
67
66
|
/** Whether any planned step's input is run or written. */
|
|
68
|
-
export declare function hasCommandSink(
|
|
67
|
+
export declare function hasCommandSink(steps: readonly SerializedScenarioStep[]): boolean;
|
|
69
68
|
/**
|
|
70
69
|
* The first value derived for this turn that is not safe to template into a
|
|
71
70
|
* command, or undefined when every one is.
|
package/dist/replay/safety.js
CHANGED
|
@@ -118,10 +118,10 @@ export function isCommandSink(toolName) {
|
|
|
118
118
|
return sinkOf(toolName) !== undefined;
|
|
119
119
|
}
|
|
120
120
|
/** The strictest sink among the planned steps, or undefined when there is none. */
|
|
121
|
-
export function sinkOfPlan(
|
|
121
|
+
export function sinkOfPlan(steps, host) {
|
|
122
122
|
let found;
|
|
123
|
-
for (const
|
|
124
|
-
const sink = sinkOf(
|
|
123
|
+
for (const s of steps) {
|
|
124
|
+
const sink = sinkOf(s.toolName, host);
|
|
125
125
|
if (sink === "command")
|
|
126
126
|
return sink;
|
|
127
127
|
found ??= sink;
|
|
@@ -129,12 +129,12 @@ export function sinkOfPlan(entries, host) {
|
|
|
129
129
|
return found;
|
|
130
130
|
}
|
|
131
131
|
/** Whether any planned step runs its input through a shell. */
|
|
132
|
-
export function hasShellStep(
|
|
133
|
-
return
|
|
132
|
+
export function hasShellStep(steps, host) {
|
|
133
|
+
return steps.some((s) => isShellTool(s.toolName, host));
|
|
134
134
|
}
|
|
135
135
|
/** Whether any planned step's input is run or written. */
|
|
136
|
-
export function hasCommandSink(
|
|
137
|
-
return
|
|
136
|
+
export function hasCommandSink(steps) {
|
|
137
|
+
return steps.some((s) => isCommandSink(s.toolName));
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
140
|
* The first value derived for this turn that is not safe to template into a
|
|
@@ -24,12 +24,6 @@
|
|
|
24
24
|
* Fetched **lazily** — only when a step actually needs it — and cached for the
|
|
25
25
|
* turn, so a fully-executable replay never makes this call at all.
|
|
26
26
|
*
|
|
27
|
-
* A chain with calls draws on **several** recordings: a called segment's steps
|
|
28
|
-
* take their outputs from that segment's own recording, within its own range
|
|
29
|
-
* (segmented.md R-CALL-31). So the rows are cached per run id, and the cursors
|
|
30
|
-
* are kept per frame — a segment called twice in one chain replays from the
|
|
31
|
-
* start of its range both times, as two separate replays of it would.
|
|
32
|
-
*
|
|
33
27
|
* OFF BY DEFAULT NOW. A recorded output shown to the model as a step's result
|
|
34
28
|
* is last week's answer standing in for a call that did not happen here — and
|
|
35
29
|
* for a write ("INSERT 0 1", "file updated") that is a lie the model repeats to
|
|
@@ -40,10 +34,10 @@
|
|
|
40
34
|
* keeps it on, because its operator reads a per-step trace. Nothing else asks
|
|
41
35
|
* for these rows.
|
|
42
36
|
*/
|
|
43
|
-
import type {
|
|
37
|
+
import type { SerializedScenarioStep } from "./types.js";
|
|
44
38
|
export interface SourceRunOptions {
|
|
45
39
|
baseUrl: string;
|
|
46
|
-
/** The run the plan's
|
|
40
|
+
/** The run the plan's scenario came from. */
|
|
47
41
|
runId: string;
|
|
48
42
|
/** Bearer token; refreshed by the caller, read fresh on each fetch. */
|
|
49
43
|
token: () => string;
|
|
@@ -55,33 +49,30 @@ export declare class SourceRunCache {
|
|
|
55
49
|
/** run id → every recorded output of that run, in order. */
|
|
56
50
|
private readonly runs;
|
|
57
51
|
private readonly loading;
|
|
58
|
-
/**
|
|
52
|
+
/** tool name → how many of that tool's outputs have been drawn. */
|
|
59
53
|
private readonly cursor;
|
|
60
54
|
constructor(opts: SourceRunOptions);
|
|
61
55
|
/**
|
|
62
|
-
* The next recorded output for this
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
56
|
+
* The next recorded output for this step's tool, or undefined when there is
|
|
57
|
+
* none left (or the run could not be fetched). Never throws — a missing
|
|
58
|
+
* recorded output means the step is skipped, which the caller already
|
|
59
|
+
* handles.
|
|
66
60
|
*/
|
|
67
|
-
outputFor(
|
|
61
|
+
outputFor(step: SerializedScenarioStep): Promise<string | undefined>;
|
|
68
62
|
/**
|
|
69
63
|
* A step its test left out (the service's autocorrect.md AC5). Every step
|
|
70
64
|
* of a plan was in the recording, and made its call there — unless a
|
|
71
65
|
* correction added it from the agent's redo (`correctionKind: "added"`)
|
|
72
|
-
* or wrote it from the agent's answer (`"written"`, AC12), which made none.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* §9.2).
|
|
66
|
+
* or wrote it from the agent's answer (`"written"`, AC12), which made none.
|
|
67
|
+
* So one that was in it passes over its recorded output, and the next step
|
|
68
|
+
* of its tool takes its own; an added one passes over nothing. Nothing is
|
|
69
|
+
* fetched: the cursor is a count of the plan's calls of the tool, whichever
|
|
70
|
+
* of them are read later (docs/calculatedReplay.md §9.2).
|
|
77
71
|
*/
|
|
78
|
-
leftOut(
|
|
72
|
+
leftOut(step: SerializedScenarioStep): void;
|
|
79
73
|
private load;
|
|
80
74
|
private fetchRun;
|
|
81
75
|
}
|
|
82
|
-
/**
|
|
83
|
-
* The name this class had before one chain could draw on more than one
|
|
84
|
-
* recording. Kept so nothing outside has to change its import.
|
|
85
|
-
*/
|
|
76
|
+
/** The class's other name, which the controller and older callers import. */
|
|
86
77
|
export { SourceRunCache as SourceRunOutputs };
|
|
87
78
|
//# sourceMappingURL=source-run.d.ts.map
|
|
@@ -24,12 +24,6 @@
|
|
|
24
24
|
* Fetched **lazily** — only when a step actually needs it — and cached for the
|
|
25
25
|
* turn, so a fully-executable replay never makes this call at all.
|
|
26
26
|
*
|
|
27
|
-
* A chain with calls draws on **several** recordings: a called segment's steps
|
|
28
|
-
* take their outputs from that segment's own recording, within its own range
|
|
29
|
-
* (segmented.md R-CALL-31). So the rows are cached per run id, and the cursors
|
|
30
|
-
* are kept per frame — a segment called twice in one chain replays from the
|
|
31
|
-
* start of its range both times, as two separate replays of it would.
|
|
32
|
-
*
|
|
33
27
|
* OFF BY DEFAULT NOW. A recorded output shown to the model as a step's result
|
|
34
28
|
* is last week's answer standing in for a call that did not happen here — and
|
|
35
29
|
* for a write ("INSERT 0 1", "file updated") that is a lie the model repeats to
|
|
@@ -48,20 +42,20 @@ export class SourceRunCache {
|
|
|
48
42
|
/** run id → every recorded output of that run, in order. */
|
|
49
43
|
runs = new Map();
|
|
50
44
|
loading = new Map();
|
|
51
|
-
/**
|
|
45
|
+
/** tool name → how many of that tool's outputs have been drawn. */
|
|
52
46
|
cursor = new Map();
|
|
53
47
|
constructor(opts) {
|
|
54
48
|
this.opts = opts;
|
|
55
49
|
}
|
|
56
50
|
/**
|
|
57
|
-
* The next recorded output for this
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
51
|
+
* The next recorded output for this step's tool, or undefined when there is
|
|
52
|
+
* none left (or the run could not be fetched). Never throws — a missing
|
|
53
|
+
* recorded output means the step is skipped, which the caller already
|
|
54
|
+
* handles.
|
|
61
55
|
*/
|
|
62
|
-
async outputFor(
|
|
63
|
-
const runId =
|
|
64
|
-
const toolName =
|
|
56
|
+
async outputFor(step) {
|
|
57
|
+
const runId = this.opts.runId;
|
|
58
|
+
const toolName = step.toolName;
|
|
65
59
|
if (!toolName)
|
|
66
60
|
return undefined;
|
|
67
61
|
try {
|
|
@@ -75,34 +69,28 @@ export class SourceRunCache {
|
|
|
75
69
|
});
|
|
76
70
|
return undefined;
|
|
77
71
|
}
|
|
78
|
-
const all = this.runs.get(runId) ?? [];
|
|
79
|
-
|
|
80
|
-
(entry.stepFrom === null ||
|
|
81
|
-
entry.stepTo === null ||
|
|
82
|
-
(o.position >= entry.stepFrom && o.position <= entry.stepTo)));
|
|
83
|
-
if (inRange.length === 0)
|
|
72
|
+
const all = (this.runs.get(runId) ?? []).filter((o) => o.toolName === toolName);
|
|
73
|
+
if (all.length === 0)
|
|
84
74
|
return undefined;
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return inRange[at]?.output;
|
|
75
|
+
const at = this.cursor.get(toolName) ?? 0;
|
|
76
|
+
this.cursor.set(toolName, at + 1);
|
|
77
|
+
return all[at]?.output;
|
|
89
78
|
}
|
|
90
79
|
/**
|
|
91
80
|
* A step its test left out (the service's autocorrect.md AC5). Every step
|
|
92
81
|
* of a plan was in the recording, and made its call there — unless a
|
|
93
82
|
* correction added it from the agent's redo (`correctionKind: "added"`)
|
|
94
|
-
* or wrote it from the agent's answer (`"written"`, AC12), which made none.
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* §9.2).
|
|
83
|
+
* or wrote it from the agent's answer (`"written"`, AC12), which made none.
|
|
84
|
+
* So one that was in it passes over its recorded output, and the next step
|
|
85
|
+
* of its tool takes its own; an added one passes over nothing. Nothing is
|
|
86
|
+
* fetched: the cursor is a count of the plan's calls of the tool, whichever
|
|
87
|
+
* of them are read later (docs/calculatedReplay.md §9.2).
|
|
99
88
|
*/
|
|
100
|
-
leftOut(
|
|
101
|
-
const toolName =
|
|
102
|
-
if (!toolName ||
|
|
89
|
+
leftOut(step) {
|
|
90
|
+
const toolName = step.toolName;
|
|
91
|
+
if (!toolName || step.correctionKind === "added" || step.correctionKind === "written")
|
|
103
92
|
return;
|
|
104
|
-
|
|
105
|
-
this.cursor.set(key, (this.cursor.get(key) ?? 0) + 1);
|
|
93
|
+
this.cursor.set(toolName, (this.cursor.get(toolName) ?? 0) + 1);
|
|
106
94
|
}
|
|
107
95
|
load(runId) {
|
|
108
96
|
let pending = this.loading.get(runId);
|
|
@@ -154,24 +142,10 @@ export class SourceRunCache {
|
|
|
154
142
|
}
|
|
155
143
|
serviceOk(startedAt);
|
|
156
144
|
const outputs = [];
|
|
157
|
-
// A `tool_response` belongs to the `tool_selected` before it, and it is
|
|
158
|
-
// *that* row's position the range is cut on: a response row may sit just
|
|
159
|
-
// outside a range whose call sits inside it.
|
|
160
|
-
const lastCall = new Map();
|
|
161
145
|
for (const row of body.steps ?? []) {
|
|
162
|
-
if (!row.tool_name)
|
|
163
|
-
continue;
|
|
164
|
-
if (row.type === "tool_selected") {
|
|
165
|
-
lastCall.set(row.tool_name, row.step_index ?? -1);
|
|
146
|
+
if (!row.tool_name || row.type !== "tool_response")
|
|
166
147
|
continue;
|
|
167
|
-
}
|
|
168
|
-
if (row.type !== "tool_response")
|
|
169
|
-
continue;
|
|
170
|
-
outputs.push({
|
|
171
|
-
toolName: row.tool_name,
|
|
172
|
-
output: row.tool_output ?? "{}",
|
|
173
|
-
position: lastCall.get(row.tool_name) ?? row.step_index ?? -1,
|
|
174
|
-
});
|
|
148
|
+
outputs.push({ toolName: row.tool_name, output: row.tool_output ?? "{}" });
|
|
175
149
|
}
|
|
176
150
|
this.runs.set(runId, outputs);
|
|
177
151
|
logDetail("replay.source_run_loaded", {
|
|
@@ -180,15 +154,8 @@ export class SourceRunCache {
|
|
|
180
154
|
});
|
|
181
155
|
}
|
|
182
156
|
}
|
|
183
|
-
/**
|
|
184
|
-
* The name this class had before one chain could draw on more than one
|
|
185
|
-
* recording. Kept so nothing outside has to change its import.
|
|
186
|
-
*/
|
|
157
|
+
/** The class's other name, which the controller and older callers import. */
|
|
187
158
|
export { SourceRunCache as SourceRunOutputs };
|
|
188
|
-
/** One frame's queue of one tool's recorded outputs: a sub-task called twice replays from its start twice. */
|
|
189
|
-
function cursorKey(entry, toolName) {
|
|
190
|
-
return `${entry.frame.id}#${toolName}`;
|
|
191
|
-
}
|
|
192
159
|
/**
|
|
193
160
|
* Report a service answer to the breaker with the time its request started:
|
|
194
161
|
* an answer to a request sent before a hot path gave up on the service does
|
package/dist/replay/targets.d.ts
CHANGED
|
@@ -35,10 +35,6 @@
|
|
|
35
35
|
* runs on this turn's value, and one that cannot calls a tool with `null` and
|
|
36
36
|
* falls back to the model. What never happens, either way, is last week's fleet.
|
|
37
37
|
*
|
|
38
|
-
* Only frame 0 is judged here. A called segment runs on the parameters its
|
|
39
|
-
* call's `paramMapLogic` builds, not on these, so that body is scanned as a
|
|
40
|
-
* consumer and the segment's own steps are not (R-CALL-29).
|
|
41
|
-
*
|
|
42
38
|
* A THIRD WAY NOT TO BLOCK: A VALUE ONLY ONE HALF NEEDS (the service's review
|
|
43
39
|
* of calcscenarioautocorrect.md, item 11). A plan that holds both halves of an
|
|
44
40
|
* "if … otherwise" reads values only one half needs — the task's name, its
|
|
@@ -62,8 +58,7 @@
|
|
|
62
58
|
* more strictly still, because it runs before any step and can rename a target
|
|
63
59
|
* the steps then read under another name.
|
|
64
60
|
*/
|
|
65
|
-
import type
|
|
66
|
-
import { type ParamsSchema } from "./types.js";
|
|
61
|
+
import { type ParamsSchema, type SerializedScenarioStep } from "./types.js";
|
|
67
62
|
/**
|
|
68
63
|
* The subset of `missing` this plan must decline on.
|
|
69
64
|
*
|
|
@@ -74,17 +69,17 @@ import { type ParamsSchema } from "./types.js";
|
|
|
74
69
|
* `paramsLogic` is the scenario's own, when it has one: it runs on these
|
|
75
70
|
* parameters before step 0, and what it returns is what every step reads.
|
|
76
71
|
*/
|
|
77
|
-
export declare function blockingTargets(missing: readonly string[], schema: ParamsSchema | null | undefined, planned: readonly
|
|
72
|
+
export declare function blockingTargets(missing: readonly string[], schema: ParamsSchema | null | undefined, planned: readonly SerializedScenarioStep[], paramsLogic?: string | null): string[];
|
|
78
73
|
/**
|
|
79
74
|
* Of `missing`, the targets that only the steps of one half read (see A THIRD
|
|
80
75
|
* WAY NOT TO BLOCK above): the plan holds them as a mark, and they do not stop
|
|
81
76
|
* it. A key is one only when
|
|
82
77
|
*
|
|
83
|
-
* - some step
|
|
84
|
-
*
|
|
85
|
-
* - nothing else names it: no step without a test, no output logic, no test
|
|
86
|
-
*
|
|
87
|
-
*
|
|
78
|
+
* - some step that runs under a test of its own (`when`, either `otherwise`)
|
|
79
|
+
* reads it in its input logic; and
|
|
80
|
+
* - nothing else names it: no step without a test, no output logic, no test
|
|
81
|
+
* — the mark is never something a test decides on, or that an output
|
|
82
|
+
* passes on to another value; and
|
|
88
83
|
* - `paramsLogic`, when there is one, does not name the key at all, not even
|
|
89
84
|
* as a bare word (`{ ...parameters, task_name: 'x' }` is a typed default:
|
|
90
85
|
* a guessed target), and uses `parameters` only as a spread that hands
|
|
@@ -92,14 +87,9 @@ export declare function blockingTargets(missing: readonly string[], schema: Para
|
|
|
92
87
|
* - no body carries the recorded sample as a literal.
|
|
93
88
|
*
|
|
94
89
|
* Anything this cannot follow — `parameters` destructured, passed or aliased —
|
|
95
|
-
* and it answers none: every missing target blocks, as before.
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* values out of every watch, and a mark is a truthy text a mapping could
|
|
99
|
-
* branch on, change past recognising, or pass into what a later test decides
|
|
100
|
-
* on. The plan also puts the mark back after `paramsLogic` has run, so a body
|
|
101
|
-
* that dropped the key or wrote one of its own in its place still stops in
|
|
102
|
-
* front of the step.
|
|
90
|
+
* and it answers none: every missing target blocks, as before. The plan also
|
|
91
|
+
* puts the mark back after `paramsLogic` has run, so a body that dropped the
|
|
92
|
+
* key or wrote one of its own in its place still stops in front of the step.
|
|
103
93
|
*/
|
|
104
|
-
export declare function testedOnlyTargets(missing: readonly string[], schema: ParamsSchema | null | undefined, planned: readonly
|
|
94
|
+
export declare function testedOnlyTargets(missing: readonly string[], schema: ParamsSchema | null | undefined, planned: readonly SerializedScenarioStep[], paramsLogic?: string | null): string[];
|
|
105
95
|
//# sourceMappingURL=targets.d.ts.map
|
package/dist/replay/targets.js
CHANGED
|
@@ -35,10 +35,6 @@
|
|
|
35
35
|
* runs on this turn's value, and one that cannot calls a tool with `null` and
|
|
36
36
|
* falls back to the model. What never happens, either way, is last week's fleet.
|
|
37
37
|
*
|
|
38
|
-
* Only frame 0 is judged here. A called segment runs on the parameters its
|
|
39
|
-
* call's `paramMapLogic` builds, not on these, so that body is scanned as a
|
|
40
|
-
* consumer and the segment's own steps are not (R-CALL-29).
|
|
41
|
-
*
|
|
42
38
|
* A THIRD WAY NOT TO BLOCK: A VALUE ONLY ONE HALF NEEDS (the service's review
|
|
43
39
|
* of calcscenarioautocorrect.md, item 11). A plan that holds both halves of an
|
|
44
40
|
* "if … otherwise" reads values only one half needs — the task's name, its
|
|
@@ -107,11 +103,11 @@ export function blockingTargets(missing, schema, planned, paramsLogic) {
|
|
|
107
103
|
* WAY NOT TO BLOCK above): the plan holds them as a mark, and they do not stop
|
|
108
104
|
* it. A key is one only when
|
|
109
105
|
*
|
|
110
|
-
* - some step
|
|
111
|
-
*
|
|
112
|
-
* - nothing else names it: no step without a test, no output logic, no test
|
|
113
|
-
*
|
|
114
|
-
*
|
|
106
|
+
* - some step that runs under a test of its own (`when`, either `otherwise`)
|
|
107
|
+
* reads it in its input logic; and
|
|
108
|
+
* - nothing else names it: no step without a test, no output logic, no test
|
|
109
|
+
* — the mark is never something a test decides on, or that an output
|
|
110
|
+
* passes on to another value; and
|
|
115
111
|
* - `paramsLogic`, when there is one, does not name the key at all, not even
|
|
116
112
|
* as a bare word (`{ ...parameters, task_name: 'x' }` is a typed default:
|
|
117
113
|
* a guessed target), and uses `parameters` only as a spread that hands
|
|
@@ -119,20 +115,13 @@ export function blockingTargets(missing, schema, planned, paramsLogic) {
|
|
|
119
115
|
* - no body carries the recorded sample as a literal.
|
|
120
116
|
*
|
|
121
117
|
* Anything this cannot follow — `parameters` destructured, passed or aliased —
|
|
122
|
-
* and it answers none: every missing target blocks, as before.
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* values out of every watch, and a mark is a truthy text a mapping could
|
|
126
|
-
* branch on, change past recognising, or pass into what a later test decides
|
|
127
|
-
* on. The plan also puts the mark back after `paramsLogic` has run, so a body
|
|
128
|
-
* that dropped the key or wrote one of its own in its place still stops in
|
|
129
|
-
* front of the step.
|
|
118
|
+
* and it answers none: every missing target blocks, as before. The plan also
|
|
119
|
+
* puts the mark back after `paramsLogic` has run, so a body that dropped the
|
|
120
|
+
* key or wrote one of its own in its place still stops in front of the step.
|
|
130
121
|
*/
|
|
131
122
|
export function testedOnlyTargets(missing, schema, planned, paramsLogic) {
|
|
132
123
|
if (missing.length === 0 || !schema || planned.length === 0)
|
|
133
124
|
return [];
|
|
134
|
-
if (planned.some((entry) => entry.depth > 0))
|
|
135
|
-
return [];
|
|
136
125
|
const { tested, others } = consumerBodies(planned);
|
|
137
126
|
const all = [...tested, ...others];
|
|
138
127
|
if (all.length === 0 || all.some(readsWhole))
|
|
@@ -152,33 +141,21 @@ export function testedOnlyTargets(missing, schema, planned, paramsLogic) {
|
|
|
152
141
|
});
|
|
153
142
|
}
|
|
154
143
|
/**
|
|
155
|
-
* The bodies that run with the
|
|
156
|
-
*
|
|
157
|
-
*
|
|
144
|
+
* The bodies that run with the plan's `parameters`, as {@link callerBodies}
|
|
145
|
+
* lists them, told apart: the input logic of each step that runs under a test
|
|
146
|
+
* of its own, and every other body.
|
|
158
147
|
*/
|
|
159
148
|
function consumerBodies(planned) {
|
|
160
149
|
const tested = [];
|
|
161
150
|
const others = [];
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (entry.step.when?.logic)
|
|
171
|
-
others.push(entry.step.when.logic);
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const frame = entry.frame;
|
|
175
|
-
if (frame.depth !== 1 || seenFrames.has(frame.id))
|
|
176
|
-
continue;
|
|
177
|
-
seenFrames.add(frame.id);
|
|
178
|
-
if (frame.paramMapLogic)
|
|
179
|
-
others.push(frame.paramMapLogic);
|
|
180
|
-
if (frame.when?.logic)
|
|
181
|
-
others.push(frame.when.logic);
|
|
151
|
+
for (const step of planned) {
|
|
152
|
+
const input = step.toolInputLogic;
|
|
153
|
+
if (input)
|
|
154
|
+
(isStepTest(step.when) ? tested : others).push(input);
|
|
155
|
+
if (step.toolOutputLogic)
|
|
156
|
+
others.push(step.toolOutputLogic);
|
|
157
|
+
if (step.when?.logic)
|
|
158
|
+
others.push(step.when.logic);
|
|
182
159
|
}
|
|
183
160
|
return { tested, others };
|
|
184
161
|
}
|
|
@@ -216,38 +193,22 @@ function mentions(body, key) {
|
|
|
216
193
|
return pattern.test(body);
|
|
217
194
|
}
|
|
218
195
|
/**
|
|
219
|
-
* Every logic body that runs with the
|
|
196
|
+
* Every logic body that runs with the plan's `parameters`.
|
|
220
197
|
*
|
|
221
198
|
* Both of a step's bodies, because `toolOutputLogic` is handed `parameters`
|
|
222
|
-
* too and a value baked in there travels just as far.
|
|
223
|
-
* of each segment called directly from frame 0: it reads the caller's
|
|
224
|
-
* parameters to build the segment's.
|
|
199
|
+
* too and a value baked in there travels just as far.
|
|
225
200
|
*/
|
|
226
201
|
function callerBodies(planned) {
|
|
227
202
|
const bodies = [];
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (entry.step.when?.logic)
|
|
238
|
-
bodies.push(entry.step.when.logic);
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
// The frame's own mapping, once, and only when its caller is frame 0 —
|
|
242
|
-
// and the call row's test, which is decided in the caller's scope.
|
|
243
|
-
const frame = entry.frame;
|
|
244
|
-
if (frame.depth !== 1 || seenFrames.has(frame.id))
|
|
245
|
-
continue;
|
|
246
|
-
seenFrames.add(frame.id);
|
|
247
|
-
if (frame.paramMapLogic)
|
|
248
|
-
bodies.push(frame.paramMapLogic);
|
|
249
|
-
if (frame.when?.logic)
|
|
250
|
-
bodies.push(frame.when.logic);
|
|
203
|
+
for (const step of planned) {
|
|
204
|
+
if (step.toolInputLogic)
|
|
205
|
+
bodies.push(step.toolInputLogic);
|
|
206
|
+
if (step.toolOutputLogic)
|
|
207
|
+
bodies.push(step.toolOutputLogic);
|
|
208
|
+
// A step's test reads the parameters too (autocorrect.md AC5): a test
|
|
209
|
+
// that decides on a target nobody named decides on nothing.
|
|
210
|
+
if (step.when?.logic)
|
|
211
|
+
bodies.push(step.when.logic);
|
|
251
212
|
}
|
|
252
213
|
return bodies;
|
|
253
214
|
}
|
package/dist/replay/types.d.ts
CHANGED
|
@@ -10,33 +10,6 @@
|
|
|
10
10
|
* everywhere and nothing is assumed present. A scenario whose `state` is not
|
|
11
11
|
* `"ready"` carries an **empty** `steps` array, by the service's own rule.
|
|
12
12
|
*/
|
|
13
|
-
/** One step: the tool, and the JS logic that recomputes its arguments. */
|
|
14
|
-
/**
|
|
15
|
-
* A segment inlined into the chain that calls it (segmented.md R-CALL-28).
|
|
16
|
-
*
|
|
17
|
-
* The caller's chain holds one *call* row where a stretch of steps used to be,
|
|
18
|
-
* and the service serves that segment's own chain inside it. Nothing is copied
|
|
19
|
-
* into the caller's scenario: this is one payload, built at serving time from
|
|
20
|
-
* the segment as it stands right now.
|
|
21
|
-
*/
|
|
22
|
-
export interface InlinedSegment {
|
|
23
|
-
id: string;
|
|
24
|
-
intent: string;
|
|
25
|
-
paramsObject: ParamsSchema | null;
|
|
26
|
-
runId: string;
|
|
27
|
-
stepFrom: number;
|
|
28
|
-
stepTo: number;
|
|
29
|
-
/**
|
|
30
|
-
* The segment's `chain_revision` as served. Sent back on every verdict about
|
|
31
|
-
* one of its steps, so a failure counted against a chain that has since been
|
|
32
|
-
* rebuilt is dropped (R-CALL-13, R-REUSE-5).
|
|
33
|
-
*/
|
|
34
|
-
chainRevision: number;
|
|
35
|
-
/** May itself contain call rows, to the depth limit. */
|
|
36
|
-
steps: SerializedScenarioStep[];
|
|
37
|
-
}
|
|
38
|
-
/** Why a call cannot run right now (segmented.md 9.9). */
|
|
39
|
-
export type CallUnusable = "switched_off" | "not_ready" | "missing" | "stale" | "unresolved" | "depth" | "unsupported";
|
|
40
13
|
/**
|
|
41
14
|
* A reason the plan stops in front of a step, as the service decided it
|
|
42
15
|
* (plan-services.md D8). The service knows the grace period, the retry clock
|
|
@@ -47,18 +20,9 @@ export interface StepStop {
|
|
|
47
20
|
* `parked`: the step has failed too often and its retry is not due, or its
|
|
48
21
|
* grace has ended. `nondeterministic`: the calculation could not write code
|
|
49
22
|
* that computes this step's input — a judgement — so the agent makes it.
|
|
50
|
-
* `decision`: the task has an "if … otherwise" here (the service's
|
|
51
|
-
* segmentsbreakdown.md SB-6). The plan runs only the steps before it and
|
|
52
|
-
* hands the task back, and the agent decides which way applies. Not a
|
|
53
|
-
* failure: it counts nothing against the step. Runners up to 0.3.18 read it
|
|
54
|
-
* as an unknown kind and stop here too, as `known_bad_step`.
|
|
55
|
-
*/
|
|
56
|
-
kind: "parked" | "nondeterministic" | "decision";
|
|
57
|
-
/**
|
|
58
|
-
* For `nondeterministic`: which check the step failed at calculation. For
|
|
59
|
-
* `decision`: the condition in words. It holds the recorded run's values,
|
|
60
|
-
* so it is never shown to the agent (it would hint at the recorded half).
|
|
61
23
|
*/
|
|
24
|
+
kind: "parked" | "nondeterministic";
|
|
25
|
+
/** For `nondeterministic`: which check the step failed at calculation. */
|
|
62
26
|
why?: string;
|
|
63
27
|
/** For `parked` inside its grace: when the step may be tried again. */
|
|
64
28
|
retryAt?: string;
|
|
@@ -82,12 +46,12 @@ export interface StepTest {
|
|
|
82
46
|
}
|
|
83
47
|
/** Whether a served test is one this runner can run; anything else is no plan to arm. */
|
|
84
48
|
export declare function isStepTest(value: unknown): value is StepTest;
|
|
49
|
+
/** One step: the tool, and the JS logic that recomputes its arguments. */
|
|
85
50
|
export interface SerializedScenarioStep {
|
|
86
51
|
stepIndex: number;
|
|
87
52
|
/**
|
|
88
53
|
* Run this step only when its test says so (autocorrect.md AC5). Absent or
|
|
89
|
-
* null on a step with no test — every step of an older service.
|
|
90
|
-
* row the test decides the whole sub-task.
|
|
54
|
+
* null on a step with no test — every step of an older service.
|
|
91
55
|
*/
|
|
92
56
|
when?: StepTest | null;
|
|
93
57
|
/**
|
|
@@ -100,16 +64,10 @@ export interface SerializedScenarioStep {
|
|
|
100
64
|
*/
|
|
101
65
|
correctionId?: string | null;
|
|
102
66
|
correctionKind?: "added" | "tested" | "changed" | "written" | null;
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*/
|
|
108
|
-
kind?: "tool" | "segment";
|
|
109
|
-
/** As the recorder wrote it — `mcp__<server>__<tool>` for an MCP call. Null on a call row. */
|
|
110
|
-
toolName: string | null;
|
|
111
|
-
/** JS body: `(parameters, intent, respParams) => object`. Null on a call row. */
|
|
112
|
-
toolInputLogic: string | null;
|
|
67
|
+
/** As the recorder wrote it — `mcp__<server>__<tool>` for an MCP call. */
|
|
68
|
+
toolName: string;
|
|
69
|
+
/** JS body: `(parameters, intent, respParams) => object`. */
|
|
70
|
+
toolInputLogic: string;
|
|
113
71
|
/** JS body: `(toolOutput, parameters, intent, respParams) => object`. */
|
|
114
72
|
toolOutputLogic: string | null;
|
|
115
73
|
/** The model's reasoning for this step, used in the steering directive. */
|
|
@@ -139,30 +97,6 @@ export interface SerializedScenarioStep {
|
|
|
139
97
|
* {@link ../replay/source-run.ts} is what actually supplies recorded outputs.
|
|
140
98
|
*/
|
|
141
99
|
recordedOutput?: string | null;
|
|
142
|
-
/** Which segment answers this call right now, if one does. */
|
|
143
|
-
segmentId?: string | null;
|
|
144
|
-
/** What the sub-task is, in one line. The ask, frozen with the chain. */
|
|
145
|
-
callIntent?: string | null;
|
|
146
|
-
/** How many of the caller's recorded steps the call covers. */
|
|
147
|
-
callStepCount?: number | null;
|
|
148
|
-
/** JS body: `(parameters, intent, respParams) => <the segment's parameters>`. */
|
|
149
|
-
paramMapLogic?: string | null;
|
|
150
|
-
/** JS body: `(out, parameters, intent, respParams) => <keys the caller reads>`. */
|
|
151
|
-
resultMapLogic?: string | null;
|
|
152
|
-
/** The segment, inlined. Absent when the call is `unusable`. */
|
|
153
|
-
segment?: InlinedSegment | null;
|
|
154
|
-
/**
|
|
155
|
-
* Set when the call cannot run: the plan stops in front of it and the agent
|
|
156
|
-
* does that stretch itself (R-CALL-29, R-FALL-5).
|
|
157
|
-
*/
|
|
158
|
-
unusable?: CallUnusable;
|
|
159
|
-
/** How the answer was reached. Read only for the `plan.armed` line (R-CALL-32). */
|
|
160
|
-
resolution?: {
|
|
161
|
-
state: string;
|
|
162
|
-
score?: number;
|
|
163
|
-
verdict?: string;
|
|
164
|
-
resolvedAt?: string;
|
|
165
|
-
} | null;
|
|
166
100
|
}
|
|
167
101
|
/**
|
|
168
102
|
* What a parameter *is* (segmented.md R-PARAM-1). A `target` is what the task
|
|
@@ -244,12 +178,6 @@ export type FallbackKind =
|
|
|
244
178
|
"step_failed"
|
|
245
179
|
/** The plan stopped in front of a step that has failed too often, without trying it. */
|
|
246
180
|
| "known_bad_step"
|
|
247
|
-
/**
|
|
248
|
-
* The plan stopped in front of a *call* that cannot run — its segment is
|
|
249
|
-
* gone, switched off, out of date, or nested too deep (segmented.md
|
|
250
|
-
* R-CALL-29). Like a known-bad step it counts nothing against any step.
|
|
251
|
-
*/
|
|
252
|
-
| "unusable_call"
|
|
253
181
|
/**
|
|
254
182
|
* The plan stopped in front of a step the calculation marked
|
|
255
183
|
* non-deterministic: its input needs a judgement no code computes
|
|
@@ -261,14 +189,7 @@ export type FallbackKind =
|
|
|
261
189
|
* (autocorrect.md AC5): this request is the other half of an "if …
|
|
262
190
|
* otherwise", and the agent does it. No fault of the plan; counts nothing.
|
|
263
191
|
*/
|
|
264
|
-
| "other_branch"
|
|
265
|
-
/**
|
|
266
|
-
* The plan ran the steps before its decision and stopped in front of it
|
|
267
|
-
* (the service's segmentsbreakdown.md SB-17): the agent decides which way
|
|
268
|
-
* applies and does the rest. No fault of the plan; it counts nothing, and
|
|
269
|
-
* the plan is billed only up to the stop (SB-26).
|
|
270
|
-
*/
|
|
271
|
-
| "decision";
|
|
192
|
+
| "other_branch";
|
|
272
193
|
/**
|
|
273
194
|
* Upgrade order. `not_steered` is the floor — the control group, and the safe
|
|
274
195
|
* answer if a turn dies mid-flight — and each later decision point can only move
|
|
@@ -344,14 +265,6 @@ export interface ExecutionStepResult {
|
|
|
344
265
|
* waiting out the grace period.
|
|
345
266
|
*/
|
|
346
267
|
toolMissing?: boolean;
|
|
347
|
-
/**
|
|
348
|
-
* The scenario this step belongs to, when it ran inside a called segment: a
|
|
349
|
-
* failure is counted where a repair would happen (segmented.md R-CALL-13).
|
|
350
|
-
* Absent on a step of the reported scenario's own chain.
|
|
351
|
-
*/
|
|
352
|
-
scenarioId?: string;
|
|
353
|
-
/** That segment's `chain_revision` as served. Absent at depth 0. */
|
|
354
|
-
chainRevision?: number;
|
|
355
268
|
}
|
|
356
269
|
/** Narrowing guard for a payload that is worth arming a plan from. */
|
|
357
270
|
export declare function isReadyScenario(value: unknown): value is SerializedScenario;
|