@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
|
@@ -397,6 +397,61 @@ function foldLine(idx, line) {
|
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* The model's text after the message that made the tool call `toolUseId`: its
|
|
402
|
+
* answer, once that call's result came back (the service's autocorrect.md
|
|
403
|
+
* AC11). Empty while none has been written, and when the call is not in the
|
|
404
|
+
* transcript — never an older text, which `lastAssistantText` can be: the
|
|
405
|
+
* sentence before a call, or the turn before's answer.
|
|
406
|
+
*/
|
|
407
|
+
export function textAfterToolUse(transcriptPath, toolUseId) {
|
|
408
|
+
if (!transcriptPath || !toolUseId)
|
|
409
|
+
return "";
|
|
410
|
+
const idx = indexFor(transcriptPath);
|
|
411
|
+
const at = idx?.toolUses.get(toolUseId)?.message;
|
|
412
|
+
if (!idx || at === undefined)
|
|
413
|
+
return "";
|
|
414
|
+
let after = false;
|
|
415
|
+
let text = "";
|
|
416
|
+
for (const [key, entry] of idx.messages) {
|
|
417
|
+
if (key === at) {
|
|
418
|
+
after = true;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (after && entry.texts.length > 0)
|
|
422
|
+
text = entry.texts.join("");
|
|
423
|
+
}
|
|
424
|
+
return text.trim();
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* {@link textAfterToolUse}, waited for: up to `timeoutMs` for a text to appear,
|
|
428
|
+
* then until it stops changing for `confirmMs`. Empty when none came in time —
|
|
429
|
+
* a plan's report then goes without an answer, rather than with a wrong one.
|
|
430
|
+
*/
|
|
431
|
+
export async function settledTextAfterToolUse(transcriptPath, toolUseId, opts = {}) {
|
|
432
|
+
if (!transcriptPath || !toolUseId)
|
|
433
|
+
return "";
|
|
434
|
+
const pollMs = opts.pollMs ?? 150;
|
|
435
|
+
const confirmMs = opts.confirmMs ?? 400;
|
|
436
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 2_000);
|
|
437
|
+
const sleep = () => new Promise((resolve) => {
|
|
438
|
+
const t = setTimeout(resolve, pollMs);
|
|
439
|
+
t.unref?.();
|
|
440
|
+
});
|
|
441
|
+
let latest = textAfterToolUse(transcriptPath, toolUseId);
|
|
442
|
+
let since = Date.now();
|
|
443
|
+
while (Date.now() < deadline) {
|
|
444
|
+
if (latest && Date.now() - since >= confirmMs)
|
|
445
|
+
return latest;
|
|
446
|
+
await sleep();
|
|
447
|
+
const now = textAfterToolUse(transcriptPath, toolUseId);
|
|
448
|
+
if (now !== latest) {
|
|
449
|
+
latest = now;
|
|
450
|
+
since = Date.now();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return latest;
|
|
454
|
+
}
|
|
400
455
|
/** Forget every cached index (tests; a caller that knows files were replaced). */
|
|
401
456
|
export function forgetTranscripts() {
|
|
402
457
|
indexes.clear();
|
|
@@ -61,6 +61,27 @@ export interface FlushOptions {
|
|
|
61
61
|
*/
|
|
62
62
|
timeoutMs?: number;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* A plan's own call recorded into the turn's newest run (the service's
|
|
66
|
+
* autocorrect.md AC2): which plan made it, which of its steps, and the chain
|
|
67
|
+
* revision it was served at — left out when the service served none, since
|
|
68
|
+
* its schema takes a number or nothing, never null.
|
|
69
|
+
*/
|
|
70
|
+
export interface PlanCallMark {
|
|
71
|
+
scenarioId: string;
|
|
72
|
+
stepIndex: number;
|
|
73
|
+
chainRevision?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A recorded step's metadata. `pinnedBy`: a plan executed this step live in
|
|
77
|
+
* the turn (R-INTENT-7). `byPlan`: the plan made this call before the agent
|
|
78
|
+
* redid the task, and it is recorded first into the agent's repeat so the
|
|
79
|
+
* repeat holds the whole turn (AC2). The service stores neither with a vector.
|
|
80
|
+
*/
|
|
81
|
+
export type StepMetadata = {
|
|
82
|
+
pinnedBy?: string;
|
|
83
|
+
byPlan?: PlanCallMark;
|
|
84
|
+
};
|
|
64
85
|
export interface Recorder {
|
|
65
86
|
/**
|
|
66
87
|
* Create the run; returns the client-generated runId synchronously.
|
|
@@ -86,17 +107,18 @@ export interface Recorder {
|
|
|
86
107
|
};
|
|
87
108
|
/**
|
|
88
109
|
* Set when a plan executed this step, to the plan's scenario id
|
|
89
|
-
* (R-INTENT-7)
|
|
110
|
+
* (R-INTENT-7), or made it before the agent redid the task (`byPlan`,
|
|
111
|
+
* AC2). The service stores such a step without a vector, so the
|
|
90
112
|
* system's own replays never count as recurrences of the work.
|
|
91
113
|
*/
|
|
92
|
-
metadata?:
|
|
93
|
-
pinnedBy: string;
|
|
94
|
-
};
|
|
114
|
+
metadata?: StepMetadata;
|
|
95
115
|
}): string;
|
|
96
116
|
recordToolResponse(runId: string, stepIndex: number, d: {
|
|
97
117
|
toolName: string;
|
|
98
118
|
toolOutput?: string;
|
|
99
119
|
toolError?: string;
|
|
120
|
+
/** `byPlan` on the answer of a plan's own call (AC2); omitted otherwise. */
|
|
121
|
+
metadata?: StepMetadata;
|
|
100
122
|
}): string;
|
|
101
123
|
recordFinalAnswer(runId: string, stepIndex: number, d: {
|
|
102
124
|
answer: string;
|
|
@@ -417,6 +439,27 @@ export interface ExecutionReport {
|
|
|
417
439
|
* person reading the audit log, this is for the console.
|
|
418
440
|
*/
|
|
419
441
|
declined?: string;
|
|
442
|
+
/**
|
|
443
|
+
* The plan ran to its end and the agent did not take its result: it went on
|
|
444
|
+
* with calls of its own, recorded as the recording's newest run with the
|
|
445
|
+
* reason `redone` (the service's newestRun.md N7, autocorrect.md AC1).
|
|
446
|
+
* `runId` is that repeat's own id; `ownCalls` the agent's own tool calls
|
|
447
|
+
* recorded into it — not the plan's calls recorded first (AC2), and not a
|
|
448
|
+
* step a plan pinned. Only on the report of the prompt's plan, and only when
|
|
449
|
+
* the service took the repeat; an older service drops the field.
|
|
450
|
+
*/
|
|
451
|
+
redoneBy?: {
|
|
452
|
+
runId: string;
|
|
453
|
+
ownCalls: number;
|
|
454
|
+
};
|
|
455
|
+
/**
|
|
456
|
+
* The model's final answer, when the prompt's plan ran to its end and no
|
|
457
|
+
* redo of the model's own was recorded (the service's autocorrect.md AC11):
|
|
458
|
+
* the service checks whether the model took the plan's result, and holds and
|
|
459
|
+
* corrects the plan when it did not. Read from the transcript after the turn
|
|
460
|
+
* ends; an older service drops the field.
|
|
461
|
+
*/
|
|
462
|
+
answer?: string;
|
|
420
463
|
}
|
|
421
464
|
/** Optional capability: reporting needs a service, and a NullRecorder has none. */
|
|
422
465
|
export interface ScenarioReporter {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
*/
|
|
51
51
|
import { type AuthSession } from "../auth/client.js";
|
|
52
52
|
import { type ServiceHealth } from "../util/service-health.js";
|
|
53
|
-
import type { ExecutionReport, FlushOptions, IntentMatchAnswer, IntentMatchOptions, IntentMatchRequest, Recorder, RecorderHealth, RecorderHealthReporter, RunCreation, RunMatch, RunMetrics, StartRunOptions } from "./recorder.js";
|
|
53
|
+
import type { ExecutionReport, FlushOptions, IntentMatchAnswer, IntentMatchOptions, IntentMatchRequest, Recorder, RecorderHealth, RecorderHealthReporter, RunCreation, RunMatch, RunMetrics, StartRunOptions, StepMetadata } from "./recorder.js";
|
|
54
54
|
/**
|
|
55
55
|
* Caps for the free text this recorder sends, in UTF-8 bytes. Tool payloads
|
|
56
56
|
* arrive capped at 64 KiB by their producers; text never passed through one,
|
|
@@ -70,6 +70,8 @@ export declare const TEXT_CAPS: {
|
|
|
70
70
|
readonly toolError: number;
|
|
71
71
|
/** An execution report's error lines, rendered in a web page. */
|
|
72
72
|
readonly reportError: number;
|
|
73
|
+
/** The model's answer on a plan's report (AC11): the service keeps 4,000 characters of it. */
|
|
74
|
+
readonly reportAnswer: number;
|
|
73
75
|
};
|
|
74
76
|
/**
|
|
75
77
|
* The most an execution report's request may weigh. The service reads this
|
|
@@ -225,14 +227,13 @@ export declare class RemoteRecorder implements Recorder, RecorderHealthReporter
|
|
|
225
227
|
text: string;
|
|
226
228
|
source: "thinking";
|
|
227
229
|
};
|
|
228
|
-
metadata?:
|
|
229
|
-
pinnedBy: string;
|
|
230
|
-
};
|
|
230
|
+
metadata?: StepMetadata;
|
|
231
231
|
}): string;
|
|
232
232
|
recordToolResponse(runId: string, stepIndex: number, data: {
|
|
233
233
|
toolName: string;
|
|
234
234
|
toolOutput?: string;
|
|
235
235
|
toolError?: string;
|
|
236
|
+
metadata?: StepMetadata;
|
|
236
237
|
}): string;
|
|
237
238
|
recordFinalAnswer(runId: string, stepIndex: number, data: {
|
|
238
239
|
answer: string;
|
|
@@ -75,6 +75,8 @@ export const TEXT_CAPS = {
|
|
|
75
75
|
toolError: 8 * 1024,
|
|
76
76
|
/** An execution report's error lines, rendered in a web page. */
|
|
77
77
|
reportError: 2 * 1024,
|
|
78
|
+
/** The model's answer on a plan's report (AC11): the service keeps 4,000 characters of it. */
|
|
79
|
+
reportAnswer: 8 * 1024,
|
|
78
80
|
};
|
|
79
81
|
/**
|
|
80
82
|
* The most an execution report's request may weigh. The service reads this
|
|
@@ -674,6 +676,10 @@ export class RemoteRecorder {
|
|
|
674
676
|
toolName: data.toolName,
|
|
675
677
|
toolOutput: out(toolOutput),
|
|
676
678
|
toolError: out(toolError),
|
|
679
|
+
// Omitted, not nulled, when absent: an ordinary answer is the body it
|
|
680
|
+
// has always been. Only a plan's own call recorded into a redo carries
|
|
681
|
+
// one (`byPlan`, the service's autocorrect.md AC2).
|
|
682
|
+
metadata: data.metadata,
|
|
677
683
|
}));
|
|
678
684
|
return id;
|
|
679
685
|
}
|
|
@@ -738,12 +744,19 @@ export class RemoteRecorder {
|
|
|
738
744
|
const costUsd = report.deriveCostUsd + report.sessionCostUsd + report.fallbackCostUsd;
|
|
739
745
|
const prompt = pend(report.prompt, TEXT_CAPS.prompt);
|
|
740
746
|
const error = pend(report.error, TEXT_CAPS.reportError);
|
|
747
|
+
const answer = pend(report.answer, TEXT_CAPS.reportAnswer);
|
|
741
748
|
const steps = report.steps?.map(pendStep);
|
|
742
749
|
this.enqueue({
|
|
743
750
|
lane: EXECUTIONS,
|
|
744
751
|
kind: "report",
|
|
745
|
-
bytes: sizeOf(prompt, error, ...(steps ?? []).flatMap((s) => [s.toolInput, s.toolOutput])) + 1024,
|
|
752
|
+
bytes: sizeOf(prompt, error, answer, ...(steps ?? []).flatMap((s) => [s.toolInput, s.toolOutput])) + 1024,
|
|
746
753
|
send: async () => {
|
|
754
|
+
// A redo whose run the service refused to create is no run of its:
|
|
755
|
+
// never named (the service's autocorrect.md AC1), even when the
|
|
756
|
+
// refusal came after the report was built.
|
|
757
|
+
const redoneBy = report.redoneBy && this.runs.get(report.redoneBy.runId)?.settled?.created.created === false
|
|
758
|
+
? undefined
|
|
759
|
+
: report.redoneBy;
|
|
747
760
|
const body = await this.request("POST", `/scenarios/${report.scenarioId}/executions`,
|
|
748
761
|
// Per-step verdicts and the headline failure (errorshandling.md) ride
|
|
749
762
|
// beside the costs. All optional on the service side, so an older
|
|
@@ -776,6 +789,12 @@ export class RemoteRecorder {
|
|
|
776
789
|
// Why nothing ran, when nothing ran. Additive: an older service drops
|
|
777
790
|
// the field and books the cost exactly as it always did.
|
|
778
791
|
declined: report.declined,
|
|
792
|
+
// The plan ran to its end and the agent redid the task (the
|
|
793
|
+
// service's autocorrect.md AC1). Additive, like `declined`.
|
|
794
|
+
redoneBy,
|
|
795
|
+
// ...or only answered: its answer, capped and redacted like any
|
|
796
|
+
// text (AC11). Additive, like `redoneBy`.
|
|
797
|
+
answer: out(answer),
|
|
779
798
|
}, steps), { retryTransient: Boolean(report.ticket) });
|
|
780
799
|
const r = (body ?? {});
|
|
781
800
|
const failed = report.steps?.filter((s) => s.status === "failed").length ?? 0;
|
|
@@ -795,6 +814,9 @@ export class RemoteRecorder {
|
|
|
795
814
|
baselineEligible: report.baselineEligible === false ? false : undefined,
|
|
796
815
|
sharedWith: report.sharedWith?.length,
|
|
797
816
|
declined: report.declined,
|
|
817
|
+
// `bir investigate` prints it beside the outcome: "redone by run_…".
|
|
818
|
+
redoneBy: report.redoneBy?.runId,
|
|
819
|
+
ownCalls: report.redoneBy?.ownCalls,
|
|
798
820
|
});
|
|
799
821
|
},
|
|
800
822
|
});
|
|
@@ -90,11 +90,7 @@ export declare class SwitchableRecorder implements Recorder {
|
|
|
90
90
|
currentAccessToken(): string | undefined;
|
|
91
91
|
startRun(input: string, metadata?: Record<string, unknown>, options?: StartRunOptions): string;
|
|
92
92
|
recordToolSelected(runId: string, stepIndex: number, d: Parameters<Recorder["recordToolSelected"]>[2]): string;
|
|
93
|
-
recordToolResponse(runId: string, stepIndex: number, d:
|
|
94
|
-
toolName: string;
|
|
95
|
-
toolOutput?: string;
|
|
96
|
-
toolError?: string;
|
|
97
|
-
}): string;
|
|
93
|
+
recordToolResponse(runId: string, stepIndex: number, d: Parameters<Recorder["recordToolResponse"]>[2]): string;
|
|
98
94
|
recordFinalAnswer(runId: string, stepIndex: number, d: {
|
|
99
95
|
answer: string;
|
|
100
96
|
}): string;
|
package/dist/replay/bundle.d.ts
CHANGED
|
@@ -34,7 +34,17 @@ export interface BundleEntry {
|
|
|
34
34
|
response: string;
|
|
35
35
|
/** True when `response` is a recorded output, not a fresh run. */
|
|
36
36
|
recorded?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Set on a step its test left out (the service's autocorrect.md AC5): no
|
|
39
|
+
* call was made, and the bundle says so in its place, so the model can tell
|
|
40
|
+
* a whole answer from a part. `text` is the test in words, when it has some.
|
|
41
|
+
*/
|
|
42
|
+
notNeeded?: {
|
|
43
|
+
text: string | null;
|
|
44
|
+
};
|
|
37
45
|
}
|
|
46
|
+
/** The test's words, one line and short, for a bundle or a note. */
|
|
47
|
+
export declare function testWords(text: string | null | undefined): string | null;
|
|
38
48
|
/** Cap a computed input for inclusion in a bundle. */
|
|
39
49
|
export declare function bundleInput(value: unknown): string;
|
|
40
50
|
export interface AssembleOptions {
|
package/dist/replay/bundle.js
CHANGED
|
@@ -30,6 +30,13 @@ export const BASH_CHANNEL_MAX = 25_000;
|
|
|
30
30
|
const MAX_INPUT_CHARS = 2_000;
|
|
31
31
|
/** Fixed structural overhead reserved per step (labels, separators). */
|
|
32
32
|
const PER_STEP_OVERHEAD = 80;
|
|
33
|
+
/** The test's words, one line and short, for a bundle or a note. */
|
|
34
|
+
export function testWords(text) {
|
|
35
|
+
const line = (text ?? "").trim().split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
36
|
+
if (!line)
|
|
37
|
+
return null;
|
|
38
|
+
return line.length > 200 ? `${line.slice(0, 200)}…` : line;
|
|
39
|
+
}
|
|
33
40
|
/** Cap a computed input for inclusion in a bundle. */
|
|
34
41
|
export function bundleInput(value) {
|
|
35
42
|
let text;
|
|
@@ -90,7 +97,9 @@ const SUBTASK_FOOTER = "\n\nThe calls above are done. Continue your task, callin
|
|
|
90
97
|
* Compose the bundle. `maxChars` is a hard ceiling on the returned string.
|
|
91
98
|
*/
|
|
92
99
|
export function assembleBundle(entries, maxChars, opts = {}) {
|
|
93
|
-
|
|
100
|
+
// A step left out has no response to share the budget with: only the steps
|
|
101
|
+
// that answered split it.
|
|
102
|
+
const n = entries.filter((e) => !e.notNeeded).length;
|
|
94
103
|
const anyRecorded = entries.some((e) => e.recorded);
|
|
95
104
|
// A hand-over outranks a sub-task: it says "continue" too, and it also has to
|
|
96
105
|
// say that the plan stopped short. A sub-task outranks the live and mixed
|
|
@@ -111,10 +120,22 @@ export function assembleBundle(entries, maxChars, opts = {}) {
|
|
|
111
120
|
: LIVE_FOOTER;
|
|
112
121
|
const fixedCost = header.length +
|
|
113
122
|
footer.length +
|
|
114
|
-
entries.reduce((sum, e) => sum +
|
|
123
|
+
entries.reduce((sum, e) => sum +
|
|
124
|
+
PER_STEP_OVERHEAD +
|
|
125
|
+
e.toolName.length +
|
|
126
|
+
e.input.length +
|
|
127
|
+
// A left-out step's line, which has no response to take from.
|
|
128
|
+
(e.notNeeded ? PER_STEP_OVERHEAD + (testWords(e.notNeeded.text)?.length ?? 0) : 0), 0);
|
|
115
129
|
const responseBudget = Math.max(0, maxChars - fixedCost);
|
|
116
130
|
const perResponse = n > 0 ? Math.floor(responseBudget / n) : responseBudget;
|
|
117
131
|
const sections = entries.map((e, i) => {
|
|
132
|
+
if (e.notNeeded) {
|
|
133
|
+
// Never "skipped" or "failed": the step was left out on purpose, because
|
|
134
|
+
// this request does not need it, and nothing is missing for its absence.
|
|
135
|
+
const why = testWords(e.notNeeded.text);
|
|
136
|
+
return (`\n\n--- Step ${i + 1}: ${e.toolName} (not needed) ---\n` +
|
|
137
|
+
`Not run: this request does not need it${why ? ` — it is only for when ${why}` : ""}.`);
|
|
138
|
+
}
|
|
118
139
|
const response = truncate(e.response, perResponse);
|
|
119
140
|
return (`\n\n--- Step ${i + 1}: ${e.toolName} ---\n` +
|
|
120
141
|
`Call: ${e.input}\n` +
|
|
@@ -514,6 +514,60 @@ export interface ReplayState {
|
|
|
514
514
|
* sample of what the task costs unaided.
|
|
515
515
|
*/
|
|
516
516
|
hostStop?: HostStop;
|
|
517
|
+
/**
|
|
518
|
+
* The served chain's revision, for the mark on the plan's own calls when
|
|
519
|
+
* they are recorded into the turn's newest run (autocorrect.md AC2).
|
|
520
|
+
*/
|
|
521
|
+
chainRevision?: number;
|
|
522
|
+
/**
|
|
523
|
+
* Keep every call this plan makes, whole, for the turn's newest run: set by
|
|
524
|
+
* the control server on a prompt plan whose match offered to record the
|
|
525
|
+
* turn as a repeat. A plan that runs to its end and is then redone by the
|
|
526
|
+
* agent has its calls recorded first into that repeat (AC2) — the report's
|
|
527
|
+
* copies are cut to 3,800 bytes, and the agent's work is built on them.
|
|
528
|
+
*/
|
|
529
|
+
keepCalls?: boolean;
|
|
530
|
+
/**
|
|
531
|
+
* Those calls, in the order the plan made them, keyed by frame and step —
|
|
532
|
+
* a sub-task called twice keeps both calls of each step.
|
|
533
|
+
*/
|
|
534
|
+
turnCalls?: Map<string, TurnCall>;
|
|
535
|
+
/** What they hold, in characters: bounded by {@link TURN_CALLS_MAX_CHARS}. */
|
|
536
|
+
turnCallChars?: number;
|
|
537
|
+
/**
|
|
538
|
+
* Steer mode: the steps a test just left out (AC5), and the note that tells
|
|
539
|
+
* the model so — riding on the `PostToolUse` that moved the cursor past
|
|
540
|
+
* them, or given in place of a call the model makes to one of them.
|
|
541
|
+
*/
|
|
542
|
+
skipNote?: {
|
|
543
|
+
text: string;
|
|
544
|
+
tools: Set<string>;
|
|
545
|
+
delivered: boolean;
|
|
546
|
+
refused?: number;
|
|
547
|
+
/**
|
|
548
|
+
* It says the sequence is complete — the steps left out were the plan's
|
|
549
|
+
* last. The only note that outlives the plan: every other one names a step
|
|
550
|
+
* to call next, which a retired plan no longer has (AC5 review).
|
|
551
|
+
*/
|
|
552
|
+
complete?: boolean;
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* One of a plan's own calls, kept whole for the turn's newest run (the
|
|
557
|
+
* service's autocorrect.md AC2): redacted and capped like a recorded call,
|
|
558
|
+
* never cut to what a report carries.
|
|
559
|
+
*/
|
|
560
|
+
export interface TurnCall {
|
|
561
|
+
scenarioId: string;
|
|
562
|
+
stepIndex: number;
|
|
563
|
+
chainRevision: number | null;
|
|
564
|
+
toolName: string;
|
|
565
|
+
/** Serialized and redacted, at most 64 KiB, as a recorded call's input is. */
|
|
566
|
+
input: string;
|
|
567
|
+
/** The tool's answer, when it gave one; on the same terms. */
|
|
568
|
+
output?: string;
|
|
569
|
+
/** What it reported instead, when it failed. */
|
|
570
|
+
error?: string;
|
|
517
571
|
}
|
|
518
572
|
/**
|
|
519
573
|
* Why a match did not arm. One code per gate of the ladder, so the caller can
|
|
@@ -522,10 +576,21 @@ export interface ReplayState {
|
|
|
522
576
|
export type DeclineCode = "replay_disabled" | "not_ready" | "flatten_failed" | "known_bad_first_step" | "nondeterministic_first_step" | "unusable_first_step" | "coverage" | "missing_target" | "no_derive_key"
|
|
523
577
|
/** The service handed over no plan: this runner is too old for it. */
|
|
524
578
|
| "runner_update_required"
|
|
579
|
+
/**
|
|
580
|
+
* The service handed over no plan: it holds it, because the agent did the
|
|
581
|
+
* task again after the plan's last run (the service's autocorrect.md AC4).
|
|
582
|
+
*/
|
|
583
|
+
| "held"
|
|
525
584
|
/** The session's host only records: its adapter asked for no plan with the prompt. */
|
|
526
585
|
| "host_records_only"
|
|
527
586
|
/** The plan names a tool the host's agent does not hold. */
|
|
528
587
|
| "host_tool_missing"
|
|
588
|
+
/**
|
|
589
|
+
* The plan has steps that run only when a test says so (the service's
|
|
590
|
+
* autocorrect.md AC5), and the session's host is another app, whose adapter
|
|
591
|
+
* steps through a plan call by call and knows no tests yet.
|
|
592
|
+
*/
|
|
593
|
+
| "host_tests_unsupported"
|
|
529
594
|
/**
|
|
530
595
|
* No gate said no: the plan was good, and late. Not ready when the prompt
|
|
531
596
|
* had to be answered (`arm_timeout`), or ready just after it had been
|
|
@@ -786,6 +851,50 @@ export declare class ReplayController {
|
|
|
786
851
|
* ({@link pinFailure}). `how` lets the caller say so outright.
|
|
787
852
|
*/
|
|
788
853
|
postTool(state: ReplayState, toolUseId: string, output: string, failed?: string, how?: PostToolOptions): boolean;
|
|
854
|
+
/**
|
|
855
|
+
* A steer plan whose cursor reached its end: in front of a parked step (or a
|
|
856
|
+
* call the service could not answer), which hands over; otherwise it ran to
|
|
857
|
+
* its end — and from here every call is the model's own.
|
|
858
|
+
*/
|
|
859
|
+
private finishSteer;
|
|
860
|
+
/**
|
|
861
|
+
* Move a plan's own cursor past the steps this request does not need, and
|
|
862
|
+
* act on what its tests decide (the service's autocorrect.md AC5). Every
|
|
863
|
+
* verdict is recorded as it is made. `stopped` means the plan handed over —
|
|
864
|
+
* the other half, a test that could not answer, or no step needed at all —
|
|
865
|
+
* and its note waits in {@link takeNote}. Otherwise the steps left out are
|
|
866
|
+
* named in a note for the model ({@link takeSkipNote}), and the caller goes
|
|
867
|
+
* on: a step is due, or the plan is done.
|
|
868
|
+
*
|
|
869
|
+
* Nothing at all for a plan without tests, which behaves exactly as before.
|
|
870
|
+
*/
|
|
871
|
+
private settleCursor;
|
|
872
|
+
/** What the model is told about steps a test left out (AC5). */
|
|
873
|
+
private skipNoteText;
|
|
874
|
+
/**
|
|
875
|
+
* The note naming the steps a test just left out, claimed for delivery on a
|
|
876
|
+
* `PostToolUse` (AC5). Returned once; undefined when there is none.
|
|
877
|
+
*/
|
|
878
|
+
takeSkipNote(state: ReplayState | undefined): string | undefined;
|
|
879
|
+
/**
|
|
880
|
+
* The same note, for a call the model is about to make to one of the steps
|
|
881
|
+
* it names — when the note has not reached the model yet. The control server
|
|
882
|
+
* gives it in place of that call, whether the plan is still steering or has
|
|
883
|
+
* just finished: a step left out must never run because the note was late.
|
|
884
|
+
*/
|
|
885
|
+
skipNoteFor(state: ReplayState | undefined, toolName: string): string | undefined;
|
|
886
|
+
/**
|
|
887
|
+
* The pin guard took a call to the next step as that step: a note still
|
|
888
|
+
* waiting to say that a step before it was left out has nothing left to
|
|
889
|
+
* stop, and goes — as it went at the call before the guard existed.
|
|
890
|
+
*/
|
|
891
|
+
pinTaken(state: ReplayState | undefined): void;
|
|
892
|
+
/**
|
|
893
|
+
* The plan's own calls of the turn, whole, in the order it made them — for
|
|
894
|
+
* the turn's newest run when the agent redoes the task (AC2). Empty unless
|
|
895
|
+
* the control server asked for them to be kept ({@link ReplayState.keepCalls}).
|
|
896
|
+
*/
|
|
897
|
+
turnCallsOf(state: ReplayState | undefined): TurnCall[];
|
|
789
898
|
/**
|
|
790
899
|
* The result of a call pinned under a plan that has since handed over
|
|
791
900
|
* because another call ran beside it (`concurrentCall`). Nothing threads —
|
|
@@ -854,6 +963,16 @@ export declare class ReplayController {
|
|
|
854
963
|
* usual cost.
|
|
855
964
|
*/
|
|
856
965
|
cutShort?: boolean;
|
|
966
|
+
/**
|
|
967
|
+
* The plan ran to its end and the agent redid the task in a repeat the
|
|
968
|
+
* service took (autocorrect.md AC1): that repeat's id and the agent's
|
|
969
|
+
* own calls in it. Only the control server knows, and only for the
|
|
970
|
+
* prompt's plan.
|
|
971
|
+
*/
|
|
972
|
+
redoneBy?: {
|
|
973
|
+
runId: string;
|
|
974
|
+
ownCalls: number;
|
|
975
|
+
};
|
|
857
976
|
}): ExecutionReport | undefined;
|
|
858
977
|
/**
|
|
859
978
|
* The step a failed replay is fairly blamed on: the first whose own logic
|
|
@@ -951,6 +1070,13 @@ export declare class ReplayController {
|
|
|
951
1070
|
* step.
|
|
952
1071
|
*/
|
|
953
1072
|
private stopKindOf;
|
|
1073
|
+
/**
|
|
1074
|
+
* The steps before flat position `bound` that ran, 1-based, and the ones
|
|
1075
|
+
* their tests left out (AC5) — the plan cursor's own, and a divergence
|
|
1076
|
+
* bundle's (`extra`). `left` is empty when no step was left out; `ran` is
|
|
1077
|
+
* then not filled, since every step before `bound` ran.
|
|
1078
|
+
*/
|
|
1079
|
+
private ranBefore;
|
|
954
1080
|
private noteFor;
|
|
955
1081
|
/**
|
|
956
1082
|
* The executor handed to a plan: dispatch a step to the proxy that owns its
|
|
@@ -1038,6 +1164,13 @@ export declare class ReplayController {
|
|
|
1038
1164
|
* the recording's input and output beside this run's verdict.
|
|
1039
1165
|
*/
|
|
1040
1166
|
private recordStep;
|
|
1167
|
+
/**
|
|
1168
|
+
* Keep one call of the plan whole (AC2): its arguments and its answer,
|
|
1169
|
+
* redacted and capped as a recorded call's are. Later news about the same
|
|
1170
|
+
* step replaces earlier, in place, so the order is the order the calls went
|
|
1171
|
+
* out. Past {@link TURN_CALLS_MAX_CHARS} a call keeps only a report's cut.
|
|
1172
|
+
*/
|
|
1173
|
+
private keepCall;
|
|
1041
1174
|
/** The step verdicts, in chain order, for the execution report. */
|
|
1042
1175
|
private stepResultsOf;
|
|
1043
1176
|
private logStep;
|