@basein/runner 0.3.21 → 0.3.23

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.
Files changed (50) hide show
  1. package/dist/bin/bir-hooks.d.ts +12 -16
  2. package/dist/bin/bir-hooks.js +12 -16
  3. package/dist/bin/bir-scenario.js +1 -1
  4. package/dist/bin/bir.js +1 -21
  5. package/dist/bin/investigate.d.ts +0 -9
  6. package/dist/bin/investigate.js +14 -42
  7. package/dist/bin/scenario-edit.js +13 -44
  8. package/dist/control/pin-guard.d.ts +16 -0
  9. package/dist/control/pin-guard.js +36 -0
  10. package/dist/control/recorder-main.d.ts +0 -15
  11. package/dist/control/recorder-main.js +7 -39
  12. package/dist/control/server.d.ts +81 -131
  13. package/dist/control/server.js +460 -968
  14. package/dist/record/recorder.d.ts +47 -138
  15. package/dist/record/recorder.js +0 -3
  16. package/dist/record/remote-recorder.d.ts +1 -9
  17. package/dist/record/remote-recorder.js +11 -49
  18. package/dist/record/switchable-recorder.js +0 -2
  19. package/dist/replay/bundle.d.ts +2 -2
  20. package/dist/replay/bundle.js +3 -3
  21. package/dist/replay/controller.d.ts +75 -124
  22. package/dist/replay/controller.js +207 -529
  23. package/dist/replay/derive.d.ts +0 -10
  24. package/dist/replay/derive.js +0 -3
  25. package/dist/replay/handover.d.ts +2 -37
  26. package/dist/replay/handover.js +38 -106
  27. package/dist/replay/logic.d.ts +0 -11
  28. package/dist/replay/logic.js +0 -24
  29. package/dist/replay/plan.d.ts +43 -163
  30. package/dist/replay/plan.js +144 -556
  31. package/dist/replay/safety.d.ts +4 -5
  32. package/dist/replay/safety.js +7 -7
  33. package/dist/replay/source-run.d.ts +15 -24
  34. package/dist/replay/source-run.js +25 -58
  35. package/dist/replay/targets.d.ts +11 -21
  36. package/dist/replay/targets.js +30 -69
  37. package/dist/replay/types.d.ts +9 -96
  38. package/dist/replay/types.js +3 -16
  39. package/dist/util/service-headers.d.ts +1 -2
  40. package/dist/util/service-headers.js +1 -2
  41. package/docs/calculatedReplay.md +17 -74
  42. package/docs/calculatedReplayGuide.md +10 -30
  43. package/package.json +1 -1
  44. package/production.md +5 -35
  45. package/dist/replay/condition-words.d.ts +0 -28
  46. package/dist/replay/condition-words.js +0 -59
  47. package/dist/replay/flatten.d.ts +0 -161
  48. package/dist/replay/flatten.js +0 -222
  49. package/dist/replay/reads-only.d.ts +0 -29
  50. package/dist/replay/reads-only.js +0 -261
