@basein/runner 0.3.15 → 0.3.17
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 +7 -0
- package/dist/bin/bir-scenario.js +8 -5
- package/dist/bin/bir.js +2 -1
- package/dist/bin/investigate.d.ts +71 -0
- package/dist/bin/investigate.js +145 -13
- package/dist/bin/scenario-edit.d.ts +45 -1
- package/dist/bin/scenario-edit.js +134 -14
- package/dist/control/server.d.ts +16 -0
- package/dist/control/server.js +156 -7
- package/dist/control/transcript.d.ts +18 -0
- package/dist/control/transcript.js +55 -0
- package/dist/record/recorder.d.ts +47 -4
- package/dist/record/remote-recorder.d.ts +5 -4
- package/dist/record/remote-recorder.js +23 -1
- package/dist/record/switchable-recorder.d.ts +1 -5
- package/dist/replay/bundle.d.ts +10 -0
- package/dist/replay/bundle.js +23 -2
- package/dist/replay/controller.d.ts +133 -0
- package/dist/replay/controller.js +496 -59
- package/dist/replay/flatten.d.ts +39 -3
- package/dist/replay/flatten.js +42 -2
- package/dist/replay/handover.d.ts +39 -1
- package/dist/replay/handover.js +81 -2
- package/dist/replay/logic.d.ts +8 -0
- package/dist/replay/logic.js +19 -0
- package/dist/replay/plan.d.ts +165 -4
- package/dist/replay/plan.js +488 -25
- package/dist/replay/source-run.d.ts +14 -1
- package/dist/replay/source-run.js +25 -2
- package/dist/replay/targets.js +8 -1
- package/dist/replay/types.d.ts +52 -3
- package/dist/replay/types.js +14 -0
- package/dist/util/service-headers.d.ts +7 -1
- package/dist/util/service-headers.js +7 -1
- package/docs/BaseInstRunner.md +3 -1
- package/docs/calculatedReplay.md +117 -6
- package/docs/calculatedReplayGuide.md +41 -3
- package/package.json +1 -1
package/dist/replay/flatten.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* the service did that long before this payload was built. It only lays out
|
|
23
23
|
* what it was served, and stops in front of anything it cannot run.
|
|
24
24
|
*/
|
|
25
|
-
import type { SerializedScenario, SerializedScenarioStep, CallUnusable } from "./types.js";
|
|
25
|
+
import type { SerializedScenario, SerializedScenarioStep, CallUnusable, StepTest } from "./types.js";
|
|
26
26
|
import type { ParamsSchema } from "./types.js";
|
|
27
27
|
/** The scope one stretch of steps runs in. Frame 0 is the caller's own. */
|
|
28
28
|
export interface Frame {
|
|
@@ -47,6 +47,15 @@ export interface Frame {
|
|
|
47
47
|
chainRevision?: number;
|
|
48
48
|
/** The segment's own parameters, for the settings a mapping omits (R-CALL-21). */
|
|
49
49
|
paramsObject?: ParamsSchema | null;
|
|
50
|
+
/**
|
|
51
|
+
* The call row that opened this frame, when it carries a test (the
|
|
52
|
+
* service's autocorrect.md AC5): the whole sub-task runs only when the test
|
|
53
|
+
* says so. It is evaluated in the *caller's* frame, before this one is
|
|
54
|
+
* entered.
|
|
55
|
+
*/
|
|
56
|
+
when?: StepTest;
|
|
57
|
+
/** That call row itself, for the verdict a sub-task left out gets. */
|
|
58
|
+
callStep?: SerializedScenarioStep;
|
|
50
59
|
/** Filled by the plan when the frame is entered. */
|
|
51
60
|
parameters?: Record<string, unknown>;
|
|
52
61
|
respParams: Record<string, unknown>;
|
|
@@ -63,7 +72,11 @@ export interface FlatEntry {
|
|
|
63
72
|
step: SerializedScenarioStep;
|
|
64
73
|
frame: Frame;
|
|
65
74
|
depth: number;
|
|
66
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* First / last of its frame's *own* entries. Where a frame is entered and
|
|
77
|
+
* left is {@link frameEdges}: a frame whose chain begins or ends with a
|
|
78
|
+
* call begins or ends with that call's steps.
|
|
79
|
+
*/
|
|
67
80
|
first: boolean;
|
|
68
81
|
last: boolean;
|
|
69
82
|
}
|
|
@@ -113,9 +126,32 @@ export declare function flattenChain(scenario: SerializedScenario, opts?: {
|
|
|
113
126
|
* Used for the known-bad-step gate, which judges entries: a parked step in the
|
|
114
127
|
* middle of a sub-task stops the plan in front of the *sub-task*, not in front
|
|
115
128
|
* of the step, because stopping inside it would skip the body that hands its
|
|
116
|
-
* results back.
|
|
129
|
+
* results back. The sub-task starts at its first entry *or its callee's*: one
|
|
130
|
+
* whose chain begins with a call starts where that call's steps do.
|
|
117
131
|
*/
|
|
118
132
|
export declare function clampToFrameStart(entries: readonly FlatEntry[], index: number): number;
|
|
133
|
+
/**
|
|
134
|
+
* Where each called frame begins and ends in the flat list, its callees'
|
|
135
|
+
* entries included — not where its *own* entries do (`first` / `last`).
|
|
136
|
+
*
|
|
137
|
+
* The two differ when a sub-task's chain begins or ends with a call: for
|
|
138
|
+
* F = [call G, f1] the flat order is g0, g1, f1, and F begins at g0. Entering
|
|
139
|
+
* F there, before G, is what gives G's mapping (and G's test) F's parameters;
|
|
140
|
+
* deciding F's own test there is what keeps G from running when F is not
|
|
141
|
+
* needed (the service's autocorrect.md AC5). Leaving F after the last of
|
|
142
|
+
* them is what hands G's results on through F.
|
|
143
|
+
*/
|
|
144
|
+
export interface FrameEdges {
|
|
145
|
+
/** Per frame id, the flat position of its first entry, its callees' included. */
|
|
146
|
+
start: Map<number, number>;
|
|
147
|
+
/** Per frame id, the flat position of its last entry, its callees' included. */
|
|
148
|
+
end: Map<number, number>;
|
|
149
|
+
/** Per flat position, the called frames that begin there, outermost first. */
|
|
150
|
+
startsAt: Map<number, Frame[]>;
|
|
151
|
+
/** Per flat position, the called frames that end there, innermost first. */
|
|
152
|
+
endsAt: Map<number, Frame[]>;
|
|
153
|
+
}
|
|
154
|
+
export declare function frameEdges(entries: readonly FlatEntry[]): FrameEdges;
|
|
119
155
|
/**
|
|
120
156
|
* One frame per chain, for a payload with no calls in it and for the callers
|
|
121
157
|
* that build a plan by hand (tests, `bir replay`). Byte for byte the old
|
package/dist/replay/flatten.js
CHANGED
|
@@ -121,6 +121,10 @@ export function flattenChain(scenario, opts = {}) {
|
|
|
121
121
|
segmentId: segment.id,
|
|
122
122
|
chainRevision: segment.chainRevision,
|
|
123
123
|
paramsObject: segment.paramsObject ?? null,
|
|
124
|
+
// A sub-task may run only when a test says so (AC5): the plan decides
|
|
125
|
+
// the whole frame at its first entry, in the caller's scope.
|
|
126
|
+
...(step.when ? { when: step.when } : {}),
|
|
127
|
+
callStep: step,
|
|
124
128
|
respParams: {},
|
|
125
129
|
};
|
|
126
130
|
frameStart.set(child.id, entries.length);
|
|
@@ -147,6 +151,13 @@ export function flattenChain(scenario, opts = {}) {
|
|
|
147
151
|
});
|
|
148
152
|
return { entries, stopAt, stopReason };
|
|
149
153
|
}
|
|
154
|
+
/** Whether `frame` is `outer` or lies inside it. */
|
|
155
|
+
function within(frame, outer) {
|
|
156
|
+
for (let f = frame; f; f = f.parent)
|
|
157
|
+
if (f.id === outer.id)
|
|
158
|
+
return true;
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
150
161
|
/**
|
|
151
162
|
* Move a stop back to the start of the outermost called frame it falls in
|
|
152
163
|
* (R-CALL-30, 10.6 step 3).
|
|
@@ -154,7 +165,8 @@ export function flattenChain(scenario, opts = {}) {
|
|
|
154
165
|
* Used for the known-bad-step gate, which judges entries: a parked step in the
|
|
155
166
|
* middle of a sub-task stops the plan in front of the *sub-task*, not in front
|
|
156
167
|
* of the step, because stopping inside it would skip the body that hands its
|
|
157
|
-
* results back.
|
|
168
|
+
* results back. The sub-task starts at its first entry *or its callee's*: one
|
|
169
|
+
* whose chain begins with a call starts where that call's steps do.
|
|
158
170
|
*/
|
|
159
171
|
export function clampToFrameStart(entries, index) {
|
|
160
172
|
const at = entries[index];
|
|
@@ -166,11 +178,39 @@ export function clampToFrameStart(entries, index) {
|
|
|
166
178
|
if (!outermost)
|
|
167
179
|
return index;
|
|
168
180
|
for (let i = 0; i < entries.length; i += 1) {
|
|
169
|
-
if (entries[i].frame
|
|
181
|
+
if (within(entries[i].frame, outermost))
|
|
170
182
|
return i;
|
|
171
183
|
}
|
|
172
184
|
return index;
|
|
173
185
|
}
|
|
186
|
+
export function frameEdges(entries) {
|
|
187
|
+
const start = new Map();
|
|
188
|
+
const end = new Map();
|
|
189
|
+
const frames = new Map();
|
|
190
|
+
entries.forEach((entry, index) => {
|
|
191
|
+
for (let f = entry.frame; f; f = f.parent) {
|
|
192
|
+
frames.set(f.id, f);
|
|
193
|
+
if (!start.has(f.id))
|
|
194
|
+
start.set(f.id, index);
|
|
195
|
+
end.set(f.id, index);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
const startsAt = new Map();
|
|
199
|
+
const endsAt = new Map();
|
|
200
|
+
for (const f of frames.values()) {
|
|
201
|
+
if (f.depth === 0)
|
|
202
|
+
continue;
|
|
203
|
+
const s = start.get(f.id);
|
|
204
|
+
const e = end.get(f.id);
|
|
205
|
+
startsAt.set(s, [...(startsAt.get(s) ?? []), f]);
|
|
206
|
+
endsAt.set(e, [...(endsAt.get(e) ?? []), f]);
|
|
207
|
+
}
|
|
208
|
+
for (const list of startsAt.values())
|
|
209
|
+
list.sort((a, b) => a.depth - b.depth);
|
|
210
|
+
for (const list of endsAt.values())
|
|
211
|
+
list.sort((a, b) => b.depth - a.depth);
|
|
212
|
+
return { start, end, startsAt, endsAt };
|
|
213
|
+
}
|
|
174
214
|
/**
|
|
175
215
|
* One frame per chain, for a payload with no calls in it and for the callers
|
|
176
216
|
* that build a plan by hand (tests, `bir replay`). Byte for byte the old
|
|
@@ -57,7 +57,19 @@ export type HandoverStopReason =
|
|
|
57
57
|
* place of the plan's. Its result is real; the plan's later steps were
|
|
58
58
|
* computed for the call it planned, so it stops there.
|
|
59
59
|
*/
|
|
60
|
-
| "host_edited"
|
|
60
|
+
| "host_edited"
|
|
61
|
+
/**
|
|
62
|
+
* The step's test (the service's autocorrect.md AC5) threw, or answered
|
|
63
|
+
* neither true nor false: nothing says whether this request needs the
|
|
64
|
+
* step, and nothing is guessed. The step did not run.
|
|
65
|
+
*/
|
|
66
|
+
| "test_failed"
|
|
67
|
+
/**
|
|
68
|
+
* A sub-task's last step was left out by its test (AC5), and the sub-task
|
|
69
|
+
* could not hand its results back to its caller: the body that maps them
|
|
70
|
+
* threw. Its steps that ran did their work; the one left out did not run.
|
|
71
|
+
*/
|
|
72
|
+
| "result_map";
|
|
61
73
|
export interface HandoverNoteInput {
|
|
62
74
|
intent: string;
|
|
63
75
|
kind: FallbackKind;
|
|
@@ -72,6 +84,25 @@ export interface HandoverNoteInput {
|
|
|
72
84
|
stopReason?: HandoverStopReason;
|
|
73
85
|
/** Steps that ran under the scenario before the stop. */
|
|
74
86
|
executed: number;
|
|
87
|
+
/**
|
|
88
|
+
* When steps before the stop were left out by their tests (AC5): the
|
|
89
|
+
* 1-based numbers of the steps that ran, so the note names them rather than
|
|
90
|
+
* "steps 1–N". `executed` is then their count.
|
|
91
|
+
*/
|
|
92
|
+
ranSteps?: number[];
|
|
93
|
+
/**
|
|
94
|
+
* The steps before the stop that their tests left out, 1-based and
|
|
95
|
+
* inclusive (a sub-task left out whole spans several): named, so the model
|
|
96
|
+
* neither thinks they ran nor runs them.
|
|
97
|
+
*/
|
|
98
|
+
leftOut?: Array<{
|
|
99
|
+
from: number;
|
|
100
|
+
to: number;
|
|
101
|
+
toolName: string;
|
|
102
|
+
text: string | null;
|
|
103
|
+
}>;
|
|
104
|
+
/** For `result_map`: the sub-task whose results could not go back. */
|
|
105
|
+
subTask?: string | null;
|
|
75
106
|
/**
|
|
76
107
|
* Of {@link executed}, how many are the model's own tool calls above — the
|
|
77
108
|
* steps a steered session ran before a divergence took over. Only read when
|
|
@@ -120,6 +151,13 @@ export interface HandoverNoteInput {
|
|
|
120
151
|
ownerScenarioId?: string | null;
|
|
121
152
|
/** The sub-task the stopped step belongs to, when it is inside one. */
|
|
122
153
|
inSubTask?: string | null;
|
|
154
|
+
/**
|
|
155
|
+
* For `other_branch`: the stopped step's test in words — what the steps
|
|
156
|
+
* from there on are for, which this request is not (autocorrect.md AC5).
|
|
157
|
+
*/
|
|
158
|
+
whenText?: string | null;
|
|
123
159
|
}
|
|
124
160
|
export declare function buildHandoverNote(n: HandoverNoteInput): string;
|
|
161
|
+
/** "Step 2", "Steps 1–3", "Steps 1, 3 and 5–6": 1-based numbers in order, runs joined. */
|
|
162
|
+
export declare function stepsPhrase(numbers: readonly number[]): string;
|
|
125
163
|
//# sourceMappingURL=handover.d.ts.map
|
package/dist/replay/handover.js
CHANGED
|
@@ -35,7 +35,28 @@ export function buildHandoverNote(n) {
|
|
|
35
35
|
];
|
|
36
36
|
if (intent)
|
|
37
37
|
lines.push(`Intent: ${intent}`);
|
|
38
|
-
|
|
38
|
+
// Steps left out by their tests sit among those before the stop (AC5): the
|
|
39
|
+
// ones that ran are then named, never "steps 1–N", which would say a step
|
|
40
|
+
// ran that did not.
|
|
41
|
+
const numbered = n.ranSteps && n.ranSteps.length > 0 ? n.ranSteps.slice(0, n.executed) : undefined;
|
|
42
|
+
if (n.executed > 0 && numbered) {
|
|
43
|
+
const above = Math.max(0, Math.min(n.executedAbove ?? 0, n.executed));
|
|
44
|
+
const delivery = above >= n.executed ? "above" : n.delivery;
|
|
45
|
+
const one = numbered.length === 1;
|
|
46
|
+
const ran = `${stepsPhrase(numbered)} of ${n.totalSteps} ran under the scenario`;
|
|
47
|
+
const them = one ? "it" : "them";
|
|
48
|
+
const aboveAre = `${stepsPhrase(numbered.slice(0, above)).toLowerCase()} ${above === 1 ? "is the tool call" : "are the tool calls"} above`;
|
|
49
|
+
lines.push(delivery === "above"
|
|
50
|
+
? `${ran}; ${one ? "its call and result are the tool call" : "their calls and results are the tool calls"} above. Do not repeat ${them}.`
|
|
51
|
+
: above > 0
|
|
52
|
+
? delivery === "none"
|
|
53
|
+
? `${ran}: ${aboveAre}, and the side effects of the rest are in place. Do not repeat ${them}.`
|
|
54
|
+
: `${ran}: ${aboveAre}, and the rest ${n.executed - above === 1 ? "is" : "are"} below. Do not repeat ${them}.`
|
|
55
|
+
: delivery === "none"
|
|
56
|
+
? `${ran}, and ${one ? "its" : "their"} side effects are in place. Do not repeat ${them}.`
|
|
57
|
+
: `${ran}; ${one ? "its call and result are" : "their calls and results are"} below. Do not repeat ${them}.`);
|
|
58
|
+
}
|
|
59
|
+
else if (n.executed > 0) {
|
|
39
60
|
// Part of the work may be the model's own calls above (a steered session
|
|
40
61
|
// that diverged), and the rest the bundle below or side effects only.
|
|
41
62
|
const above = Math.max(0, Math.min(n.executedAbove ?? 0, n.executed));
|
|
@@ -65,6 +86,14 @@ export function buildHandoverNote(n) {
|
|
|
65
86
|
else {
|
|
66
87
|
lines.push("No step of the scenario ran; the whole task is yours.");
|
|
67
88
|
}
|
|
89
|
+
if (n.leftOut && n.leftOut.length > 0) {
|
|
90
|
+
// Named, so the model neither counts them as done nor runs them now.
|
|
91
|
+
const named = n.leftOut.map((l) => `${l.from === l.to ? `step ${l.from}` : `steps ${l.from}–${l.to}`} (${l.toolName})`);
|
|
92
|
+
const texts = [...new Set(n.leftOut.map((l) => l.text).filter((t) => !!t))];
|
|
93
|
+
const why = texts.length === 1 ? ` — only for when ${texts[0]}` : "";
|
|
94
|
+
const one = n.leftOut.length === 1 && n.leftOut[0].from === n.leftOut[0].to;
|
|
95
|
+
lines.push(`Not needed for this request, so not run: ${joinList(named)}${why}. Do not run ${one ? "it" : "them"}.`);
|
|
96
|
+
}
|
|
68
97
|
if (n.inFlight) {
|
|
69
98
|
// Never "ran", never "did not run": the host has not said which.
|
|
70
99
|
const inTool = n.inFlight.toolName ? ` (${n.inFlight.toolName})` : "";
|
|
@@ -93,10 +122,22 @@ export function buildHandoverNote(n) {
|
|
|
93
122
|
// judgement, and the agent is the one to make it (plan-services.md D8).
|
|
94
123
|
lines.push(`Step ${human} of ${n.totalSteps}${tool} did not run: its input needs a judgement the scenario cannot compute. Make that choice yourself and carry on.`);
|
|
95
124
|
}
|
|
125
|
+
else if (n.kind === "other_branch") {
|
|
126
|
+
// The plan holds one half of an "if … otherwise" and this request is the
|
|
127
|
+
// other (autocorrect.md AC5). The steps from here are not wrong, they are
|
|
128
|
+
// not this request's: the model is told what they are for, never to run
|
|
129
|
+
// them, and the list of their tools is left out below for that reason.
|
|
130
|
+
const why = n.whenText ? `they are only for when ${n.whenText}, and that is not so for this request` : "this request is the other case of the task, which they are not for";
|
|
131
|
+
lines.push(n.stepIndex >= n.totalSteps
|
|
132
|
+
? "None of the scenario's steps was needed for this request. Do the task as this request needs it."
|
|
133
|
+
: `Step ${human} of ${n.totalSteps}${tool} and the steps after it did not run: ${why}. Do the rest of the task yourself, as this request needs it.`);
|
|
134
|
+
}
|
|
96
135
|
else {
|
|
97
136
|
lines.push(stepFailedLine(n, human, tool));
|
|
98
137
|
}
|
|
99
|
-
|
|
138
|
+
// Not for the other half: those tools are the ones this request must not
|
|
139
|
+
// run, and naming them invites exactly that.
|
|
140
|
+
if (n.remainingTools.length > 0 && n.kind !== "other_branch") {
|
|
100
141
|
lines.push(`The remaining planned steps were: ${n.remainingTools.join(", ")}.`);
|
|
101
142
|
}
|
|
102
143
|
if (n.params && Object.keys(n.params).length > 0) {
|
|
@@ -111,6 +152,32 @@ export function buildHandoverNote(n) {
|
|
|
111
152
|
}
|
|
112
153
|
return truncate(lines.join("\n"), MAX_HANDOVER_NOTE);
|
|
113
154
|
}
|
|
155
|
+
/** "Step 2", "Steps 1–3", "Steps 1, 3 and 5–6": 1-based numbers in order, runs joined. */
|
|
156
|
+
export function stepsPhrase(numbers) {
|
|
157
|
+
if (numbers.length === 0)
|
|
158
|
+
return "No step";
|
|
159
|
+
if (numbers.length === 1)
|
|
160
|
+
return `Step ${numbers[0]}`;
|
|
161
|
+
const runs = [];
|
|
162
|
+
let from = numbers[0];
|
|
163
|
+
let to = from;
|
|
164
|
+
for (const n of numbers.slice(1)) {
|
|
165
|
+
if (n === to + 1) {
|
|
166
|
+
to = n;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
runs.push(from === to ? `${from}` : `${from}–${to}`);
|
|
170
|
+
from = to = n;
|
|
171
|
+
}
|
|
172
|
+
runs.push(from === to ? `${from}` : `${from}–${to}`);
|
|
173
|
+
return `Steps ${joinList(runs)}`;
|
|
174
|
+
}
|
|
175
|
+
/** "a", "a and b", "a, b and c". */
|
|
176
|
+
function joinList(items) {
|
|
177
|
+
if (items.length <= 1)
|
|
178
|
+
return items.join("");
|
|
179
|
+
return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
|
|
180
|
+
}
|
|
114
181
|
/** The line that names a stopped step and says what may be assumed about it. */
|
|
115
182
|
function stepFailedLine(n, human, tool) {
|
|
116
183
|
const at = `Step ${human} of ${n.totalSteps}${tool}`;
|
|
@@ -135,6 +202,18 @@ function stepFailedLine(n, human, tool) {
|
|
|
135
202
|
`could not read its output${detail ? ` (${detail})` : ""}, so it stops there. Do not run it again.`);
|
|
136
203
|
}
|
|
137
204
|
switch (n.stopReason) {
|
|
205
|
+
case "result_map": {
|
|
206
|
+
// Nothing of the stopped position ran: its sub-task's last step was left
|
|
207
|
+
// out, and what failed is handing the sub-task's results on.
|
|
208
|
+
const what = n.subTask ? ` "${truncate(n.subTask, 120)}"` : "";
|
|
209
|
+
return (`The sub-task${what} could not hand back its results${detail ? ` (${detail})` : ""}, ` +
|
|
210
|
+
"so the scenario stops after it. Do the rest of the task yourself.");
|
|
211
|
+
}
|
|
212
|
+
case "test_failed":
|
|
213
|
+
// The test could not answer, so whether the step is needed is open: the
|
|
214
|
+
// model decides, as it would without the scenario.
|
|
215
|
+
return (`${at} did not run: the scenario could not tell whether this request needs it` +
|
|
216
|
+
`${detail ? ` (${detail})` : ""}. Decide that yourself and carry on.`);
|
|
138
217
|
case "not_run":
|
|
139
218
|
return `${at} did not run${detail ? `: ${detail}` : ""}. Nothing of it happened; do it yourself.`;
|
|
140
219
|
case "uncertain":
|
package/dist/replay/logic.d.ts
CHANGED
|
@@ -74,6 +74,14 @@ export interface LogicRunOptions {
|
|
|
74
74
|
}
|
|
75
75
|
/** Transform the derived parameters once, before any step runs. */
|
|
76
76
|
export declare function evalParamsLogic(code: string, parameters: Record<string, unknown>, intent: string): Record<string, unknown>;
|
|
77
|
+
/**
|
|
78
|
+
* A step's test (the service's autocorrect.md AC5): whether this request needs
|
|
79
|
+
* the step, from the parameters and what the earlier steps accumulated. The
|
|
80
|
+
* answer must be exactly `true` or `false` — a truthy string or a missing key
|
|
81
|
+
* is not an answer, and a test that cannot answer is never guessed at: it
|
|
82
|
+
* throws, and the plan stops in front of the step.
|
|
83
|
+
*/
|
|
84
|
+
export declare function evalWhenLogic(code: string, parameters: Record<string, unknown>, intent: string, respParams?: Record<string, unknown>): boolean;
|
|
77
85
|
/** Derive one step's tool input from params, intent and the accumulated respParams. */
|
|
78
86
|
export declare function evalToolInputLogic(code: string, parameters: Record<string, unknown>, intent: string, respParams?: Record<string, unknown>, opts?: LogicRunOptions): Record<string, unknown>;
|
|
79
87
|
/** Extract keys from a step's real output, to merge into respParams for later steps. */
|
package/dist/replay/logic.js
CHANGED
|
@@ -137,6 +137,25 @@ function assertPlainObject(result, label) {
|
|
|
137
137
|
export function evalParamsLogic(code, parameters, intent) {
|
|
138
138
|
return assertPlainObject(run("paramsLogic", ["parameters", "intent"], code, [parameters, intent]), "paramsLogic");
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* A step's test (the service's autocorrect.md AC5): whether this request needs
|
|
142
|
+
* the step, from the parameters and what the earlier steps accumulated. The
|
|
143
|
+
* answer must be exactly `true` or `false` — a truthy string or a missing key
|
|
144
|
+
* is not an answer, and a test that cannot answer is never guessed at: it
|
|
145
|
+
* throws, and the plan stops in front of the step.
|
|
146
|
+
*/
|
|
147
|
+
export function evalWhenLogic(code, parameters, intent, respParams = {}) {
|
|
148
|
+
const answer = run("whenLogic", ["parameters", "intent", "respParams"], code, [
|
|
149
|
+
parameters,
|
|
150
|
+
intent,
|
|
151
|
+
respParams,
|
|
152
|
+
]);
|
|
153
|
+
if (answer !== true && answer !== false) {
|
|
154
|
+
const shown = answer === undefined ? "undefined" : typeof answer === "string" ? `"${answer.slice(0, 40)}"` : typeof answer;
|
|
155
|
+
throw new Error(`whenLogic must return true or false, and returned ${shown}`);
|
|
156
|
+
}
|
|
157
|
+
return answer;
|
|
158
|
+
}
|
|
140
159
|
/** Derive one step's tool input from params, intent and the accumulated respParams. */
|
|
141
160
|
export function evalToolInputLogic(code, parameters, intent, respParams = {}, opts) {
|
|
142
161
|
return assertPlainObject(run("toolInputLogic", ["parameters", "intent", "respParams"], code, [parameters, intent, respParams], opts), "toolInputLogic");
|
package/dist/replay/plan.d.ts
CHANGED
|
@@ -83,8 +83,14 @@ export interface DriveOptions {
|
|
|
83
83
|
* It takes the *entry*, not the step: a step inside a called segment takes its
|
|
84
84
|
* recorded output from that segment's own recording, within its own range
|
|
85
85
|
* (segmented.md R-CALL-31).
|
|
86
|
+
*
|
|
87
|
+
* `leftOut`, when there, is told of every step a test left out (the service's
|
|
88
|
+
* autocorrect.md AC5): one that was in the recording made its call there, and
|
|
89
|
+
* its output is passed over, so the next step of its tool takes its own.
|
|
86
90
|
*/
|
|
87
|
-
export type RecordedOutputFor = (entry: FlatEntry) => Promise<string | undefined
|
|
91
|
+
export type RecordedOutputFor = ((entry: FlatEntry) => Promise<string | undefined>) & {
|
|
92
|
+
leftOut?: (entry: FlatEntry) => void;
|
|
93
|
+
};
|
|
88
94
|
/**
|
|
89
95
|
* What one step did, told to the caller as it happens.
|
|
90
96
|
*
|
|
@@ -111,7 +117,12 @@ export interface StepInfo {
|
|
|
111
117
|
* call produced none — `skipped`, or a failure before the tool ran.
|
|
112
118
|
*/
|
|
113
119
|
output?: string;
|
|
114
|
-
|
|
120
|
+
/**
|
|
121
|
+
* `not_taken`: the step's test said this request does not need it, and it
|
|
122
|
+
* was left out on purpose (the service's autocorrect.md AC5). No call, no
|
|
123
|
+
* output logic; a sub-task's call row left out whole is one such verdict.
|
|
124
|
+
*/
|
|
125
|
+
outcome: "executed" | "recorded" | "skipped" | "failed" | "not_taken";
|
|
115
126
|
/** Which part of the step broke. Only set for `failed`. */
|
|
116
127
|
stage?: ExecutionStage;
|
|
117
128
|
error?: string;
|
|
@@ -152,8 +163,18 @@ export interface ComposeResult {
|
|
|
152
163
|
* and yet the work did not happen, and an outcome must not say it did.
|
|
153
164
|
*/
|
|
154
165
|
errored: number;
|
|
166
|
+
/** Steps a test left out because this request does not need them (AC5). */
|
|
167
|
+
notTaken: number;
|
|
168
|
+
/** Those steps, in order: what a note counting the steps that ran leaves out. */
|
|
169
|
+
left: LeftOut[];
|
|
155
170
|
/** Where, and why, the run stopped short of its end, when it did. */
|
|
156
171
|
stopped?: PlanStop;
|
|
172
|
+
/**
|
|
173
|
+
* The flat position the walk ended at. At or past the plan's `stepCount`
|
|
174
|
+
* when it reached the end of the planned steps — past it when a test left
|
|
175
|
+
* out a sub-task that reaches beyond a parked step.
|
|
176
|
+
*/
|
|
177
|
+
endedAt: number;
|
|
157
178
|
}
|
|
158
179
|
/** Where, and why, a run stopped short of the chain's end (fallbk.md). */
|
|
159
180
|
export interface PlanStop {
|
|
@@ -170,6 +191,58 @@ export interface PlanStop {
|
|
|
170
191
|
* Absent means its logic threw or its tool reported an error.
|
|
171
192
|
*/
|
|
172
193
|
reason?: HandoverStopReason;
|
|
194
|
+
/** For `other_branch`: the test in words — what the steps from here are for (AC5). */
|
|
195
|
+
whenText?: string | null;
|
|
196
|
+
/**
|
|
197
|
+
* For `result_map`: the sub-task whose results could not go back to its
|
|
198
|
+
* caller, after its last step was left out by its test (AC5).
|
|
199
|
+
*/
|
|
200
|
+
subTask?: string | null;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* What a step's test decided at one flat position (the service's
|
|
204
|
+
* autocorrect.md AC5).
|
|
205
|
+
*
|
|
206
|
+
* - `run`: no test, or its test said yes.
|
|
207
|
+
* - `skip`: this request does not need it — the step, or the whole sub-task
|
|
208
|
+
* whose call row's test said no. The walk goes on at `until`. `leave` says
|
|
209
|
+
* the step left out was the last of an entered sub-task, so the sub-task's
|
|
210
|
+
* results still go back to its caller.
|
|
211
|
+
* - `stop`: the test said `hand_over` (this request is the other half), or
|
|
212
|
+
* could not answer. `info` is the step's verdict when there is one to give.
|
|
213
|
+
*/
|
|
214
|
+
export type Gate = {
|
|
215
|
+
kind: "run";
|
|
216
|
+
} | {
|
|
217
|
+
kind: "skip";
|
|
218
|
+
until: number;
|
|
219
|
+
info: StepInfo;
|
|
220
|
+
entry: FlatEntry;
|
|
221
|
+
text: string | null;
|
|
222
|
+
tools: string[];
|
|
223
|
+
} | {
|
|
224
|
+
kind: "stop";
|
|
225
|
+
stop: PlanStop;
|
|
226
|
+
info?: StepInfo;
|
|
227
|
+
};
|
|
228
|
+
/** A step left out by its test, as the steer note and the bundle name it. */
|
|
229
|
+
export interface LeftOut {
|
|
230
|
+
/** The owning scenario's own `stepIndex` — the caller's for a sub-task. */
|
|
231
|
+
stepIndex: number;
|
|
232
|
+
/** Its flat position. */
|
|
233
|
+
flatIndex: number;
|
|
234
|
+
/**
|
|
235
|
+
* The flat position after it: past every step of a sub-task left out
|
|
236
|
+
* whole, `flatIndex + 1` for one step.
|
|
237
|
+
*/
|
|
238
|
+
until: number;
|
|
239
|
+
toolName: string;
|
|
240
|
+
/**
|
|
241
|
+
* Every tool the steps left out call — the sub-task's, for a sub-task left
|
|
242
|
+
* out whole — so a call the model makes to any of them is known for one.
|
|
243
|
+
*/
|
|
244
|
+
tools: string[];
|
|
245
|
+
text: string | null;
|
|
173
246
|
}
|
|
174
247
|
export interface PlanOptions {
|
|
175
248
|
scenario: SerializedScenario;
|
|
@@ -230,7 +303,22 @@ export declare class ScenarioReplayPlan {
|
|
|
230
303
|
private readonly stopAt;
|
|
231
304
|
/** This plan is a sub-task inside the agent's own task (segmented.md R-OUT-13). */
|
|
232
305
|
readonly subTask: boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Where each called frame begins and ends, its callees' entries included:
|
|
308
|
+
* where it is entered, its test decided and it is left, and where a
|
|
309
|
+
* sub-task its test leaves out ends (AC5).
|
|
310
|
+
*/
|
|
311
|
+
private readonly edges;
|
|
312
|
+
/** Each entry's flat position. */
|
|
313
|
+
private readonly positions;
|
|
314
|
+
/** Every frame of the flat list, by id. */
|
|
315
|
+
private readonly frames;
|
|
316
|
+
/** The plan's own walk: what its cursor has entered and decided (AC5). */
|
|
317
|
+
private readonly walk;
|
|
233
318
|
constructor(opts: PlanOptions);
|
|
319
|
+
private frameById;
|
|
320
|
+
/** The called frames that begin at flat position `flatIndex`, outermost first. */
|
|
321
|
+
private framesStartingAt;
|
|
234
322
|
/** The caller's own frame — the one at depth 0. */
|
|
235
323
|
private rootFrame;
|
|
236
324
|
/** The parameters one entry's logic runs with (segmented.md R-CALL-29). */
|
|
@@ -246,7 +334,27 @@ export declare class ScenarioReplayPlan {
|
|
|
246
334
|
* out. A mapping that omits a setting therefore cannot pass the service's
|
|
247
335
|
* check and then run `undefined` here.
|
|
248
336
|
*/
|
|
337
|
+
private enterFramesAt;
|
|
249
338
|
private enterFrame;
|
|
339
|
+
/**
|
|
340
|
+
* Leave every called frame that ends at `flatIndex` and that `walk`
|
|
341
|
+
* entered, innermost first: a sub-task whose chain ends with a call is left
|
|
342
|
+
* after its callee has handed it its results.
|
|
343
|
+
*/
|
|
344
|
+
private leaveFramesAt;
|
|
345
|
+
/**
|
|
346
|
+
* After a skip that ends at flat position `last`: leave every entered
|
|
347
|
+
* sub-task that ends there — its results still go back to its caller. A
|
|
348
|
+
* result mapping that throws ends the walk: its verdict is filed on the
|
|
349
|
+
* call row, in the caller's chain, and the stop says the step at `last` was
|
|
350
|
+
* left out, not run (`result_map`). Rethrown instead when `rethrow`.
|
|
351
|
+
*/
|
|
352
|
+
private leaveAfterSkip;
|
|
353
|
+
/**
|
|
354
|
+
* The call row that opened `frame`, as a step of its caller's chain: where
|
|
355
|
+
* a sub-task's test and its result mapping are filed (R-CALL-13).
|
|
356
|
+
*/
|
|
357
|
+
private callRowOf;
|
|
250
358
|
/**
|
|
251
359
|
* Leaving a called frame: hand what the segment emitted back to its caller
|
|
252
360
|
* (R-CALL-29). `out` is the frame's own accumulated `respParams`.
|
|
@@ -295,8 +403,6 @@ export declare class ScenarioReplayPlan {
|
|
|
295
403
|
stopStep(): SerializedScenarioStep | undefined;
|
|
296
404
|
/** The entry the plan stops in front of, with the frame that owns it. */
|
|
297
405
|
stopEntry(): FlatEntry | undefined;
|
|
298
|
-
/** Tool names from position `from` to the end of the flat chain. */
|
|
299
|
-
toolsFrom(from: number): string[];
|
|
300
406
|
/** Every entry in order — for the report, and for the owning scenario ids. */
|
|
301
407
|
allEntries(): readonly FlatEntry[];
|
|
302
408
|
/** The entry awaiting execution, or undefined when the plan is done. */
|
|
@@ -333,6 +439,61 @@ export declare class ScenarioReplayPlan {
|
|
|
333
439
|
* a throw as divergence.
|
|
334
440
|
*/
|
|
335
441
|
toolInputForCurrentStep(): Record<string, unknown>;
|
|
442
|
+
/** Whether the plan's cursor rests in front of the step it is parked before. */
|
|
443
|
+
atStop(): boolean;
|
|
444
|
+
/**
|
|
445
|
+
* The test an entry runs under, in words, when it has one: its own, or that
|
|
446
|
+
* of a sub-task beginning at it — one whose chain begins with a call begins
|
|
447
|
+
* at its callee's first step.
|
|
448
|
+
*/
|
|
449
|
+
onlyWhen(entry: FlatEntry): {
|
|
450
|
+
text: string | null;
|
|
451
|
+
} | undefined;
|
|
452
|
+
/**
|
|
453
|
+
* The tool names of the steps from flat position `from` to the end, each
|
|
454
|
+
* that runs only under a test marked so — for a hand-over note's "remaining
|
|
455
|
+
* planned steps", which must not read as steps this request needs.
|
|
456
|
+
*/
|
|
457
|
+
remainingFrom(from: number): string[];
|
|
458
|
+
/** The steps the plan's own cursor has left out so far, in order (AC5). */
|
|
459
|
+
leftOut(): readonly LeftOut[];
|
|
460
|
+
/** Whether any planned step runs only under a test. */
|
|
461
|
+
hasTests(): boolean;
|
|
462
|
+
/** One test, answered — or the reason it could not be. Never throws. */
|
|
463
|
+
private answer;
|
|
464
|
+
/**
|
|
465
|
+
* What the tests decide at flat position `flatIndex` for `walk`. Enters the
|
|
466
|
+
* position's frame when its own test needs the frame's parameters, and
|
|
467
|
+
* nothing else changes: the caller moves its cursor.
|
|
468
|
+
*/
|
|
469
|
+
private gate;
|
|
470
|
+
/**
|
|
471
|
+
* A sub-task's test, decided in its caller's scope at flat position
|
|
472
|
+
* `flatIndex`, where the sub-task begins. Undefined when it says yes.
|
|
473
|
+
*/
|
|
474
|
+
private decideCall;
|
|
475
|
+
/** What a skip the gate decided leaves out, as the notes name it. */
|
|
476
|
+
private leftOutOf;
|
|
477
|
+
/**
|
|
478
|
+
* Tell a recorded-output source of the steps one skip left out — every step
|
|
479
|
+
* of a sub-task left out whole — so its queues pass over the outputs of the
|
|
480
|
+
* ones that were in the recording ({@link RecordedOutputFor}).
|
|
481
|
+
*/
|
|
482
|
+
private passOver;
|
|
483
|
+
/**
|
|
484
|
+
* Move the plan's own cursor past every step this request does not need,
|
|
485
|
+
* deciding each test as its step comes due (AC5). Stops at a step that will
|
|
486
|
+
* run, at the end, or at a test that stops the plan — which it returns, with
|
|
487
|
+
* the steps it left out. The observer is told each verdict as it is made.
|
|
488
|
+
* A sub-task whose results cannot go back after its last step was left out
|
|
489
|
+
* stops the plan too (`result_map`), its verdict filed on the call row.
|
|
490
|
+
*/
|
|
491
|
+
settleTests(onStep?: StepObserver, opts?: {
|
|
492
|
+
rethrow?: boolean;
|
|
493
|
+
}): {
|
|
494
|
+
stop?: PlanStop;
|
|
495
|
+
left: LeftOut[];
|
|
496
|
+
};
|
|
336
497
|
/**
|
|
337
498
|
* Thread a real tool output into `respParams` and advance.
|
|
338
499
|
*
|