@basein/runner 0.3.26 → 0.3.28
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 +13 -0
- package/dist/bin/bir-scenario.js +7 -5
- package/dist/bin/bir.js +36 -5
- package/dist/bin/investigate.d.ts +13 -0
- package/dist/bin/investigate.js +67 -2
- package/dist/bin/scenario-edit.d.ts +102 -1
- package/dist/bin/scenario-edit.js +318 -23
- package/dist/control/server.d.ts +31 -2
- package/dist/control/server.js +170 -17
- package/dist/control/transcript.d.ts +9 -0
- package/dist/control/transcript.js +56 -9
- package/dist/record/recorder.d.ts +50 -0
- package/dist/record/remote-recorder.d.ts +9 -1
- package/dist/record/remote-recorder.js +38 -0
- package/dist/replay/controller.d.ts +71 -2
- package/dist/replay/controller.js +137 -19
- package/dist/replay/plan.d.ts +41 -0
- package/dist/replay/plan.js +171 -3
- package/dist/replay/reads-only.d.ts +48 -0
- package/dist/replay/reads-only.js +161 -0
- package/docs/calculatedReplayGuide.md +74 -1
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* degrades to "run the turn normally" or returns something the session can
|
|
12
12
|
* ignore. Nothing throws at a hook.
|
|
13
13
|
*/
|
|
14
|
-
import type { ExecutionReport } from "../record/recorder.js";
|
|
14
|
+
import type { ExecutionReport, ExecutionTurn, TurnUsage } from "../record/recorder.js";
|
|
15
15
|
import { type ReplayMode, type StepReach } from "./coverage.js";
|
|
16
16
|
import { deriveParameters, type AgentDay, type DeriveContext, type DeriveResult } from "./derive.js";
|
|
17
17
|
import { type HandoverStopReason } from "./handover.js";
|
|
@@ -92,6 +92,13 @@ export interface PreToolOptions {
|
|
|
92
92
|
* route's deadline guard spoke first. No further step is started.
|
|
93
93
|
*/
|
|
94
94
|
signal?: AbortSignal;
|
|
95
|
+
/**
|
|
96
|
+
* The call only looks and changes nothing (replay/reads-only.ts), as the
|
|
97
|
+
* control server judged it with what the tool's server says of it. A direct
|
|
98
|
+
* plan the model has not asked for yet lets such a call through and waits
|
|
99
|
+
* for `run_scenario` (B2 is11.md); nothing runs in its place.
|
|
100
|
+
*/
|
|
101
|
+
readsOnly?: boolean;
|
|
95
102
|
}
|
|
96
103
|
/**
|
|
97
104
|
* What the caller knows about how a pinned call ended, beyond its error text
|
|
@@ -353,6 +360,15 @@ export interface Handover {
|
|
|
353
360
|
error?: string;
|
|
354
361
|
/** For a `step_failed`: where the stopped step stood ({@link stopOf}). */
|
|
355
362
|
stop?: string;
|
|
363
|
+
/**
|
|
364
|
+
* Why it stopped, as a code: for a `step_failed` the stop's own reason
|
|
365
|
+
* (`budget`, `cancelled`, `test_failed`, `concurrent`, `host_rejected`, …),
|
|
366
|
+
* `failed` for a plain failure; for another kind the kind itself. Only the
|
|
367
|
+
* report reads it (the service's learningPhase.md LP11): {@link stop}
|
|
368
|
+
* folds the stops in front of a step into `not_run`, and that is all the
|
|
369
|
+
* service needs to continue one, not to tell why a plan stopped.
|
|
370
|
+
*/
|
|
371
|
+
reason?: string;
|
|
356
372
|
/** What the model is told. Always built, even when delivery fails. */
|
|
357
373
|
note: string;
|
|
358
374
|
/** Set once the note reached the model, so it is never delivered twice. */
|
|
@@ -461,6 +477,16 @@ export interface ReplayState {
|
|
|
461
477
|
bashCwd?: string;
|
|
462
478
|
/** Set when the plan handed the task to the model part-way (fallbk.md). */
|
|
463
479
|
handover?: Handover;
|
|
480
|
+
/**
|
|
481
|
+
* Characters this plan handed the agent to read so far — `run_scenario`'s
|
|
482
|
+
* answer, a divergence's bundle, the notes told in place of a call or
|
|
483
|
+
* beside its result — for the report's `turn` (the service's
|
|
484
|
+
* learningPhase.md LP11). Counted where the text leaves for the agent,
|
|
485
|
+
* never where it is built: a note made and never delivered was not read.
|
|
486
|
+
*/
|
|
487
|
+
handedChars?: number;
|
|
488
|
+
/** The bundle this plan handed the agent, when it handed one: brief (0.3.25), or the full one. */
|
|
489
|
+
bundleKind?: "brief" | "full";
|
|
464
490
|
/**
|
|
465
491
|
* Every planned step ran and the model was handed the results to answer
|
|
466
492
|
* from: a steered plan whose last step threaded, a direct run that ran to
|
|
@@ -514,6 +540,12 @@ export interface ReplayState {
|
|
|
514
540
|
running?: Promise<RunArmedResult>;
|
|
515
541
|
/** The model called `run_scenario` for this plan (its `PreToolUse` was seen). */
|
|
516
542
|
directRequested?: boolean;
|
|
543
|
+
/**
|
|
544
|
+
* Calls that only look, made while this direct plan waited for
|
|
545
|
+
* `run_scenario` (B2 is11.md): each ran as the model wrote it, and the plan
|
|
546
|
+
* stayed announced. The first is told the plan still waits, once.
|
|
547
|
+
*/
|
|
548
|
+
readsWaited?: number;
|
|
517
549
|
/**
|
|
518
550
|
* The direct run's caller went away mid-run — Esc on `run_scenario`, or its
|
|
519
551
|
* client timing out. The turn it cut short is no measurement of the task, so
|
|
@@ -677,7 +709,23 @@ export type DeclineCode = "replay_disabled" | "not_ready" | "known_bad_first_ste
|
|
|
677
709
|
* failed on this request, so none of its steps could be computed. Set by
|
|
678
710
|
* the control server.
|
|
679
711
|
*/
|
|
680
|
-
| "params_failed"
|
|
712
|
+
| "params_failed"
|
|
713
|
+
/**
|
|
714
|
+
* A direct plan announced to the model, which made a call that may change
|
|
715
|
+
* something before it called `run_scenario`: the plan retired running
|
|
716
|
+
* nothing, because run after the model's own change it could do the same
|
|
717
|
+
* work twice. Calls that only look before it do not count (B2 is11.md).
|
|
718
|
+
* Set by the control server (runners after 0.3.26; up to it, such a plan
|
|
719
|
+
* was retired at any first call, and reported no reason).
|
|
720
|
+
*/
|
|
721
|
+
| "changed_first"
|
|
722
|
+
/**
|
|
723
|
+
* A direct plan whose `run_scenario` call never reached the runner: the
|
|
724
|
+
* person refused it, a rule denied it, or it failed on the way. Refused
|
|
725
|
+
* means refused: the plan retired running nothing. Set by the control
|
|
726
|
+
* server (runners after 0.3.26; up to it, reported with no reason).
|
|
727
|
+
*/
|
|
728
|
+
| "run_refused";
|
|
681
729
|
/**
|
|
682
730
|
* What a continuation continues (the service's fallbk.md Part 3): the plan
|
|
683
731
|
* that handed over, the step it stopped in front of, and the recording of the
|
|
@@ -696,6 +744,14 @@ export interface ContinuationOf {
|
|
|
696
744
|
* user's request: it ends with the answer.
|
|
697
745
|
*/
|
|
698
746
|
export declare function isSubTask(state: Pick<ReplayState, "armedBy" | "continuation">): boolean;
|
|
747
|
+
/**
|
|
748
|
+
* The report's `turn` (the service's learningPhase.md LP11): what this plan
|
|
749
|
+
* did in the turn, from its state, and the window's tokens and model calls,
|
|
750
|
+
* which only the control server can read (`usage`; undefined when it could
|
|
751
|
+
* not). Every value is null when it is not known, and nothing here throws: a
|
|
752
|
+
* state it cannot read is a `turn` of nulls, never a report lost.
|
|
753
|
+
*/
|
|
754
|
+
export declare function turnOf(state: ReplayState, usage?: TurnUsage): ExecutionTurn;
|
|
699
755
|
/** What `PreToolUse` should do about this call. */
|
|
700
756
|
export type PreToolAction =
|
|
701
757
|
/** Pin the arguments and let the real tool run. */
|
|
@@ -874,6 +930,13 @@ export declare class ReplayController {
|
|
|
874
930
|
* Returns it once and marks it delivered; undefined when there is none.
|
|
875
931
|
*/
|
|
876
932
|
takeNote(state: ReplayState | undefined): string | undefined;
|
|
933
|
+
/**
|
|
934
|
+
* The hand-over note `run_scenario` answers with: claimed when it is still
|
|
935
|
+
* to tell ({@link takeNote}), and told again when it was told already — the
|
|
936
|
+
* same text the run always answered (`takeNote(state) ?? the note`), now
|
|
937
|
+
* counted as read either way.
|
|
938
|
+
*/
|
|
939
|
+
private runNote;
|
|
877
940
|
/** Whether a note about this plan is still to be told to the model: a hand-over's, or the unseen results'. */
|
|
878
941
|
hasUndeliveredNote(state: ReplayState | undefined): boolean;
|
|
879
942
|
/**
|
|
@@ -1091,6 +1154,12 @@ export declare class ReplayController {
|
|
|
1091
1154
|
* own baseline, so the turn's saving is booked somewhere.
|
|
1092
1155
|
*/
|
|
1093
1156
|
booksOwnSaving?: boolean;
|
|
1157
|
+
/**
|
|
1158
|
+
* The model calls and tokens of the window `sessionCostUsd` was read
|
|
1159
|
+
* for, for the report's `turn` (the service's learningPhase.md LP11).
|
|
1160
|
+
* Only the control server holds the transcript; absent, they are null.
|
|
1161
|
+
*/
|
|
1162
|
+
usage?: TurnUsage;
|
|
1094
1163
|
}): ExecutionReport | undefined;
|
|
1095
1164
|
/**
|
|
1096
1165
|
* The step a failed replay is fairly blamed on: the first whose own logic
|
|
@@ -221,6 +221,10 @@ export function sentACall(state) {
|
|
|
221
221
|
}
|
|
222
222
|
return false;
|
|
223
223
|
}
|
|
224
|
+
/** {@link Handover.reason}: a stopped step's own reason, `failed` for a plain failure, else the kind. */
|
|
225
|
+
function handoverReason(kind, stopReason) {
|
|
226
|
+
return kind === "step_failed" ? (stopReason ?? "failed") : kind;
|
|
227
|
+
}
|
|
224
228
|
/**
|
|
225
229
|
* How long a call waits for the previous pin to thread before it is judged to
|
|
226
230
|
* be running beside it: the control server's hook-view fallback
|
|
@@ -255,6 +259,59 @@ const TURN_CALLS_MAX_CHARS = 8 * 1024 * 1024;
|
|
|
255
259
|
export function isSubTask(state) {
|
|
256
260
|
return state.armedBy === "intent" && state.continuation === undefined;
|
|
257
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Count text the plan hands the agent to read ({@link ReplayState.handedChars}),
|
|
264
|
+
* where it leaves for the agent. Adds a length and nothing else.
|
|
265
|
+
*/
|
|
266
|
+
function handed(state, text) {
|
|
267
|
+
if (!state || typeof text !== "string" || text.length === 0)
|
|
268
|
+
return;
|
|
269
|
+
state.handedChars = (state.handedChars ?? 0) + text.length;
|
|
270
|
+
}
|
|
271
|
+
/** Longest `stop` or `reason` a report's `turn` carries: they are codes, and a code is short. */
|
|
272
|
+
const TURN_CODE_MAX = 64;
|
|
273
|
+
const turnCode = (value) => typeof value === "string" && value !== "" ? value.slice(0, TURN_CODE_MAX) : null;
|
|
274
|
+
/**
|
|
275
|
+
* The report's `turn` (the service's learningPhase.md LP11): what this plan
|
|
276
|
+
* did in the turn, from its state, and the window's tokens and model calls,
|
|
277
|
+
* which only the control server can read (`usage`; undefined when it could
|
|
278
|
+
* not). Every value is null when it is not known, and nothing here throws: a
|
|
279
|
+
* state it cannot read is a `turn` of nulls, never a report lost.
|
|
280
|
+
*/
|
|
281
|
+
export function turnOf(state, usage) {
|
|
282
|
+
const count = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.round(n) : null;
|
|
283
|
+
try {
|
|
284
|
+
const armed = state.mode === "direct" || state.mode === "steer";
|
|
285
|
+
const h = state.handover;
|
|
286
|
+
return {
|
|
287
|
+
mode: armed ? state.mode : null,
|
|
288
|
+
modelCalls: count(usage?.modelCalls),
|
|
289
|
+
inputTokens: count(usage?.inputTokens),
|
|
290
|
+
outputTokens: count(usage?.outputTokens),
|
|
291
|
+
cacheReadTokens: count(usage?.cacheReadTokens),
|
|
292
|
+
cacheWriteTokens: count(usage?.cacheWriteTokens),
|
|
293
|
+
bundleChars: armed ? count(state.handedChars ?? 0) : null,
|
|
294
|
+
brief: state.bundleKind === "brief" ? true : state.bundleKind === "full" ? false : null,
|
|
295
|
+
// Read by the control server after the turn, when it reads the answer (AC11).
|
|
296
|
+
answerChars: null,
|
|
297
|
+
handover: h ? { stop: turnCode(h.stop), reason: turnCode(h.reason) } : null,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return {
|
|
302
|
+
mode: null,
|
|
303
|
+
modelCalls: null,
|
|
304
|
+
inputTokens: null,
|
|
305
|
+
outputTokens: null,
|
|
306
|
+
cacheReadTokens: null,
|
|
307
|
+
cacheWriteTokens: null,
|
|
308
|
+
bundleChars: null,
|
|
309
|
+
brief: null,
|
|
310
|
+
answerChars: null,
|
|
311
|
+
handover: null,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
258
315
|
export class ReplayController {
|
|
259
316
|
enabled;
|
|
260
317
|
work = new ProxyWorkQueue();
|
|
@@ -700,21 +757,24 @@ export class ReplayController {
|
|
|
700
757
|
stopAt,
|
|
701
758
|
subTask: isSubTask(state),
|
|
702
759
|
});
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
760
|
+
// A steer directive shows the arguments the steps will run with, so the
|
|
761
|
+
// call the model writes is one the pin guard accepts (plan.ts,
|
|
762
|
+
// `steeringDirective`). A direct one says which of this request's values
|
|
763
|
+
// the steps take, and puts this request's values where the recording's
|
|
764
|
+
// stand in its intent (B2 is11.md): "set Olga up" under a request for
|
|
765
|
+
// Carlos made a careful model check first, and that check retired the
|
|
766
|
+
// plan. Both need the parameters settled — which costs nothing when the
|
|
767
|
+
// derivation has already answered: gate 7 waited for it, or there was
|
|
768
|
+
// nothing to derive. One turn of the loop lets an answer that is already
|
|
769
|
+
// in hand through; one still on its way is not waited for here, and the
|
|
770
|
+
// directive leaves the values out (a direct one names each recorded value
|
|
771
|
+
// by its role instead). A `paramsLogic` that throws is not judged here:
|
|
772
|
+
// the first call that needs the parameters meets the same failure, as
|
|
773
|
+
// before.
|
|
774
|
+
if (!deriveAnswered)
|
|
775
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
776
|
+
if (deriveAnswered)
|
|
777
|
+
await state.plan.ready().catch(() => undefined);
|
|
718
778
|
state.mode = mode;
|
|
719
779
|
state.stepsPlanned = steps.length;
|
|
720
780
|
// The chain this plan was served at: what its own calls are marked with
|
|
@@ -967,6 +1027,7 @@ export class ReplayController {
|
|
|
967
1027
|
scenario: state.scenarioId ?? undefined,
|
|
968
1028
|
bytes: h.note.length,
|
|
969
1029
|
});
|
|
1030
|
+
handed(state, h.note);
|
|
970
1031
|
return h.note;
|
|
971
1032
|
}
|
|
972
1033
|
// A plan that ran to its end with nobody there to read it has no
|
|
@@ -975,10 +1036,25 @@ export class ReplayController {
|
|
|
975
1036
|
if (u && !u.delivered) {
|
|
976
1037
|
u.delivered = true;
|
|
977
1038
|
logDetail("replay.unseen_delivered", { scenario: state.scenarioId ?? undefined, bytes: u.note.length });
|
|
1039
|
+
handed(state, u.note);
|
|
978
1040
|
return u.note;
|
|
979
1041
|
}
|
|
980
1042
|
return undefined;
|
|
981
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* The hand-over note `run_scenario` answers with: claimed when it is still
|
|
1046
|
+
* to tell ({@link takeNote}), and told again when it was told already — the
|
|
1047
|
+
* same text the run always answered (`takeNote(state) ?? the note`), now
|
|
1048
|
+
* counted as read either way.
|
|
1049
|
+
*/
|
|
1050
|
+
runNote(state) {
|
|
1051
|
+
const note = this.takeNote(state);
|
|
1052
|
+
if (note !== undefined)
|
|
1053
|
+
return note;
|
|
1054
|
+
const again = state.handover?.note;
|
|
1055
|
+
handed(state, again);
|
|
1056
|
+
return again;
|
|
1057
|
+
}
|
|
982
1058
|
/** Whether a note about this plan is still to be told to the model: a hand-over's, or the unseen results'. */
|
|
983
1059
|
hasUndeliveredNote(state) {
|
|
984
1060
|
return Boolean((state?.handover && !state.handover.delivered) || (state?.unseen && !state.unseen.delivered));
|
|
@@ -1055,6 +1131,16 @@ export class ReplayController {
|
|
|
1055
1131
|
});
|
|
1056
1132
|
return { kind: "passthrough" };
|
|
1057
1133
|
}
|
|
1134
|
+
// A CALL THAT ONLY LOOKS DOES NOT END A DIRECT PLAN (B2 is11.md). Asked
|
|
1135
|
+
// for `run_scenario` first, the model looked at something first instead —
|
|
1136
|
+
// in is11 its own notes, after the note had named another request's
|
|
1137
|
+
// person. Read as a divergence, this call would run every step here,
|
|
1138
|
+
// unattended; read as the model leaving the plan, it retired a plan that
|
|
1139
|
+
// would have done the task right. It is neither: it runs as written,
|
|
1140
|
+
// nothing runs in its place, and the plan waits for `run_scenario`. The
|
|
1141
|
+
// control server retires it at the first call that may change something.
|
|
1142
|
+
if (state.mode === "direct" && hook.readsOnly === true)
|
|
1143
|
+
return { kind: "passthrough" };
|
|
1058
1144
|
// Everything below — the wait for the parameters, and a divergence that
|
|
1059
1145
|
// executes the rest of the plan — happens inside this one hook, which the
|
|
1060
1146
|
// host waits on for a bounded time. One deadline covers all of it — and
|
|
@@ -1142,6 +1228,7 @@ export class ReplayController {
|
|
|
1142
1228
|
if (skip && toolName !== plan.expectedTool() && skip.tools.has(toolName) && (skip.refused ?? 0) < 2) {
|
|
1143
1229
|
skip.delivered = true;
|
|
1144
1230
|
skip.refused = (skip.refused ?? 0) + 1;
|
|
1231
|
+
handed(state, skip.text);
|
|
1145
1232
|
return this.deliverInstead(skip.text, toolName, otherHost);
|
|
1146
1233
|
}
|
|
1147
1234
|
// A call to the next step's tool: a note the model has read is done
|
|
@@ -1673,6 +1760,7 @@ export class ReplayController {
|
|
|
1673
1760
|
if (!skip || skip.delivered)
|
|
1674
1761
|
return undefined;
|
|
1675
1762
|
skip.delivered = true;
|
|
1763
|
+
handed(state, skip.text);
|
|
1676
1764
|
return skip.text;
|
|
1677
1765
|
}
|
|
1678
1766
|
/**
|
|
@@ -1686,6 +1774,7 @@ export class ReplayController {
|
|
|
1686
1774
|
if (!skip || skip.delivered || !skip.tools.has(toolName))
|
|
1687
1775
|
return undefined;
|
|
1688
1776
|
skip.delivered = true;
|
|
1777
|
+
handed(state, skip.text);
|
|
1689
1778
|
return skip.text;
|
|
1690
1779
|
}
|
|
1691
1780
|
/**
|
|
@@ -1874,7 +1963,7 @@ export class ReplayController {
|
|
|
1874
1963
|
delivery: "none",
|
|
1875
1964
|
stopReason: "not_run",
|
|
1876
1965
|
});
|
|
1877
|
-
return handedOver(this.
|
|
1966
|
+
return handedOver(this.runNote(state)) ?? { ok: false, why: ready.error };
|
|
1878
1967
|
}
|
|
1879
1968
|
const deadline = Date.now() + this.budgets.planMs;
|
|
1880
1969
|
let result;
|
|
@@ -1968,12 +2057,15 @@ export class ReplayController {
|
|
|
1968
2057
|
? { ok: true, text: `${note}\n\n${result.text}`, responseModel: {}, steps: result.executed + result.recorded, partial: true }
|
|
1969
2058
|
: (handedOver(note) ?? { ok: false, why: note });
|
|
1970
2059
|
}
|
|
1971
|
-
const note = this.
|
|
2060
|
+
const note = this.runNote(state) ?? "";
|
|
1972
2061
|
if (!live) {
|
|
1973
2062
|
// Nothing ran: the tool answers with an error that says so, which is
|
|
1974
2063
|
// what a model reacts to best.
|
|
1975
2064
|
return handedOver(note) ?? { ok: false, why: note };
|
|
1976
2065
|
}
|
|
2066
|
+
// The note in front was counted as it was claimed; the rest of the answer here.
|
|
2067
|
+
handed(state, `\n\n${result.text}`);
|
|
2068
|
+
state.bundleKind = result.brief ? "brief" : "full";
|
|
1977
2069
|
return {
|
|
1978
2070
|
ok: true,
|
|
1979
2071
|
text: `${note}\n\n${result.text}`,
|
|
@@ -1993,7 +2085,7 @@ export class ReplayController {
|
|
|
1993
2085
|
ran: 0,
|
|
1994
2086
|
noneNeeded: true,
|
|
1995
2087
|
});
|
|
1996
|
-
return handedOver(this.
|
|
2088
|
+
return handedOver(this.runNote(state)) ?? { ok: false, why: "" };
|
|
1997
2089
|
}
|
|
1998
2090
|
if (result.executed === 0) {
|
|
1999
2091
|
// Every step stood on its recorded output (BIR_REPLAY_RECORDED_OUTPUTS):
|
|
@@ -2008,7 +2100,7 @@ export class ReplayController {
|
|
|
2008
2100
|
stopReason: "not_run",
|
|
2009
2101
|
ran: 0,
|
|
2010
2102
|
});
|
|
2011
|
-
return handedOver(this.
|
|
2103
|
+
return handedOver(this.runNote(state)) ?? { ok: false, why: "" };
|
|
2012
2104
|
}
|
|
2013
2105
|
// Live steps only, as above: a recorded stand-in did no work here.
|
|
2014
2106
|
state.stepsPinned += result.executed;
|
|
@@ -2074,6 +2166,19 @@ export class ReplayController {
|
|
|
2074
2166
|
why: "the caller went away during the last step — every step ran and nobody read the results",
|
|
2075
2167
|
});
|
|
2076
2168
|
}
|
|
2169
|
+
else {
|
|
2170
|
+
// What `run_scenario` hands the agent: the bundle, and the response
|
|
2171
|
+
// model `bir-scenario` puts under it when there is one.
|
|
2172
|
+
handed(state, result.text);
|
|
2173
|
+
try {
|
|
2174
|
+
if (Object.keys(responseModel).length > 0)
|
|
2175
|
+
handed(state, JSON.stringify(responseModel));
|
|
2176
|
+
}
|
|
2177
|
+
catch {
|
|
2178
|
+
// A model no JSON can carry reaches the agent as nothing: nothing to count.
|
|
2179
|
+
}
|
|
2180
|
+
state.bundleKind = result.brief ? "brief" : "full";
|
|
2181
|
+
}
|
|
2077
2182
|
return {
|
|
2078
2183
|
ok: true,
|
|
2079
2184
|
text: result.text,
|
|
@@ -2189,6 +2294,10 @@ export class ReplayController {
|
|
|
2189
2294
|
d.cutShort === true
|
|
2190
2295
|
? false
|
|
2191
2296
|
: undefined,
|
|
2297
|
+
// How the plan ran, what the turn's model calls and tokens were, what
|
|
2298
|
+
// the agent was handed to read, and why it handed over (LP11). Only
|
|
2299
|
+
// facts the runner already had; an older service drops the field.
|
|
2300
|
+
turn: turnOf(state, d.usage),
|
|
2192
2301
|
};
|
|
2193
2302
|
}
|
|
2194
2303
|
/**
|
|
@@ -2569,12 +2678,14 @@ export class ReplayController {
|
|
|
2569
2678
|
// done its work, and the model must know before it does the task itself.
|
|
2570
2679
|
if (stop?.reason === "uncertain" && stop.position !== undefined) {
|
|
2571
2680
|
const note = this.stopNote(state, plan, stop, "none", composed.left);
|
|
2681
|
+
handed(state, note);
|
|
2572
2682
|
return this.deliverInstead(note, toolName, state.host !== undefined);
|
|
2573
2683
|
}
|
|
2574
2684
|
// The rest is the other half (AC5): the model is told so, in place of
|
|
2575
2685
|
// its call, before it does any of the plan's remaining steps.
|
|
2576
2686
|
if (stop?.kind === "other_branch" && stop.position !== undefined) {
|
|
2577
2687
|
const note = this.stopNote(state, plan, stop, "none", composed.left);
|
|
2688
|
+
handed(state, note);
|
|
2578
2689
|
return this.deliverInstead(note, toolName, state.host !== undefined);
|
|
2579
2690
|
}
|
|
2580
2691
|
// Every remaining step was left out by its test: the steps the session
|
|
@@ -2602,6 +2713,7 @@ export class ReplayController {
|
|
|
2602
2713
|
kind: stopKind,
|
|
2603
2714
|
stepIndex: parked.stepIndex,
|
|
2604
2715
|
toolName: parked.toolName ?? undefined,
|
|
2716
|
+
reason: stopKind,
|
|
2605
2717
|
note: this.noteFor(state, {
|
|
2606
2718
|
kind: stopKind,
|
|
2607
2719
|
step: parked,
|
|
@@ -2625,6 +2737,10 @@ export class ReplayController {
|
|
|
2625
2737
|
// reason is read as adversarial interception.
|
|
2626
2738
|
const action = this.deliverInstead(text, toolName, state.host !== undefined);
|
|
2627
2739
|
logDetail("replay.inject", { channel: action.kind, bytes: text.length });
|
|
2740
|
+
// What the agent reads of it: the whole text as a refusal's reason, as
|
|
2741
|
+
// much as the channel carries as a command's output.
|
|
2742
|
+
handed(state, action.kind === "bash" ? text.slice(0, BASH_CHANNEL_MAX) : text);
|
|
2743
|
+
state.bundleKind = "full";
|
|
2628
2744
|
return action;
|
|
2629
2745
|
}
|
|
2630
2746
|
/**
|
|
@@ -2662,6 +2778,7 @@ export class ReplayController {
|
|
|
2662
2778
|
...(stop.kind === "step_failed"
|
|
2663
2779
|
? { stop: stopOf({ ranThisStep: stop.stage === "tool_output_logic", stopReason: stop.reason }) }
|
|
2664
2780
|
: {}),
|
|
2781
|
+
reason: handoverReason(stop.kind, stop.reason),
|
|
2665
2782
|
note,
|
|
2666
2783
|
delivered: true,
|
|
2667
2784
|
};
|
|
@@ -2686,6 +2803,7 @@ export class ReplayController {
|
|
|
2686
2803
|
toolName: h.step?.toolName ?? undefined,
|
|
2687
2804
|
error: h.error,
|
|
2688
2805
|
...(h.kind === "step_failed" ? { stop: stopOf(h) } : {}),
|
|
2806
|
+
reason: handoverReason(h.kind, h.stopReason),
|
|
2689
2807
|
note: this.noteFor(state, h),
|
|
2690
2808
|
delivered: false,
|
|
2691
2809
|
};
|
package/dist/replay/plan.d.ts
CHANGED
|
@@ -514,6 +514,47 @@ export declare class ScenarioReplayPlan {
|
|
|
514
514
|
* body gets at most `bodyMs` ({@link PREVIEW_LOGIC_MS}) of it.
|
|
515
515
|
*/
|
|
516
516
|
private previewArguments;
|
|
517
|
+
/**
|
|
518
|
+
* This request's value of `key`, as a directive may show it, or undefined
|
|
519
|
+
* (B2 is11.md): once the reading has settled, a plain value the reading
|
|
520
|
+
* took from the request — never a mark, and never a value a step works out
|
|
521
|
+
* (`from: "step"`): before the run nobody has that one, and the recorded one
|
|
522
|
+
* is another request's (issue 1212).
|
|
523
|
+
*/
|
|
524
|
+
private shownValueOf;
|
|
525
|
+
/**
|
|
526
|
+
* A text written from the recording — the intent, a step's reasoning — put
|
|
527
|
+
* right for this request, for a directive to show (B2 is11.md).
|
|
528
|
+
*
|
|
529
|
+
* Both name the recording's values: "set Olga up", "moves each lead to
|
|
530
|
+
* Olga". Shown beside a request for Carlos, the name tells the model the
|
|
531
|
+
* plan is for someone else. A careful model then checks before it runs
|
|
532
|
+
* anything, and a direct plan's first call that was not `run_scenario`
|
|
533
|
+
* retired it — so the replay was lost on exactly the turn replay is for: the
|
|
534
|
+
* same task with a new value. The steps never read the text: they take
|
|
535
|
+
* Carlos from the request.
|
|
536
|
+
*
|
|
537
|
+
* Each recorded value of a parameter found in the text, as a whole word, is
|
|
538
|
+
* replaced by this request's own value where the plan has one it may show
|
|
539
|
+
* ({@link shownValueOf}), and else by the parameter's role (`<assignee
|
|
540
|
+
* name>`): a target the request did not name, a value a step works out, or
|
|
541
|
+
* any value while the reading has not settled. A value the same as the
|
|
542
|
+
* recorded one stays as it is. One pass, longest value first, so nothing put
|
|
543
|
+
* in is replaced again.
|
|
544
|
+
*/
|
|
545
|
+
forThisRequest(text: string): string;
|
|
546
|
+
/** {@link forThisRequest} as one function, built once for a directive's many texts. */
|
|
547
|
+
private rewriter;
|
|
548
|
+
/**
|
|
549
|
+
* This request's own values, for the direct directive's line that says the
|
|
550
|
+
* steps take them (B2 is11.md): what the reading took from the request.
|
|
551
|
+
* The values the request names in words come first — a name, a title: what
|
|
552
|
+
* a reader holds the note against — those unlike the recording's first;
|
|
553
|
+
* then the values worked out from the day ("tomorrow" as a date). Targets
|
|
554
|
+
* only: never a setting, never a value a step works out, never a mark.
|
|
555
|
+
* Undefined until the reading has settled, or when it named none.
|
|
556
|
+
*/
|
|
557
|
+
requestValues(): string | undefined;
|
|
517
558
|
/**
|
|
518
559
|
* The directive injected via `UserPromptSubmit`'s `additionalContext`.
|
|
519
560
|
*
|