@@ -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 { FlatEntry } from "./flatten.js";
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(entries: readonly FlatEntry[], host?: HostSinks): SinkKind | undefined;
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(entries: readonly FlatEntry[], host?: HostSinks): boolean;
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(entries: readonly FlatEntry[]): boolean;
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.
@@ -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(entries, host) {
121
+ export function sinkOfPlan(steps, host) {
122
122
  let found;
123
- for (const e of entries) {
124
- const sink = sinkOf(e.step.toolName, host);
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(entries, host) {
133
- return entries.some((e) => isShellTool(e.step.toolName, host));
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(entries) {
137
- return entries.some((e) => isCommandSink(e.step.toolName));
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 { FlatEntry } from "./flatten.js";
37
+ import type { SerializedScenarioStep } from "./types.js";
44
38
  export interface SourceRunOptions {
45
39
  baseUrl: string;
46
- /** The run the plan's own scenario came from; others are fetched on demand. */
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
- /** `frameId#toolName` → how many of that tool the frame has drawn. */
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 entry's tool, from this entry's own
63
- * recording and range, or undefined when there is none left (or the run could
64
- * not be fetched). Never throws — a missing recorded output means the step is
65
- * skipped, which the caller already handles.
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(entry: FlatEntry): Promise<string | undefined>;
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. So one that was in it passes over its recorded output,
73
- * and the next step of its tool takes its own; an added one passes over
74
- * nothing. Nothing is fetched: the cursor is a count of this frame's calls
75
- * of the tool, whichever of them are read later (docs/calculatedReplay.md
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(entry: FlatEntry): void;
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
- /** `frameId#toolName` → how many of that tool the frame has drawn. */
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 entry's tool, from this entry's own
58
- * recording and range, or undefined when there is none left (or the run could
59
- * not be fetched). Never throws — a missing recorded output means the step is
60
- * skipped, which the caller already handles.
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(entry) {
63
- const runId = entry.runId || this.opts.runId;
64
- const toolName = entry.step.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
- const inRange = all.filter((o) => o.toolName === toolName &&
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 key = cursorKey(entry, toolName);
86
- const at = this.cursor.get(key) ?? 0;
87
- this.cursor.set(key, at + 1);
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. So one that was in it passes over its recorded output,
95
- * and the next step of its tool takes its own; an added one passes over
96
- * nothing. Nothing is fetched: the cursor is a count of this frame's calls
97
- * of the tool, whichever of them are read later (docs/calculatedReplay.md
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(entry) {
101
- const toolName = entry.step.toolName;
102
- if (!toolName || entry.step.correctionKind === "added" || entry.step.correctionKind === "written")
89
+ leftOut(step) {
90
+ const toolName = step.toolName;
91
+ if (!toolName || step.correctionKind === "added" || step.correctionKind === "written")
103
92
  return;
104
- const key = cursorKey(entry, toolName);
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
@@ -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 { FlatEntry } from "./flatten.js";
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 FlatEntry[], paramsLogic?: string | null): string[];
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 of frame 0 that runs under a test of its own (`when`, either
84
- * `otherwise`) reads it in its input logic; and
85
- * - nothing else names it: no step without a test, no output logic, no test,
86
- * no sub-task's parameter mapping or test — the mark is never something a
87
- * test decides on, or that an output passes on to another value; and
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. Nor in a plan
96
- * that calls a sub-task: the sub-task's mappings — the one that builds its
97
- * parameters, the one that hands its results back — run on the caller's
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 FlatEntry[], paramsLogic?: string | null): string[];
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
@@ -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 of frame 0 that runs under a test of its own (`when`, either
111
- * `otherwise`) reads it in its input logic; and
112
- * - nothing else names it: no step without a test, no output logic, no test,
113
- * no sub-task's parameter mapping or test — the mark is never something a
114
- * test decides on, or that an output passes on to another value; and
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. Nor in a plan
123
- * that calls a sub-task: the sub-task's mappings — the one that builds its
124
- * parameters, the one that hands its results back — run on the caller's
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 caller's own `parameters`, as
156
- * {@link callerBodies} lists them, told apart: the input logic of each step of
157
- * frame 0 that runs under a test of its own, and every other body.
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 seenFrames = new Set();
163
- for (const entry of planned) {
164
- if (entry.depth === 0) {
165
- const input = entry.step.toolInputLogic;
166
- if (input)
167
- (isStepTest(entry.step.when) ? tested : others).push(input);
168
- if (entry.step.toolOutputLogic)
169
- others.push(entry.step.toolOutputLogic);
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 caller's own `parameters`.
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. Plus the `paramMapLogic`
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 seenFrames = new Set();
229
- for (const entry of planned) {
230
- if (entry.depth === 0) {
231
- if (entry.step.toolInputLogic)
232
- bodies.push(entry.step.toolInputLogic);
233
- if (entry.step.toolOutputLogic)
234
- bodies.push(entry.step.toolOutputLogic);
235
- // A step's test reads the parameters too (autocorrect.md AC5): a test
236
- // that decides on a target nobody named decides on nothing.
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
  }
@@ -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. On a call
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
- * `"segment"` is a call: a sub-task this chain runs by calling a recorded
105
- * scenario instead of carrying a copy of its steps (R-CALL-1). Absent means
106
- * `"tool"`, which is what every older service sends.
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;