@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.
- package/dist/bin/bir-hooks.d.ts +12 -16
- package/dist/bin/bir-hooks.js +12 -16
- 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 +14 -42
- package/dist/bin/scenario-edit.js +13 -44
- package/dist/control/pin-guard.d.ts +16 -0
- package/dist/control/pin-guard.js +36 -0
- package/dist/control/recorder-main.d.ts +0 -15
- package/dist/control/recorder-main.js +7 -39
- package/dist/control/server.d.ts +81 -131
- package/dist/control/server.js +460 -968
- package/dist/record/recorder.d.ts +47 -138
- package/dist/record/recorder.js +0 -3
- package/dist/record/remote-recorder.d.ts +1 -9
- package/dist/record/remote-recorder.js +11 -49
- package/dist/record/switchable-recorder.js +0 -2
- package/dist/replay/bundle.d.ts +2 -2
- package/dist/replay/bundle.js +3 -3
- package/dist/replay/controller.d.ts +75 -124
- package/dist/replay/controller.js +207 -529
- 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 +17 -74
- package/docs/calculatedReplayGuide.md +10 -30
- package/package.json +1 -1
- package/production.md +5 -35
- 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
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* condition-words — the request's own words for its decision (the service's
|
|
3
|
-
* segmentsbreakdown.md SB-18).
|
|
4
|
-
*
|
|
5
|
-
* When a plan stops at its decision, the hand-over note quotes the sentences
|
|
6
|
-
* of the live request that hold a word for a condition ("if … otherwise"), so
|
|
7
|
-
* the agent sees which choice is its own to make. Only the request's words:
|
|
8
|
-
* never the condition the service keeps, which is written from the recorded
|
|
9
|
-
* run and would hint at the half the recording took.
|
|
10
|
-
*
|
|
11
|
-
* The word lists are the service's (its src/scenarios/condition.ts), copied
|
|
12
|
-
* exactly. Keep the two the same: the service decides which requests may hold
|
|
13
|
-
* a condition with them, and this quotes the sentences they find.
|
|
14
|
-
*/
|
|
15
|
-
/** Words that say a part of a task depends on something: English, and the Hebrew the service reads. */
|
|
16
|
-
const CONDITION_WORDS = /\b(if|otherwise|else|unless|only when|only if|in case|whether)\b/i;
|
|
17
|
-
const HEBREW_CONDITION = /(^|[\s,.;:!?"'()\-])(אם|אחרת|אלא אם|במקרה ש|רק אם|רק כאשר)($|[\s,.;:!?"'()\-])/;
|
|
18
|
-
/** The longest quote a decision note carries, in characters. */
|
|
19
|
-
export const MAX_DECISION_QUOTE = 600;
|
|
20
|
-
/** Whether a text has a word for a condition in it: the service's `mayHoldCondition`. */
|
|
21
|
-
export function mayHoldCondition(text) {
|
|
22
|
-
return CONDITION_WORDS.test(text) || HEBREW_CONDITION.test(text);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Where one sentence ends and the next begins: after `.`, `!`, `?` or `…` and
|
|
26
|
-
* white space, in front of a capital letter, a letter of a script with no
|
|
27
|
-
* capitals (Hebrew), a quote mark or a bracket. Never in front of a digit or a
|
|
28
|
-
* small letter, so "Nov. 20" and "e.g. the" stay in one sentence.
|
|
29
|
-
*/
|
|
30
|
-
const SENTENCE_BREAK = /(?<=[.!?…])\s+(?=[\p{Lu}\p{Lo}"'“‘(\[])/u;
|
|
31
|
-
/**
|
|
32
|
-
* The sentences of `request` that hold a word for a condition, in order,
|
|
33
|
-
* each on one line of single spaces, joined by a space. Cut to at most `max`
|
|
34
|
-
* characters, ending in "…" when cut. Null when no sentence holds one.
|
|
35
|
-
*
|
|
36
|
-
* Lines are sentences of their own: the request is split at line breaks
|
|
37
|
-
* first, then at {@link SENTENCE_BREAK}.
|
|
38
|
-
*/
|
|
39
|
-
export function conditionSentences(request, max = MAX_DECISION_QUOTE) {
|
|
40
|
-
if (typeof request !== "string" || request.trim() === "")
|
|
41
|
-
return null;
|
|
42
|
-
const sentences = request
|
|
43
|
-
.replace(/\r\n?/g, "\n")
|
|
44
|
-
.split("\n")
|
|
45
|
-
.flatMap((line) => line.split(SENTENCE_BREAK))
|
|
46
|
-
.map((s) => s.replace(/\s+/g, " ").trim())
|
|
47
|
-
.filter((s) => s !== "" && mayHoldCondition(s));
|
|
48
|
-
if (sentences.length === 0)
|
|
49
|
-
return null;
|
|
50
|
-
const quote = sentences.join(" ");
|
|
51
|
-
if (quote.length <= max)
|
|
52
|
-
return quote;
|
|
53
|
-
let cut = quote.slice(0, Math.max(0, max - 1));
|
|
54
|
-
// Never half of a character written as two code units (an emoji).
|
|
55
|
-
if (/[\uD800-\uDBFF]$/.test(cut))
|
|
56
|
-
cut = cut.slice(0, -1);
|
|
57
|
-
return `${cut.trimEnd()}…`;
|
|
58
|
-
}
|
|
59
|
-
//# sourceMappingURL=condition-words.js.map
|
package/dist/replay/flatten.d.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flattening calls into frames (segmented.md 10.6).
|
|
3
|
-
*
|
|
4
|
-
* A chain may hold a **call**: one row that says "here the agent does this
|
|
5
|
-
* sub-task", answered by a recorded scenario the service inlines into the
|
|
6
|
-
* payload. Running it is not a second plan. It is one plan over one flat list
|
|
7
|
-
* of steps, in which some steps belong to another scenario and run with *its*
|
|
8
|
-
* parameters, *its* intent and *its* accumulated values.
|
|
9
|
-
*
|
|
10
|
-
* That is what a **frame** is: the scope one stretch of steps runs in. Frame 0
|
|
11
|
-
* is the caller's own. Entering a called frame evaluates the call's
|
|
12
|
-
* `paramMapLogic` to build the segment's parameters; leaving it evaluates
|
|
13
|
-
* `resultMapLogic` and merges the result back into the caller's `respParams`
|
|
14
|
-
* (R-CALL-29).
|
|
15
|
-
*
|
|
16
|
-
* Everything downstream reads the flat list rather than the chain: the gates
|
|
17
|
-
* judge the steps that will really run (R-CALL-30), recorded outputs come from
|
|
18
|
-
* the right recording and range (R-CALL-31), and a failure is reported on the
|
|
19
|
-
* scenario that owns the step (R-CALL-13).
|
|
20
|
-
*
|
|
21
|
-
* The flattener itself decides nothing about *which* segment answers a call —
|
|
22
|
-
* the service did that long before this payload was built. It only lays out
|
|
23
|
-
* what it was served, and stops in front of anything it cannot run.
|
|
24
|
-
*/
|
|
25
|
-
import type { SerializedScenario, SerializedScenarioStep, CallUnusable, StepTest } from "./types.js";
|
|
26
|
-
import type { ParamsSchema } from "./types.js";
|
|
27
|
-
/** The scope one stretch of steps runs in. Frame 0 is the caller's own. */
|
|
28
|
-
export interface Frame {
|
|
29
|
-
id: number;
|
|
30
|
-
/** 0 for the caller, 1 for a segment it calls, 2 for one that segment calls. */
|
|
31
|
-
depth: number;
|
|
32
|
-
scenarioId: string;
|
|
33
|
-
runId: string;
|
|
34
|
-
/** The recorded range the frame's outputs come from. Null means the whole run. */
|
|
35
|
-
stepFrom: number | null;
|
|
36
|
-
stepTo: number | null;
|
|
37
|
-
/** The intent the frame's step logic runs with (R-INTENT-12). */
|
|
38
|
-
intent: string;
|
|
39
|
-
parent?: Frame;
|
|
40
|
-
/** The caller's chain `stepIndex` of the call row that opened this frame. */
|
|
41
|
-
callerStepIndex?: number;
|
|
42
|
-
callIntent?: string;
|
|
43
|
-
paramMapLogic?: string;
|
|
44
|
-
resultMapLogic?: string;
|
|
45
|
-
segmentId?: string;
|
|
46
|
-
/** The inlined segment's `chainRevision` as served (R-CALL-13). */
|
|
47
|
-
chainRevision?: number;
|
|
48
|
-
/** The segment's own parameters, for the settings a mapping omits (R-CALL-21). */
|
|
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;
|
|
59
|
-
/** Filled by the plan when the frame is entered. */
|
|
60
|
-
parameters?: Record<string, unknown>;
|
|
61
|
-
respParams: Record<string, unknown>;
|
|
62
|
-
}
|
|
63
|
-
/** One step that will really run, with the frame it runs in. */
|
|
64
|
-
export interface FlatEntry {
|
|
65
|
-
scenarioId: string;
|
|
66
|
-
runId: string;
|
|
67
|
-
stepFrom: number | null;
|
|
68
|
-
stepTo: number | null;
|
|
69
|
-
/** The owning scenario's own `scenario_steps.step_index`, not a flat position. */
|
|
70
|
-
stepIndex: number;
|
|
71
|
-
/** Always a tool row: a call row becomes a frame, or a stop. */
|
|
72
|
-
step: SerializedScenarioStep;
|
|
73
|
-
frame: Frame;
|
|
74
|
-
depth: number;
|
|
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
|
-
*/
|
|
80
|
-
first: boolean;
|
|
81
|
-
last: boolean;
|
|
82
|
-
}
|
|
83
|
-
/** Why the flat list stops short, and which call row said so. */
|
|
84
|
-
export interface FlatStop {
|
|
85
|
-
kind: "unusable";
|
|
86
|
-
reason: CallUnusable;
|
|
87
|
-
/** The scenario whose chain holds the call row. */
|
|
88
|
-
scenarioId: string;
|
|
89
|
-
/** That call row's own `stepIndex`. */
|
|
90
|
-
stepIndex: number;
|
|
91
|
-
segmentId?: string | null;
|
|
92
|
-
callIntent?: string | null;
|
|
93
|
-
}
|
|
94
|
-
export interface FlatChain {
|
|
95
|
-
entries: FlatEntry[];
|
|
96
|
-
/** Flat position the plan stops in front of. Undefined means the whole chain. */
|
|
97
|
-
stopAt?: number;
|
|
98
|
-
stopReason?: FlatStop;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Mirrors the service's `SEGMENT_MAX_DEPTH`. A constant, not a setting: one
|
|
102
|
-
* call row holds one answer, which is only correct because a call inside a
|
|
103
|
-
* level-two segment is never answered (R-CALL-12).
|
|
104
|
-
*/
|
|
105
|
-
export declare const MAX_CALL_DEPTH = 2;
|
|
106
|
-
/**
|
|
107
|
-
* Lay a served chain out as one flat list of steps, with a frame per call.
|
|
108
|
-
*
|
|
109
|
-
* Stops at the first call it cannot run. The stop is *clamped* to the start of
|
|
110
|
-
* the outermost called frame that contains it: a plan must never run half a
|
|
111
|
-
* sub-task and leave its `resultMapLogic` unevaluated, because the caller's
|
|
112
|
-
* later steps read what that body returns (R-CALL-30).
|
|
113
|
-
*/
|
|
114
|
-
export declare function flattenChain(scenario: SerializedScenario, opts?: {
|
|
115
|
-
maxDepth?: number;
|
|
116
|
-
/** The range a segment armed on its own covers (its own recording). */
|
|
117
|
-
range?: {
|
|
118
|
-
stepFrom: number;
|
|
119
|
-
stepTo: number;
|
|
120
|
-
} | null;
|
|
121
|
-
}): FlatChain;
|
|
122
|
-
/**
|
|
123
|
-
* Move a stop back to the start of the outermost called frame it falls in
|
|
124
|
-
* (R-CALL-30, 10.6 step 3).
|
|
125
|
-
*
|
|
126
|
-
* Used for the known-bad-step gate, which judges entries: a parked step in the
|
|
127
|
-
* middle of a sub-task stops the plan in front of the *sub-task*, not in front
|
|
128
|
-
* of the step, because stopping inside it would skip the body that hands its
|
|
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.
|
|
131
|
-
*/
|
|
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;
|
|
155
|
-
/**
|
|
156
|
-
* One frame per chain, for a payload with no calls in it and for the callers
|
|
157
|
-
* that build a plan by hand (tests, `bir replay`). Byte for byte the old
|
|
158
|
-
* behaviour: one frame, one scenario, one range.
|
|
159
|
-
*/
|
|
160
|
-
export declare function flatEntriesOf(scenario: SerializedScenario): FlatEntry[];
|
|
161
|
-
//# sourceMappingURL=flatten.d.ts.map
|
package/dist/replay/flatten.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flattening calls into frames (segmented.md 10.6).
|
|
3
|
-
*
|
|
4
|
-
* A chain may hold a **call**: one row that says "here the agent does this
|
|
5
|
-
* sub-task", answered by a recorded scenario the service inlines into the
|
|
6
|
-
* payload. Running it is not a second plan. It is one plan over one flat list
|
|
7
|
-
* of steps, in which some steps belong to another scenario and run with *its*
|
|
8
|
-
* parameters, *its* intent and *its* accumulated values.
|
|
9
|
-
*
|
|
10
|
-
* That is what a **frame** is: the scope one stretch of steps runs in. Frame 0
|
|
11
|
-
* is the caller's own. Entering a called frame evaluates the call's
|
|
12
|
-
* `paramMapLogic` to build the segment's parameters; leaving it evaluates
|
|
13
|
-
* `resultMapLogic` and merges the result back into the caller's `respParams`
|
|
14
|
-
* (R-CALL-29).
|
|
15
|
-
*
|
|
16
|
-
* Everything downstream reads the flat list rather than the chain: the gates
|
|
17
|
-
* judge the steps that will really run (R-CALL-30), recorded outputs come from
|
|
18
|
-
* the right recording and range (R-CALL-31), and a failure is reported on the
|
|
19
|
-
* scenario that owns the step (R-CALL-13).
|
|
20
|
-
*
|
|
21
|
-
* The flattener itself decides nothing about *which* segment answers a call —
|
|
22
|
-
* the service did that long before this payload was built. It only lays out
|
|
23
|
-
* what it was served, and stops in front of anything it cannot run.
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* Mirrors the service's `SEGMENT_MAX_DEPTH`. A constant, not a setting: one
|
|
27
|
-
* call row holds one answer, which is only correct because a call inside a
|
|
28
|
-
* level-two segment is never answered (R-CALL-12).
|
|
29
|
-
*/
|
|
30
|
-
export const MAX_CALL_DEPTH = 2;
|
|
31
|
-
/**
|
|
32
|
-
* Lay a served chain out as one flat list of steps, with a frame per call.
|
|
33
|
-
*
|
|
34
|
-
* Stops at the first call it cannot run. The stop is *clamped* to the start of
|
|
35
|
-
* the outermost called frame that contains it: a plan must never run half a
|
|
36
|
-
* sub-task and leave its `resultMapLogic` unevaluated, because the caller's
|
|
37
|
-
* later steps read what that body returns (R-CALL-30).
|
|
38
|
-
*/
|
|
39
|
-
export function flattenChain(scenario, opts = {}) {
|
|
40
|
-
const maxDepth = opts.maxDepth ?? MAX_CALL_DEPTH;
|
|
41
|
-
const entries = [];
|
|
42
|
-
let frameId = 0;
|
|
43
|
-
let stopAt;
|
|
44
|
-
let stopReason;
|
|
45
|
-
const root = {
|
|
46
|
-
id: frameId++,
|
|
47
|
-
depth: 0,
|
|
48
|
-
scenarioId: scenario.id,
|
|
49
|
-
runId: scenario.runId,
|
|
50
|
-
stepFrom: opts.range?.stepFrom ?? null,
|
|
51
|
-
stepTo: opts.range?.stepTo ?? null,
|
|
52
|
-
intent: scenario.intent ?? "",
|
|
53
|
-
respParams: {},
|
|
54
|
-
};
|
|
55
|
-
/** The entry a frame's steps start at, for the clamp. */
|
|
56
|
-
const frameStart = new Map();
|
|
57
|
-
const walk = (steps, frame, stack) => {
|
|
58
|
-
for (const step of steps) {
|
|
59
|
-
if (stopAt !== undefined)
|
|
60
|
-
return;
|
|
61
|
-
if (step.kind !== "segment") {
|
|
62
|
-
entries.push({
|
|
63
|
-
scenarioId: frame.scenarioId,
|
|
64
|
-
runId: frame.runId,
|
|
65
|
-
stepFrom: frame.stepFrom,
|
|
66
|
-
stepTo: frame.stepTo,
|
|
67
|
-
stepIndex: step.stepIndex,
|
|
68
|
-
step,
|
|
69
|
-
frame,
|
|
70
|
-
depth: frame.depth,
|
|
71
|
-
first: false,
|
|
72
|
-
last: false,
|
|
73
|
-
});
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
// A call the service already marked unrunnable, a third level, or a
|
|
77
|
-
// segment already on this stack (which would be a cycle): all the same
|
|
78
|
-
// thing to a plan — it stops here and the agent takes over (R-CALL-29).
|
|
79
|
-
const cycles = !!step.segment && stack.includes(step.segment.id);
|
|
80
|
-
const tooDeep = frame.depth + 1 > maxDepth;
|
|
81
|
-
const reason = step.unusable
|
|
82
|
-
? step.unusable
|
|
83
|
-
: !step.segment || cycles || tooDeep
|
|
84
|
-
? "depth"
|
|
85
|
-
: undefined;
|
|
86
|
-
if (reason) {
|
|
87
|
-
// The clamp: a stop inside a called frame moves back to the first entry
|
|
88
|
-
// of the outermost called frame containing it, so the caller never runs
|
|
89
|
-
// part of a sub-task (R-CALL-30).
|
|
90
|
-
let outermost = frame;
|
|
91
|
-
while (outermost && outermost.depth > 1)
|
|
92
|
-
outermost = outermost.parent;
|
|
93
|
-
stopAt =
|
|
94
|
-
frame.depth > 0 && outermost
|
|
95
|
-
? (frameStart.get(outermost.id) ?? entries.length)
|
|
96
|
-
: entries.length;
|
|
97
|
-
stopReason = {
|
|
98
|
-
kind: "unusable",
|
|
99
|
-
reason,
|
|
100
|
-
scenarioId: frame.scenarioId,
|
|
101
|
-
stepIndex: step.stepIndex,
|
|
102
|
-
segmentId: step.segmentId ?? step.segment?.id ?? null,
|
|
103
|
-
callIntent: step.callIntent ?? null,
|
|
104
|
-
};
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const segment = step.segment;
|
|
108
|
-
const child = {
|
|
109
|
-
id: frameId++,
|
|
110
|
-
depth: frame.depth + 1,
|
|
111
|
-
scenarioId: segment.id,
|
|
112
|
-
runId: segment.runId,
|
|
113
|
-
stepFrom: segment.stepFrom,
|
|
114
|
-
stepTo: segment.stepTo,
|
|
115
|
-
intent: segment.intent ?? "",
|
|
116
|
-
parent: frame,
|
|
117
|
-
callerStepIndex: step.stepIndex,
|
|
118
|
-
callIntent: step.callIntent ?? undefined,
|
|
119
|
-
paramMapLogic: step.paramMapLogic ?? undefined,
|
|
120
|
-
resultMapLogic: step.resultMapLogic ?? undefined,
|
|
121
|
-
segmentId: segment.id,
|
|
122
|
-
chainRevision: segment.chainRevision,
|
|
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,
|
|
128
|
-
respParams: {},
|
|
129
|
-
};
|
|
130
|
-
frameStart.set(child.id, entries.length);
|
|
131
|
-
walk(segment.steps ?? [], child, [...stack, segment.id]);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
frameStart.set(root.id, 0);
|
|
135
|
-
walk(scenario.steps ?? [], root, [scenario.id]);
|
|
136
|
-
// `first` / `last` per frame, marked once the whole list is known: they are
|
|
137
|
-
// where the two mapping bodies run. A frame's entries are not necessarily
|
|
138
|
-
// contiguous — a call sitting in the middle of a chain interleaves its
|
|
139
|
-
// callee's entries with its own — so these are the frame's first and last
|
|
140
|
-
// entries overall, never the edges of a run of them.
|
|
141
|
-
const firstOf = new Map();
|
|
142
|
-
const lastOf = new Map();
|
|
143
|
-
entries.forEach((entry, index) => {
|
|
144
|
-
if (!firstOf.has(entry.frame.id))
|
|
145
|
-
firstOf.set(entry.frame.id, index);
|
|
146
|
-
lastOf.set(entry.frame.id, index);
|
|
147
|
-
});
|
|
148
|
-
entries.forEach((entry, index) => {
|
|
149
|
-
entry.first = firstOf.get(entry.frame.id) === index;
|
|
150
|
-
entry.last = lastOf.get(entry.frame.id) === index;
|
|
151
|
-
});
|
|
152
|
-
return { entries, stopAt, stopReason };
|
|
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
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Move a stop back to the start of the outermost called frame it falls in
|
|
163
|
-
* (R-CALL-30, 10.6 step 3).
|
|
164
|
-
*
|
|
165
|
-
* Used for the known-bad-step gate, which judges entries: a parked step in the
|
|
166
|
-
* middle of a sub-task stops the plan in front of the *sub-task*, not in front
|
|
167
|
-
* of the step, because stopping inside it would skip the body that hands its
|
|
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.
|
|
170
|
-
*/
|
|
171
|
-
export function clampToFrameStart(entries, index) {
|
|
172
|
-
const at = entries[index];
|
|
173
|
-
if (!at || at.depth === 0)
|
|
174
|
-
return index;
|
|
175
|
-
let outermost = at.frame;
|
|
176
|
-
while (outermost && outermost.depth > 1)
|
|
177
|
-
outermost = outermost.parent;
|
|
178
|
-
if (!outermost)
|
|
179
|
-
return index;
|
|
180
|
-
for (let i = 0; i < entries.length; i += 1) {
|
|
181
|
-
if (within(entries[i].frame, outermost))
|
|
182
|
-
return i;
|
|
183
|
-
}
|
|
184
|
-
return index;
|
|
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
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* One frame per chain, for a payload with no calls in it and for the callers
|
|
216
|
-
* that build a plan by hand (tests, `bir replay`). Byte for byte the old
|
|
217
|
-
* behaviour: one frame, one scenario, one range.
|
|
218
|
-
*/
|
|
219
|
-
export function flatEntriesOf(scenario) {
|
|
220
|
-
return flattenChain(scenario).entries;
|
|
221
|
-
}
|
|
222
|
-
//# sourceMappingURL=flatten.js.map
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Whether a tool call may change something, judged by its name alone — the
|
|
3
|
-
* runner's copy of the service's own rule (BaseIn src/scenarios/correctionCheck.ts,
|
|
4
|
-
* `readsOnly`, `commandReadsOnly`, `callMayChange`), kept word for word so the
|
|
5
|
-
* two never disagree about the same call.
|
|
6
|
-
*
|
|
7
|
-
* Used where a call after a plan's decision is the agent starting a way of its
|
|
8
|
-
* own (the service's offer1.md O7), and where a plan started after a call
|
|
9
|
-
* would make it a second time (O8). When in doubt, a call is a change: that
|
|
10
|
-
* only ends a set of scenarios early or keeps a plan from starting, never lets
|
|
11
|
-
* a second write through.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* A tool that only reads: Claude Code's read-only tools, then the name — a
|
|
15
|
-
* reading word and no writing word (`calendar_search_events`, `getJiraIssue`;
|
|
16
|
-
* `run_sql` no: `run` writes). A name with both (`get_or_create_contact`)
|
|
17
|
-
* changes something.
|
|
18
|
-
*/
|
|
19
|
-
export declare function readsOnly(toolName: string): boolean;
|
|
20
|
-
/** Whether a shell command only looks: every part of it is a looking command, and nothing is written to a file. */
|
|
21
|
-
export declare function commandReadsOnly(command: string): boolean;
|
|
22
|
-
/**
|
|
23
|
-
* Whether one call may change something. A Bash call changes nothing only
|
|
24
|
-
* when its command is there and only looks; any other tool is a change unless
|
|
25
|
-
* {@link readsOnly} says it only reads — a tool it cannot tell about counts as
|
|
26
|
-
* a change. `input` is the call's arguments, as an object or as JSON text.
|
|
27
|
-
*/
|
|
28
|
-
export declare function callMayChange(toolName: string, input: unknown): boolean;
|
|
29
|
-
//# sourceMappingURL=reads-only.d.ts.map
|