@basein/runner 0.3.21 → 0.3.22
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 +7 -16
- package/dist/bin/bir-hooks.js +7 -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/recorder-main.d.ts +0 -15
- package/dist/control/recorder-main.js +7 -39
- package/dist/control/server.d.ts +24 -137
- package/dist/control/server.js +101 -995
- package/dist/record/recorder.d.ts +11 -142
- package/dist/record/recorder.js +0 -3
- package/dist/record/remote-recorder.d.ts +1 -9
- package/dist/record/remote-recorder.js +3 -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 +19 -123
- package/dist/replay/controller.js +151 -518
- 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 +9 -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
package/dist/replay/plan.js
CHANGED
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
* step to know its reach, so it can choose between the proxy, a recorded
|
|
21
21
|
* output, and skipping.
|
|
22
22
|
*/
|
|
23
|
-
import {
|
|
24
|
-
import { flatEntriesOf, frameEdges } from "./flatten.js";
|
|
23
|
+
import { evalParamsLogic, evalResponseParamsLogic, evalToolInputLogic, evalToolOutputLogic, evalWhenLogic, LogicTimeoutError, } from "./logic.js";
|
|
25
24
|
import { assembleBundle, bundleInput, MAX_REPLAY_REASON, testWords } from "./bundle.js";
|
|
26
25
|
import { redact, redactText } from "../record/redact.js";
|
|
27
26
|
import { sinkOfPlan, unsafeParameter } from "./safety.js";
|
|
@@ -272,25 +271,6 @@ function renderArguments(toolName, input) {
|
|
|
272
271
|
}
|
|
273
272
|
return `arguments: {${parts.join(",")}}${shortened ? " (shortened)" : ""}`;
|
|
274
273
|
}
|
|
275
|
-
/**
|
|
276
|
-
* The recorded values for the *settings* a mapping did not name (R-CALL-21).
|
|
277
|
-
*
|
|
278
|
-
* A target must always be supplied by the caller; a setting is what the segment
|
|
279
|
-
* was recorded doing, and taking its sample here is what makes "may be omitted"
|
|
280
|
-
* true rather than "runs with undefined".
|
|
281
|
-
*/
|
|
282
|
-
function settingsOf(paramsObject, mapped) {
|
|
283
|
-
const out = {};
|
|
284
|
-
if (!paramsObject || typeof paramsObject !== "object")
|
|
285
|
-
return out;
|
|
286
|
-
for (const [name, p] of Object.entries(paramsObject)) {
|
|
287
|
-
if (name in mapped)
|
|
288
|
-
continue;
|
|
289
|
-
if (p?.kind === "setting" && p && "sampleValue" in p)
|
|
290
|
-
out[name] = p.sampleValue;
|
|
291
|
-
}
|
|
292
|
-
return out;
|
|
293
|
-
}
|
|
294
274
|
/**
|
|
295
275
|
* Evaluate one of the scenario's logic bodies, naming the step and the stage to
|
|
296
276
|
* the observer if it throws — then rethrow, unchanged and uncaught.
|
|
@@ -303,14 +283,13 @@ function settingsOf(paramsObject, mapped) {
|
|
|
303
283
|
* `output` is what the body was given, for a stage that runs on one: a throw
|
|
304
284
|
* in the output logic is only readable next to the output it threw on.
|
|
305
285
|
*/
|
|
306
|
-
function reportingStage(onStep,
|
|
286
|
+
function reportingStage(onStep, step, stage, input, startedAt, fn, output) {
|
|
307
287
|
try {
|
|
308
288
|
return fn();
|
|
309
289
|
}
|
|
310
290
|
catch (err) {
|
|
311
291
|
onStep?.({
|
|
312
|
-
step
|
|
313
|
-
entry,
|
|
292
|
+
step,
|
|
314
293
|
input,
|
|
315
294
|
output,
|
|
316
295
|
// A value only the recording holds is no fault of the step's (issue 1212).
|
|
@@ -328,8 +307,8 @@ export class ScenarioReplayPlan {
|
|
|
328
307
|
mode;
|
|
329
308
|
intent;
|
|
330
309
|
scenario;
|
|
331
|
-
/** The
|
|
332
|
-
|
|
310
|
+
/** The chain this plan walks. */
|
|
311
|
+
steps;
|
|
333
312
|
paramsPromise;
|
|
334
313
|
/** The values a step worked out in the recording ({@link FROM_STEP_MARK}). */
|
|
335
314
|
fromStep;
|
|
@@ -352,219 +331,33 @@ export class ScenarioReplayPlan {
|
|
|
352
331
|
readyPromise;
|
|
353
332
|
/** Exclusive end of the planned steps. Equal to the chain length unless a step is parked. */
|
|
354
333
|
stopAt;
|
|
355
|
-
/** This plan is a sub-task inside the agent's own task
|
|
334
|
+
/** This plan is a sub-task inside the agent's own task: it was armed mid-task by intent. */
|
|
356
335
|
subTask;
|
|
357
|
-
/**
|
|
358
|
-
|
|
359
|
-
* where it is entered, its test decided and it is left, and where a
|
|
360
|
-
* sub-task its test leaves out ends (AC5).
|
|
361
|
-
*/
|
|
362
|
-
edges;
|
|
363
|
-
/** Each entry's flat position. */
|
|
364
|
-
positions = new Map();
|
|
365
|
-
/** Every frame of the flat list, by id. */
|
|
366
|
-
frames = new Map();
|
|
367
|
-
/** The plan's own walk: what its cursor has entered and decided (AC5). */
|
|
368
|
-
walk = { entered: new Set(), tested: new Set(), passed: new Set(), left: [] };
|
|
336
|
+
/** The plan's own walk: what its cursor has decided (AC5). */
|
|
337
|
+
walk = { passed: new Set(), left: [] };
|
|
369
338
|
constructor(opts) {
|
|
370
339
|
this.scenario = opts.scenario;
|
|
371
340
|
this.scenarioId = opts.scenario.id;
|
|
372
341
|
this.runId = opts.scenario.runId;
|
|
373
342
|
this.intent = opts.scenario.intent ?? "";
|
|
374
|
-
this.
|
|
343
|
+
this.steps = opts.scenario.steps ?? [];
|
|
375
344
|
this.mode = opts.mode;
|
|
376
345
|
this.paramsPromise = opts.params;
|
|
377
346
|
this.fromStep = fromStepKeys(opts.scenario.paramsObject);
|
|
378
347
|
this.fallbackTargets = opts.fallbackTargets ?? "null";
|
|
379
348
|
this.respParams = { ...(opts.respParamsInit ?? {}) };
|
|
380
|
-
// The caller's own frame accumulates into the plan's `respParams`, so
|
|
381
|
-
// `accumulated`, `responseModel` and every depth-0 step see one object.
|
|
382
|
-
if (this.entries[0])
|
|
383
|
-
this.rootFrame().respParams = this.respParams;
|
|
384
349
|
this.subTask = opts.subTask === true;
|
|
385
350
|
const stop = opts.stopAt;
|
|
386
351
|
this.stopAt =
|
|
387
|
-
typeof stop === "number" && stop >= 0 && stop < this.
|
|
352
|
+
typeof stop === "number" && stop >= 0 && stop < this.steps.length
|
|
388
353
|
? stop
|
|
389
|
-
: this.
|
|
354
|
+
: this.steps.length;
|
|
390
355
|
// Judged over the steps this plan will run, as the gate that armed it was.
|
|
391
356
|
const schema = opts.scenario.paramsObject;
|
|
392
357
|
this.oneHalf =
|
|
393
358
|
schema && typeof schema === "object"
|
|
394
|
-
? testedOnlyTargets(Object.keys(schema).filter((k) => schema[k]?.kind !== "setting"), schema, this.
|
|
359
|
+
? testedOnlyTargets(Object.keys(schema).filter((k) => schema[k]?.kind !== "setting"), schema, this.steps.slice(0, this.stopAt), opts.scenario.paramsLogic)
|
|
395
360
|
: [];
|
|
396
|
-
this.edges = frameEdges(this.entries);
|
|
397
|
-
this.entries.forEach((entry, index) => {
|
|
398
|
-
this.positions.set(entry, index);
|
|
399
|
-
for (let f = entry.frame; f; f = f.parent)
|
|
400
|
-
this.frames.set(f.id, f);
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
frameById(id) {
|
|
404
|
-
return this.frames.get(id);
|
|
405
|
-
}
|
|
406
|
-
/** The called frames that begin at flat position `flatIndex`, outermost first. */
|
|
407
|
-
framesStartingAt(flatIndex) {
|
|
408
|
-
return this.edges.startsAt.get(flatIndex) ?? [];
|
|
409
|
-
}
|
|
410
|
-
/** The caller's own frame — the one at depth 0. */
|
|
411
|
-
rootFrame() {
|
|
412
|
-
let frame = this.entries[0].frame;
|
|
413
|
-
while (frame.parent)
|
|
414
|
-
frame = frame.parent;
|
|
415
|
-
return frame;
|
|
416
|
-
}
|
|
417
|
-
/** The parameters one entry's logic runs with (segmented.md R-CALL-29). */
|
|
418
|
-
paramsFor(entry) {
|
|
419
|
-
return entry.depth === 0 ? this.params : (entry.frame.parameters ?? {});
|
|
420
|
-
}
|
|
421
|
-
/** The accumulated values one entry's logic runs with. */
|
|
422
|
-
respFor(entry) {
|
|
423
|
-
return entry.depth === 0 ? this.respParams : entry.frame.respParams;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Entering a called frame: build the segment's parameters from the caller's
|
|
427
|
-
* state (R-CALL-29).
|
|
428
|
-
*
|
|
429
|
-
* The recipe is the dry run's, exactly (9.8 step 10): the mapping's output,
|
|
430
|
-
* plus the segment's own recorded sample for every *setting* the mapping left
|
|
431
|
-
* out. A mapping that omits a setting therefore cannot pass the service's
|
|
432
|
-
* check and then run `undefined` here.
|
|
433
|
-
*/
|
|
434
|
-
enterFramesAt(flatIndex, walk) {
|
|
435
|
-
// Outermost first: a sub-task whose chain begins with a call begins where
|
|
436
|
-
// its callee does, and the callee's mapping reads the sub-task's
|
|
437
|
-
// parameters — built here, before it (AC5 review).
|
|
438
|
-
for (const frame of this.framesStartingAt(flatIndex))
|
|
439
|
-
this.enterFrame(frame, walk);
|
|
440
|
-
}
|
|
441
|
-
enterFrame(frame, walk) {
|
|
442
|
-
if (frame.depth === 0)
|
|
443
|
-
return;
|
|
444
|
-
// Once per walk: a step's test may need the frame's parameters before the
|
|
445
|
-
// step's input is computed (AC5), and a steer cursor computes one step's
|
|
446
|
-
// input again when a pin was declined.
|
|
447
|
-
if (walk.entered.has(frame.id))
|
|
448
|
-
return;
|
|
449
|
-
const resp = walk.resp;
|
|
450
|
-
const parent = frame.parent;
|
|
451
|
-
const parentParams = parent
|
|
452
|
-
? parent.depth === 0
|
|
453
|
-
? this.params
|
|
454
|
-
: (parent.parameters ?? {})
|
|
455
|
-
: this.params;
|
|
456
|
-
const parentResp = parent
|
|
457
|
-
? resp
|
|
458
|
-
? (resp.get(parent.id) ?? parent.respParams)
|
|
459
|
-
: parent.respParams
|
|
460
|
-
: this.respParams;
|
|
461
|
-
const mapped = frame.paramMapLogic
|
|
462
|
-
? evalParamMapLogic(frame.paramMapLogic, parentParams, parent?.intent ?? this.intent, {
|
|
463
|
-
...parentResp,
|
|
464
|
-
})
|
|
465
|
-
: {};
|
|
466
|
-
frame.parameters = { ...settingsOf(frame.paramsObject, mapped), ...mapped };
|
|
467
|
-
if (resp)
|
|
468
|
-
resp.set(frame.id, {});
|
|
469
|
-
else
|
|
470
|
-
frame.respParams = {};
|
|
471
|
-
walk.entered.add(frame.id);
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Leave every called frame that ends at `flatIndex` and that `walk`
|
|
475
|
-
* entered, innermost first: a sub-task whose chain ends with a call is left
|
|
476
|
-
* after its callee has handed it its results.
|
|
477
|
-
*/
|
|
478
|
-
leaveFramesAt(flatIndex, walk) {
|
|
479
|
-
for (const frame of this.edges.endsAt.get(flatIndex) ?? []) {
|
|
480
|
-
if (walk.entered.has(frame.id))
|
|
481
|
-
this.leaveFrame(frame, walk.resp);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* After a skip that ends at flat position `last`: leave every entered
|
|
486
|
-
* sub-task that ends there — its results still go back to its caller. A
|
|
487
|
-
* result mapping that throws ends the walk: its verdict is filed on the
|
|
488
|
-
* call row, in the caller's chain, and the stop says the step at `last` was
|
|
489
|
-
* left out, not run (`result_map`). Rethrown instead when `rethrow`.
|
|
490
|
-
*/
|
|
491
|
-
leaveAfterSkip(last, walk, onStep, rethrow) {
|
|
492
|
-
for (const frame of this.edges.endsAt.get(last) ?? []) {
|
|
493
|
-
if (!walk.entered.has(frame.id))
|
|
494
|
-
continue;
|
|
495
|
-
try {
|
|
496
|
-
this.leaveFrame(frame, walk.resp);
|
|
497
|
-
}
|
|
498
|
-
catch (err) {
|
|
499
|
-
if (rethrow)
|
|
500
|
-
throw err;
|
|
501
|
-
const error = errText(err);
|
|
502
|
-
const { step, entry } = this.callRowOf(frame, this.entries[last]);
|
|
503
|
-
onStep?.({ step, entry, input: {}, outcome: "failed", stage: "tool_output_logic", error, ms: 0 });
|
|
504
|
-
return {
|
|
505
|
-
kind: "step_failed",
|
|
506
|
-
stepIndex: step.stepIndex,
|
|
507
|
-
// At the step left out, which did not run: what ran before it did
|
|
508
|
-
// its work, and what comes after it is left to do.
|
|
509
|
-
flatIndex: last,
|
|
510
|
-
stage: "tool_output_logic",
|
|
511
|
-
error,
|
|
512
|
-
reason: "result_map",
|
|
513
|
-
subTask: frame.callIntent ?? null,
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
return undefined;
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* The call row that opened `frame`, as a step of its caller's chain: where
|
|
521
|
-
* a sub-task's test and its result mapping are filed (R-CALL-13).
|
|
522
|
-
*/
|
|
523
|
-
callRowOf(frame, at) {
|
|
524
|
-
const step = frame.callStep ?? {
|
|
525
|
-
stepIndex: frame.callerStepIndex ?? at.stepIndex,
|
|
526
|
-
kind: "segment",
|
|
527
|
-
toolName: null,
|
|
528
|
-
toolInputLogic: null,
|
|
529
|
-
toolOutputLogic: null,
|
|
530
|
-
reasoning: null,
|
|
531
|
-
};
|
|
532
|
-
const owner = frame.parent ?? frame;
|
|
533
|
-
return {
|
|
534
|
-
step,
|
|
535
|
-
entry: {
|
|
536
|
-
scenarioId: owner.scenarioId,
|
|
537
|
-
runId: owner.runId,
|
|
538
|
-
stepFrom: owner.stepFrom,
|
|
539
|
-
stepTo: owner.stepTo,
|
|
540
|
-
stepIndex: step.stepIndex,
|
|
541
|
-
step,
|
|
542
|
-
frame: owner,
|
|
543
|
-
depth: owner.depth,
|
|
544
|
-
first: false,
|
|
545
|
-
last: false,
|
|
546
|
-
},
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Leaving a called frame: hand what the segment emitted back to its caller
|
|
551
|
-
* (R-CALL-29). `out` is the frame's own accumulated `respParams`.
|
|
552
|
-
*/
|
|
553
|
-
leaveFrame(frame, resp) {
|
|
554
|
-
if (frame.depth === 0)
|
|
555
|
-
return;
|
|
556
|
-
const parent = frame.parent;
|
|
557
|
-
if (!frame.resultMapLogic || !parent)
|
|
558
|
-
return;
|
|
559
|
-
const parentParams = parent.depth === 0 ? this.params : (parent.parameters ?? {});
|
|
560
|
-
const target = resp
|
|
561
|
-
? (resp.get(parent.id) ?? parent.respParams)
|
|
562
|
-
: parent.depth === 0
|
|
563
|
-
? this.respParams
|
|
564
|
-
: parent.respParams;
|
|
565
|
-
const own = resp ? (resp.get(frame.id) ?? frame.respParams) : frame.respParams;
|
|
566
|
-
const out = evalResultMapLogic(frame.resultMapLogic, { ...own }, parentParams, parent.intent, { ...target });
|
|
567
|
-
Object.assign(target, out);
|
|
568
361
|
}
|
|
569
362
|
/**
|
|
570
363
|
* Await the derivation, then apply `paramsLogic` — once, however many callers
|
|
@@ -684,37 +477,17 @@ export class ScenarioReplayPlan {
|
|
|
684
477
|
get stepCount() {
|
|
685
478
|
return this.stopAt;
|
|
686
479
|
}
|
|
687
|
-
/** Every step
|
|
480
|
+
/** Every step in the scenario's chain, planned or not. */
|
|
688
481
|
get totalSteps() {
|
|
689
|
-
return this.
|
|
482
|
+
return this.steps.length;
|
|
690
483
|
}
|
|
691
484
|
/** True when the plan ends in front of a known-bad step (fallbk.md D3). */
|
|
692
485
|
stopsEarly() {
|
|
693
|
-
return this.stopAt < this.
|
|
486
|
+
return this.stopAt < this.steps.length;
|
|
694
487
|
}
|
|
695
488
|
/** The known-bad step the plan stops in front of, if any. */
|
|
696
489
|
stopStep() {
|
|
697
|
-
return this.stopsEarly() ? this.
|
|
698
|
-
}
|
|
699
|
-
/** The entry the plan stops in front of, with the frame that owns it. */
|
|
700
|
-
stopEntry() {
|
|
701
|
-
return this.stopsEarly() ? this.entries[this.stopAt] : undefined;
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* The flat position of the chain's decision stop (the service's
|
|
705
|
-
* segmentsbreakdown.md SB-18), or -1 when it has none. The steps from there
|
|
706
|
-
* on are the half the recording took, whatever stops the plan first.
|
|
707
|
-
*/
|
|
708
|
-
decisionIndex() {
|
|
709
|
-
return this.entries.findIndex((e) => e.step.stop?.kind === "decision");
|
|
710
|
-
}
|
|
711
|
-
/** Every entry in order — for the report, and for the owning scenario ids. */
|
|
712
|
-
allEntries() {
|
|
713
|
-
return this.entries;
|
|
714
|
-
}
|
|
715
|
-
/** The entry awaiting execution, or undefined when the plan is done. */
|
|
716
|
-
currentEntry() {
|
|
717
|
-
return this.stepIndex < this.stopAt ? this.entries[this.stepIndex] : undefined;
|
|
490
|
+
return this.stopsEarly() ? this.steps[this.stopAt] : undefined;
|
|
718
491
|
}
|
|
719
492
|
/** The parameters in force. Empty until {@link ready} resolves. */
|
|
720
493
|
get parameters() {
|
|
@@ -772,19 +545,19 @@ export class ScenarioReplayPlan {
|
|
|
772
545
|
}
|
|
773
546
|
/** The step awaiting execution, or undefined when the plan is done. */
|
|
774
547
|
currentStep() {
|
|
775
|
-
return this.
|
|
548
|
+
return this.stepIndex < this.stopAt ? this.steps[this.stepIndex] : undefined;
|
|
776
549
|
}
|
|
777
550
|
/** The tool the current step expects, or undefined when done. */
|
|
778
551
|
expectedTool() {
|
|
779
|
-
return this.currentStep()?.toolName
|
|
552
|
+
return this.currentStep()?.toolName;
|
|
780
553
|
}
|
|
781
554
|
/** True once every planned step has been applied — the plan should be retired. */
|
|
782
555
|
isDone() {
|
|
783
556
|
return this.stepIndex >= this.stopAt;
|
|
784
557
|
}
|
|
785
|
-
/** Every step in order — for the directive, and
|
|
558
|
+
/** Every step in order — for the directive, the report and coverage reporting. */
|
|
786
559
|
allSteps() {
|
|
787
|
-
return this.
|
|
560
|
+
return this.steps;
|
|
788
561
|
}
|
|
789
562
|
/**
|
|
790
563
|
* True when `toolName` is one of this scenario's own tools.
|
|
@@ -796,7 +569,7 @@ export class ScenarioReplayPlan {
|
|
|
796
569
|
* abandoned the sequence.
|
|
797
570
|
*/
|
|
798
571
|
usesTool(toolName) {
|
|
799
|
-
return this.
|
|
572
|
+
return this.steps.some((s) => s.toolName === toolName);
|
|
800
573
|
}
|
|
801
574
|
/**
|
|
802
575
|
* Compute the pinned input for the current step. Throws when there is no
|
|
@@ -804,14 +577,10 @@ export class ScenarioReplayPlan {
|
|
|
804
577
|
* a throw as divergence.
|
|
805
578
|
*/
|
|
806
579
|
toolInputForCurrentStep() {
|
|
807
|
-
const
|
|
808
|
-
if (!
|
|
580
|
+
const step = this.steps[this.stepIndex];
|
|
581
|
+
if (!step)
|
|
809
582
|
throw new Error("no current step to compute input for");
|
|
810
|
-
|
|
811
|
-
// the first moment the caller's own accumulation is final (R-CALL-29) —
|
|
812
|
-
// unless the step's test needed the frame first (AC5).
|
|
813
|
-
this.enterFramesAt(this.stepIndex, this.walk);
|
|
814
|
-
return this.inputOf(entry, this.paramsFor(entry), entry.frame.intent, this.respFor(entry));
|
|
583
|
+
return this.inputOf(step, this.params, this.intent, this.respParams);
|
|
815
584
|
}
|
|
816
585
|
/**
|
|
817
586
|
* A step's input, refused when it holds a value only the recording had: its
|
|
@@ -820,9 +589,9 @@ export class ScenarioReplayPlan {
|
|
|
820
589
|
* is one that reads, or holds, a value the request did not give (review
|
|
821
590
|
* item 11): {@link MissingValueError}.
|
|
822
591
|
*/
|
|
823
|
-
inputOf(
|
|
592
|
+
inputOf(step, params, intent, resp, opts) {
|
|
824
593
|
const w = this.watched(params);
|
|
825
|
-
const input = evalToolInputLogic(
|
|
594
|
+
const input = evalToolInputLogic(step.toolInputLogic, w.params, intent, resp, opts);
|
|
826
595
|
const read = [...w.read][0];
|
|
827
596
|
if (read !== undefined)
|
|
828
597
|
throw this.valueError(read);
|
|
@@ -836,41 +605,28 @@ export class ScenarioReplayPlan {
|
|
|
836
605
|
// A step may run only when its test says so: a JavaScript body over the
|
|
837
606
|
// parameters, the intent and what the earlier steps accumulated, decided
|
|
838
607
|
// when the step comes due — never sooner, because the accumulation it reads
|
|
839
|
-
// is final only then.
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
//
|
|
843
|
-
// anything but true or false stops it too. Nothing is ever guessed.
|
|
608
|
+
// is final only then. `skip` leaves the step out; `hand_over` stops the plan
|
|
609
|
+
// in front of it (the other half of an "if … otherwise", which the agent
|
|
610
|
+
// does); a test that throws or answers anything but true or false stops it
|
|
611
|
+
// too. Nothing is ever guessed.
|
|
844
612
|
/** Whether the plan's cursor rests in front of the step it is parked before. */
|
|
845
613
|
atStop() {
|
|
846
614
|
return this.stopsEarly() && this.stepIndex === this.stopAt;
|
|
847
615
|
}
|
|
848
|
-
/**
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
*/
|
|
853
|
-
onlyWhen(entry) {
|
|
854
|
-
const own = entry.step.when;
|
|
855
|
-
if (own)
|
|
856
|
-
return { text: testWords(own.text) };
|
|
857
|
-
const at = this.positions.get(entry);
|
|
858
|
-
const call = at === undefined ? undefined : this.framesStartingAt(at).find((f) => f.when)?.when;
|
|
859
|
-
if (call)
|
|
860
|
-
return { text: testWords(call.text) };
|
|
861
|
-
return undefined;
|
|
616
|
+
/** The test a step runs under, in words, when it has one. */
|
|
617
|
+
onlyWhen(step) {
|
|
618
|
+
const own = step.when;
|
|
619
|
+
return own ? { text: testWords(own.text) } : undefined;
|
|
862
620
|
}
|
|
863
621
|
/**
|
|
864
|
-
* The tool names of the steps from
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
* this request needs.
|
|
622
|
+
* The tool names of the steps from position `from` to the end, each that
|
|
623
|
+
* runs only under a test marked so — for a hand-over note's "remaining
|
|
624
|
+
* planned steps", which must not read as steps this request needs.
|
|
868
625
|
*/
|
|
869
|
-
remainingFrom(from
|
|
870
|
-
return this.
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
return t ? `${name} (only when ${t.text ?? "a request needs it"})` : name;
|
|
626
|
+
remainingFrom(from) {
|
|
627
|
+
return this.steps.slice(Math.max(0, from)).map((s) => {
|
|
628
|
+
const t = this.onlyWhen(s);
|
|
629
|
+
return t ? `${s.toolName} (only when ${t.text ?? "a request needs it"})` : s.toolName;
|
|
874
630
|
});
|
|
875
631
|
}
|
|
876
632
|
/** The steps the plan's own cursor has left out so far, in order (AC5). */
|
|
@@ -879,7 +635,7 @@ export class ScenarioReplayPlan {
|
|
|
879
635
|
}
|
|
880
636
|
/** Whether any planned step runs only under a test. */
|
|
881
637
|
hasTests() {
|
|
882
|
-
return this.
|
|
638
|
+
return this.steps.slice(0, this.stopAt).some((s) => this.onlyWhen(s) !== undefined);
|
|
883
639
|
}
|
|
884
640
|
/** One test, answered — or the reason it could not be. Never throws. */
|
|
885
641
|
answer(test, params, intent, resp) {
|
|
@@ -898,180 +654,72 @@ export class ScenarioReplayPlan {
|
|
|
898
654
|
}
|
|
899
655
|
}
|
|
900
656
|
/**
|
|
901
|
-
* What the
|
|
902
|
-
*
|
|
903
|
-
* nothing else changes: the caller moves its cursor.
|
|
657
|
+
* What the step's test decides at `position` for `walk`. Nothing changes:
|
|
658
|
+
* the caller moves its cursor.
|
|
904
659
|
*/
|
|
905
|
-
gate(
|
|
906
|
-
const
|
|
907
|
-
if (!
|
|
660
|
+
gate(position, walk) {
|
|
661
|
+
const step = this.steps[position];
|
|
662
|
+
if (!step)
|
|
908
663
|
return { kind: "run" };
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
// 1. The sub-tasks that begin here, outermost first — one whose chain
|
|
912
|
-
// begins with a call begins where its callee's steps do, so nothing of
|
|
913
|
-
// it runs before its test says it is needed. Each test is decided
|
|
914
|
-
// once, in its caller's scope, before its frame is entered; a caller
|
|
915
|
-
// that begins here too is entered first, for its parameters.
|
|
916
|
-
const starting = this.framesStartingAt(flatIndex);
|
|
917
|
-
for (let k = 0; k < starting.length; k += 1) {
|
|
918
|
-
const sub = starting[k];
|
|
919
|
-
if (!sub.when || walk.tested.has(sub.id))
|
|
920
|
-
continue;
|
|
921
|
-
try {
|
|
922
|
-
for (const outer of starting.slice(0, k))
|
|
923
|
-
this.enterFrame(outer, walk);
|
|
924
|
-
}
|
|
925
|
-
catch (err) {
|
|
926
|
-
return {
|
|
927
|
-
kind: "stop",
|
|
928
|
-
stop: { kind: "step_failed", stepIndex: entry.stepIndex, flatIndex, stage: "tool_input_logic", error: errText(err) },
|
|
929
|
-
info: { step: entry.step, entry, input: {}, outcome: "failed", stage: "tool_input_logic", error: errText(err), ms: 0 },
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
const decided = this.decideCall(sub, entry, flatIndex, walk);
|
|
933
|
-
if (decided)
|
|
934
|
-
return decided;
|
|
935
|
-
walk.tested.add(sub.id);
|
|
936
|
-
}
|
|
937
|
-
// 2. The step's own test, in its own frame's scope.
|
|
938
|
-
const test = entry.step.when;
|
|
939
|
-
if (!test || walk.passed.has(flatIndex))
|
|
664
|
+
const test = step.when;
|
|
665
|
+
if (!test || walk.passed.has(position))
|
|
940
666
|
return { kind: "run" };
|
|
941
|
-
|
|
942
|
-
// Its frame's parameters, which a test inside a sub-task reads.
|
|
943
|
-
this.enterFramesAt(flatIndex, walk);
|
|
944
|
-
}
|
|
945
|
-
catch (err) {
|
|
946
|
-
return {
|
|
947
|
-
kind: "stop",
|
|
948
|
-
stop: { kind: "step_failed", stepIndex: entry.stepIndex, flatIndex, stage: "tool_input_logic", error: errText(err) },
|
|
949
|
-
info: { step: entry.step, entry, input: {}, outcome: "failed", stage: "tool_input_logic", error: errText(err), ms: 0 },
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
const own = resp ? (resp.get(frame.id) ?? {}) : this.respFor(entry);
|
|
953
|
-
const said = this.answer(test, this.paramsFor(entry), frame.intent, own);
|
|
667
|
+
const said = this.answer(test, this.params, this.intent, walk.resp ?? this.respParams);
|
|
954
668
|
const text = testWords(test.text);
|
|
955
669
|
if ("error" in said) {
|
|
956
670
|
return {
|
|
957
671
|
kind: "stop",
|
|
958
672
|
stop: {
|
|
959
673
|
kind: "step_failed",
|
|
960
|
-
stepIndex:
|
|
961
|
-
|
|
674
|
+
stepIndex: step.stepIndex,
|
|
675
|
+
position,
|
|
962
676
|
stage: "when_logic",
|
|
963
677
|
error: said.error,
|
|
964
678
|
reason: "test_failed",
|
|
965
679
|
},
|
|
966
|
-
info: { step
|
|
680
|
+
info: { step, input: {}, outcome: "failed", stage: "when_logic", error: said.error, ms: 0 },
|
|
967
681
|
};
|
|
968
682
|
}
|
|
969
683
|
if (said.yes) {
|
|
970
|
-
walk.passed.add(
|
|
684
|
+
walk.passed.add(position);
|
|
971
685
|
return { kind: "run" };
|
|
972
686
|
}
|
|
973
687
|
if (test.otherwise === "skip") {
|
|
974
688
|
return {
|
|
975
689
|
kind: "skip",
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
entry,
|
|
979
|
-
text,
|
|
980
|
-
tools: [entry.step.toolName ?? ""],
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
return { kind: "stop", stop: { kind: "other_branch", stepIndex: entry.stepIndex, flatIndex, whenText: text } };
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* A sub-task's test, decided in its caller's scope at flat position
|
|
987
|
-
* `flatIndex`, where the sub-task begins. Undefined when it says yes.
|
|
988
|
-
*/
|
|
989
|
-
decideCall(sub, entry, flatIndex, walk) {
|
|
990
|
-
const resp = walk.resp;
|
|
991
|
-
const parent = sub.parent;
|
|
992
|
-
const params = !parent || parent.depth === 0 ? this.params : (parent.parameters ?? {});
|
|
993
|
-
const parentResp = parent
|
|
994
|
-
? resp
|
|
995
|
-
? (resp.get(parent.id) ?? (parent.depth === 0 ? this.respParams : parent.respParams))
|
|
996
|
-
: parent.depth === 0
|
|
997
|
-
? this.respParams
|
|
998
|
-
: parent.respParams
|
|
999
|
-
: this.respParams;
|
|
1000
|
-
// The call row, as a step of its caller's chain: its verdict is filed
|
|
1001
|
-
// there, under the call's own index.
|
|
1002
|
-
const call = this.callRowOf(sub, entry);
|
|
1003
|
-
const owner = parent ?? sub;
|
|
1004
|
-
const said = this.answer(sub.when, params, owner.intent ?? this.intent, parentResp);
|
|
1005
|
-
const text = testWords(sub.when.text);
|
|
1006
|
-
if ("error" in said) {
|
|
1007
|
-
return {
|
|
1008
|
-
kind: "stop",
|
|
1009
|
-
stop: {
|
|
1010
|
-
kind: "step_failed",
|
|
1011
|
-
stepIndex: call.step.stepIndex,
|
|
1012
|
-
flatIndex,
|
|
1013
|
-
stage: "when_logic",
|
|
1014
|
-
error: said.error,
|
|
1015
|
-
reason: "test_failed",
|
|
1016
|
-
},
|
|
1017
|
-
info: { step: call.step, entry: call.entry, input: {}, outcome: "failed", stage: "when_logic", error: said.error, ms: 0 },
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
if (said.yes)
|
|
1021
|
-
return undefined;
|
|
1022
|
-
if (sub.when.otherwise === "skip") {
|
|
1023
|
-
const until = (this.edges.end.get(sub.id) ?? flatIndex) + 1;
|
|
1024
|
-
return {
|
|
1025
|
-
kind: "skip",
|
|
1026
|
-
until,
|
|
1027
|
-
info: { step: call.step, entry: call.entry, input: {}, outcome: "not_taken", ms: 0 },
|
|
1028
|
-
entry: call.entry,
|
|
690
|
+
info: { step, input: {}, outcome: "not_taken", ms: 0 },
|
|
691
|
+
step,
|
|
1029
692
|
text,
|
|
1030
|
-
// Every tool of the sub-task: a call the model makes to any of them is
|
|
1031
|
-
// one to a step left out.
|
|
1032
|
-
tools: [...new Set(this.entries.slice(flatIndex, until).map((e) => e.step.toolName ?? ""))],
|
|
1033
693
|
};
|
|
1034
694
|
}
|
|
1035
|
-
return {
|
|
1036
|
-
kind: "stop",
|
|
1037
|
-
stop: { kind: "other_branch", stepIndex: call.step.stepIndex, flatIndex, whenText: text },
|
|
1038
|
-
};
|
|
695
|
+
return { kind: "stop", stop: { kind: "other_branch", stepIndex: step.stepIndex, position, whenText: text } };
|
|
1039
696
|
}
|
|
1040
697
|
/** What a skip the gate decided leaves out, as the notes name it. */
|
|
1041
|
-
leftOutOf(g,
|
|
698
|
+
leftOutOf(g, position) {
|
|
1042
699
|
return {
|
|
1043
|
-
stepIndex: g.
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
toolName: g.entry.step.toolName ?? g.entry.step.callIntent ?? "a sub-task",
|
|
1047
|
-
tools: g.tools.filter((t) => t !== ""),
|
|
700
|
+
stepIndex: g.step.stepIndex,
|
|
701
|
+
position,
|
|
702
|
+
toolName: g.step.toolName,
|
|
1048
703
|
text: g.text,
|
|
1049
704
|
};
|
|
1050
705
|
}
|
|
1051
706
|
/**
|
|
1052
|
-
* Tell a recorded-output source of
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
707
|
+
* Tell a recorded-output source of a step a test left out, so its queues
|
|
708
|
+
* pass over that step's output when it was in the recording
|
|
709
|
+
* ({@link RecordedOutputFor}).
|
|
1055
710
|
*/
|
|
1056
711
|
passOver(l, recordedOutputFor) {
|
|
1057
|
-
const
|
|
1058
|
-
if (
|
|
1059
|
-
|
|
1060
|
-
for (let i = l.flatIndex; i < l.until; i += 1) {
|
|
1061
|
-
const entry = this.entries[i];
|
|
1062
|
-
if (entry)
|
|
1063
|
-
tell(entry);
|
|
1064
|
-
}
|
|
712
|
+
const step = this.steps[l.position];
|
|
713
|
+
if (step)
|
|
714
|
+
recordedOutputFor?.leftOut?.(step);
|
|
1065
715
|
}
|
|
1066
716
|
/**
|
|
1067
717
|
* Move the plan's own cursor past every step this request does not need,
|
|
1068
718
|
* deciding each test as its step comes due (AC5). Stops at a step that will
|
|
1069
719
|
* run, at the end, or at a test that stops the plan — which it returns, with
|
|
1070
720
|
* the steps it left out. The observer is told each verdict as it is made.
|
|
1071
|
-
* A sub-task whose results cannot go back after its last step was left out
|
|
1072
|
-
* stops the plan too (`result_map`), its verdict filed on the call row.
|
|
1073
721
|
*/
|
|
1074
|
-
settleTests(onStep
|
|
722
|
+
settleTests(onStep) {
|
|
1075
723
|
const left = [];
|
|
1076
724
|
while (this.stepIndex < this.stopAt) {
|
|
1077
725
|
const g = this.gate(this.stepIndex, this.walk);
|
|
@@ -1086,10 +734,7 @@ export class ScenarioReplayPlan {
|
|
|
1086
734
|
const l = this.leftOutOf(g, this.stepIndex);
|
|
1087
735
|
left.push(l);
|
|
1088
736
|
this.walk.left.push(l);
|
|
1089
|
-
|
|
1090
|
-
if (failed)
|
|
1091
|
-
return { stop: failed, left };
|
|
1092
|
-
this.stepIndex = g.until;
|
|
737
|
+
this.stepIndex += 1;
|
|
1093
738
|
}
|
|
1094
739
|
return { left };
|
|
1095
740
|
}
|
|
@@ -1102,20 +747,16 @@ export class ScenarioReplayPlan {
|
|
|
1102
747
|
* Throws when `toolOutputLogic` fails; the caller aborts to a normal turn.
|
|
1103
748
|
*/
|
|
1104
749
|
applyOutput(realOutput) {
|
|
1105
|
-
const
|
|
1106
|
-
if (!
|
|
750
|
+
const step = this.steps[this.stepIndex];
|
|
751
|
+
if (!step)
|
|
1107
752
|
return [];
|
|
1108
753
|
let derivedKeys = [];
|
|
1109
|
-
if (
|
|
1110
|
-
const derived = evalToolOutputLogic(
|
|
754
|
+
if (step.toolOutputLogic) {
|
|
755
|
+
const derived = evalToolOutputLogic(step.toolOutputLogic, realOutput, this.params, this.intent, this.respParams);
|
|
1111
756
|
derivedKeys = Object.keys(derived);
|
|
1112
|
-
// Mutated, not replaced:
|
|
1113
|
-
|
|
1114
|
-
Object.assign(this.respFor(entry), derived);
|
|
757
|
+
// Mutated, not replaced: `accumulated` hands out this very object.
|
|
758
|
+
Object.assign(this.respParams, derived);
|
|
1115
759
|
}
|
|
1116
|
-
// Leaving a called frame hands its results back to the caller before the
|
|
1117
|
-
// caller's next step reads them (R-CALL-29).
|
|
1118
|
-
this.leaveFramesAt(this.stepIndex, this.walk);
|
|
1119
760
|
this.stepIndex += 1;
|
|
1120
761
|
return derivedKeys;
|
|
1121
762
|
}
|
|
@@ -1145,12 +786,11 @@ export class ScenarioReplayPlan {
|
|
|
1145
786
|
* The arguments each planned step will run with, where they are known before
|
|
1146
787
|
* any step runs — for the steer directive (see {@link steeringDirective}).
|
|
1147
788
|
*
|
|
1148
|
-
* Known means: the parameters are settled, the step
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
-
*
|
|
1153
|
-
* those steps' arguments to the system.
|
|
789
|
+
* Known means: the parameters are settled, and the step's input logic
|
|
790
|
+
* neither reads nor passes on anything an earlier step accumulates — or it
|
|
791
|
+
* is the step at the cursor, whose accumulation is already final.
|
|
792
|
+
* Everything else is undefined, and the directive leaves those steps'
|
|
793
|
+
* arguments to the system.
|
|
1154
794
|
*
|
|
1155
795
|
* A look-ahead only: every body runs on copies, under a short limit, and a
|
|
1156
796
|
* body that throws or runs long just leaves its step without arguments —
|
|
@@ -1175,9 +815,7 @@ export class ScenarioReplayPlan {
|
|
|
1175
815
|
const left = deadline - Date.now();
|
|
1176
816
|
if (left < MIN_PREVIEW_BODY_MS)
|
|
1177
817
|
break;
|
|
1178
|
-
const
|
|
1179
|
-
if (entry.depth !== 0)
|
|
1180
|
-
continue;
|
|
818
|
+
const step = planned[i];
|
|
1181
819
|
const params = copyOf(this.params);
|
|
1182
820
|
const accumulated = copyOf(this.respParams);
|
|
1183
821
|
if (!params || !accumulated)
|
|
@@ -1190,7 +828,7 @@ export class ScenarioReplayPlan {
|
|
|
1190
828
|
});
|
|
1191
829
|
let input;
|
|
1192
830
|
try {
|
|
1193
|
-
input = this.inputOf(
|
|
831
|
+
input = this.inputOf(step, params, this.intent, resp, { timeoutMs: Math.min(bodyMs, left) });
|
|
1194
832
|
}
|
|
1195
833
|
catch (err) {
|
|
1196
834
|
// A body that throws (often on an earlier result it does not have
|
|
@@ -1207,7 +845,7 @@ export class ScenarioReplayPlan {
|
|
|
1207
845
|
try {
|
|
1208
846
|
if (readsEarlier || (resp !== accumulated && holds(input, resp)))
|
|
1209
847
|
continue;
|
|
1210
|
-
out[i] = renderArguments(
|
|
848
|
+
out[i] = renderArguments(step.toolName, input);
|
|
1211
849
|
}
|
|
1212
850
|
catch {
|
|
1213
851
|
// A value that throws when read (a getter the body returned): nothing
|
|
@@ -1239,8 +877,8 @@ export class ScenarioReplayPlan {
|
|
|
1239
877
|
* {@link MIN_PREVIEW_BODY_MS} there is no preview at all.
|
|
1240
878
|
*/
|
|
1241
879
|
steeringDirective(directToolName = "mcp__bir__run_scenario", opts) {
|
|
1242
|
-
// A sub-task plan (
|
|
1243
|
-
//
|
|
880
|
+
// A sub-task plan (armed mid-task) never tells the agent to stop calling
|
|
881
|
+
// tools: the steps below are a part of the task it is already doing.
|
|
1244
882
|
const subTask = opts?.subTask ?? this.subTask;
|
|
1245
883
|
// The intent is a paragraph the analyser wrote; it is context, not an
|
|
1246
884
|
// instruction, so it is clipped rather than dumped whole into the prompt.
|
|
@@ -1250,11 +888,11 @@ export class ScenarioReplayPlan {
|
|
|
1250
888
|
// paragraph: its first line, clipped. A chain long enough that even the
|
|
1251
889
|
// labels would run to kilobytes lists its tools alone — every tool always,
|
|
1252
890
|
// because in steer mode the list *is* the instruction.
|
|
1253
|
-
const planned = this.
|
|
891
|
+
const planned = this.steps.slice(0, this.stopAt);
|
|
1254
892
|
const args = this.previewArguments(planned, opts?.previewBudgetMs, opts?.previewBodyMs);
|
|
1255
893
|
// A step that runs only under a test says so on its line (AC5): which of
|
|
1256
894
|
// them this request needs is known only as the steps run.
|
|
1257
|
-
const tests = planned.map((
|
|
895
|
+
const tests = planned.map((s) => this.onlyWhen(s));
|
|
1258
896
|
const anyTest = tests.some((t) => t !== undefined);
|
|
1259
897
|
const onlyWhen = (i) => {
|
|
1260
898
|
const t = tests[i];
|
|
@@ -1262,9 +900,9 @@ export class ScenarioReplayPlan {
|
|
|
1262
900
|
return "";
|
|
1263
901
|
return ` (only when ${t.text ?? "this request needs it"})`;
|
|
1264
902
|
};
|
|
1265
|
-
const listing = (withWhy, argsBefore) => planned.map((
|
|
1266
|
-
const why = withWhy ? clipReasoning(
|
|
1267
|
-
const head = ` ${i + 1}. ${
|
|
903
|
+
const listing = (withWhy, argsBefore) => planned.map((s, i) => {
|
|
904
|
+
const why = withWhy ? clipReasoning(s.reasoning) : "";
|
|
905
|
+
const head = ` ${i + 1}. ${s.toolName}${onlyWhen(i)}${why ? ` — ${why}` : ""}`;
|
|
1268
906
|
const shown = i < argsBefore ? args[i] : undefined;
|
|
1269
907
|
return shown ? `${head}\n ${shown}` : head;
|
|
1270
908
|
});
|
|
@@ -1288,21 +926,10 @@ export class ScenarioReplayPlan {
|
|
|
1288
926
|
break;
|
|
1289
927
|
}
|
|
1290
928
|
const anyArgs = args.slice(0, argsShown).some((a) => a !== undefined);
|
|
1291
|
-
// A plan whose chain holds a decision (the service's segmentsbreakdown.md
|
|
1292
|
-
// SB-18) never shows its intent, even when it stops earlier for another
|
|
1293
|
-
// reason: the intent names the recorded task, the half the recording took
|
|
1294
|
-
// included, and which way applies is the agent's choice.
|
|
1295
|
-
const atDecision = this.stopStep()?.stop?.kind === "decision";
|
|
1296
|
-
const shownIntent = this.decisionIndex() >= 0 ? "" : intent;
|
|
1297
929
|
// A parked step (fallbk.md D3): the model is told up front that the plan is
|
|
1298
930
|
// partial, so the hand-over that follows is expected rather than a surprise.
|
|
1299
931
|
const tail = this.stopsEarly()
|
|
1300
|
-
? [
|
|
1301
|
-
"",
|
|
1302
|
-
atDecision
|
|
1303
|
-
? "The system will then hand the task back to you at a decision."
|
|
1304
|
-
: "The system will then hand the rest of the task back to you.",
|
|
1305
|
-
]
|
|
932
|
+
? ["", "The system will then hand the rest of the task back to you."]
|
|
1306
933
|
: [];
|
|
1307
934
|
if (this.mode === "direct") {
|
|
1308
935
|
// The sequence is listed even though the model does not call it itself:
|
|
@@ -1311,7 +938,7 @@ export class ScenarioReplayPlan {
|
|
|
1311
938
|
// place an operator watching the session can see what a replay is doing.
|
|
1312
939
|
return [
|
|
1313
940
|
"[BaseInstRunner calculated replay] A known-good tool sequence was recorded",
|
|
1314
|
-
`for this request${
|
|
941
|
+
`for this request${intent ? ` (intent: ${intent})` : ""}.`,
|
|
1315
942
|
"",
|
|
1316
943
|
anyTest
|
|
1317
944
|
? 'It runs these tools, in order, and returns their results. A step marked "only when" runs only when this request needs it, and the results say which ones did not:'
|
|
@@ -1333,7 +960,7 @@ export class ScenarioReplayPlan {
|
|
|
1333
960
|
"recorded for this request. Call exactly these tools, in order, one at a time:",
|
|
1334
961
|
"",
|
|
1335
962
|
...lines,
|
|
1336
|
-
...(
|
|
963
|
+
...(intent ? ["", `Context: ${intent}`] : []),
|
|
1337
964
|
...(anyTest
|
|
1338
965
|
? [
|
|
1339
966
|
"",
|
|
@@ -1381,26 +1008,13 @@ export class ScenarioReplayPlan {
|
|
|
1381
1008
|
* waiting is a side effect nobody is told about.
|
|
1382
1009
|
*/
|
|
1383
1010
|
async composeBundle(maxChars, execute, recordedOutputFor, onStep, opts = {}) {
|
|
1384
|
-
//
|
|
1385
|
-
//
|
|
1386
|
-
const resp =
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
// A sub-task the plan's cursor is inside goes on from what it has made so
|
|
1390
|
-
// far: its copy, and the frames and tests the cursor already settled. A
|
|
1391
|
-
// bundle that started such a frame afresh read `{}` for it — its steps'
|
|
1392
|
-
// outputs were lost, and a test there read nothing (AC5 review).
|
|
1393
|
-
for (const id of this.walk.entered) {
|
|
1394
|
-
const frame = this.frameById(id);
|
|
1395
|
-
if (frame)
|
|
1396
|
-
resp.set(id, { ...frame.respParams });
|
|
1397
|
-
}
|
|
1398
|
-
// Its own walk, on those copies: what it enters and decides is not the
|
|
1399
|
-
// live plan's either (AC5).
|
|
1011
|
+
// A copy: composing must not corrupt the live plan's accumulation if the
|
|
1012
|
+
// caller decides to keep steering afterwards.
|
|
1013
|
+
const resp = { ...this.respParams };
|
|
1014
|
+
// Its own walk, on that copy, from the tests the cursor already settled:
|
|
1015
|
+
// what it decides is not the live plan's either (AC5).
|
|
1400
1016
|
const walk = {
|
|
1401
1017
|
resp,
|
|
1402
|
-
entered: new Set(this.walk.entered),
|
|
1403
|
-
tested: new Set(this.walk.tested),
|
|
1404
1018
|
passed: new Set(this.walk.passed),
|
|
1405
1019
|
left: [],
|
|
1406
1020
|
};
|
|
@@ -1412,13 +1026,12 @@ export class ScenarioReplayPlan {
|
|
|
1412
1026
|
let notTaken = 0;
|
|
1413
1027
|
let stopped;
|
|
1414
1028
|
const stopOnFailure = opts.stopOnFailure === true;
|
|
1415
|
-
let
|
|
1416
|
-
for (;
|
|
1417
|
-
const
|
|
1418
|
-
const step = entry.step;
|
|
1029
|
+
let position = this.stepIndex;
|
|
1030
|
+
for (; position < this.stopAt; position += 1) {
|
|
1031
|
+
const step = this.steps[position];
|
|
1419
1032
|
// A step this request does not need is left out, and said so in the
|
|
1420
1033
|
// bundle; a test that says "the other half" stops the bundle here (AC5).
|
|
1421
|
-
const g = this.gate(
|
|
1034
|
+
const g = this.gate(position, walk);
|
|
1422
1035
|
if (g.kind === "stop") {
|
|
1423
1036
|
if (g.info)
|
|
1424
1037
|
onStep?.(g.info);
|
|
@@ -1428,26 +1041,18 @@ export class ScenarioReplayPlan {
|
|
|
1428
1041
|
if (g.kind === "skip") {
|
|
1429
1042
|
onStep?.(g.info);
|
|
1430
1043
|
notTaken += 1;
|
|
1431
|
-
const l = this.leftOutOf(g,
|
|
1044
|
+
const l = this.leftOutOf(g, position);
|
|
1432
1045
|
walk.left.push(l);
|
|
1433
1046
|
this.passOver(l, recordedOutputFor);
|
|
1434
1047
|
entries.push({
|
|
1435
|
-
toolName:
|
|
1048
|
+
toolName: step.toolName,
|
|
1436
1049
|
input: "",
|
|
1437
1050
|
response: "",
|
|
1438
1051
|
notNeeded: { text: g.text },
|
|
1439
1052
|
});
|
|
1440
|
-
// The sub-tasks that end with the step left out still hand back what
|
|
1441
|
-
// they made; one that cannot stops the bundle (`result_map`).
|
|
1442
|
-
const failed = this.leaveAfterSkip(g.until - 1, walk, onStep, !stopOnFailure);
|
|
1443
|
-
if (failed) {
|
|
1444
|
-
stopped = failed;
|
|
1445
|
-
break;
|
|
1446
|
-
}
|
|
1447
|
-
flatIndex = g.until - 1;
|
|
1448
1053
|
continue;
|
|
1449
1054
|
}
|
|
1450
|
-
const before = this.stopBefore(
|
|
1055
|
+
const before = this.stopBefore(step, position, opts.signal, opts.deadline);
|
|
1451
1056
|
if (before) {
|
|
1452
1057
|
stopped = before;
|
|
1453
1058
|
break;
|
|
@@ -1455,11 +1060,7 @@ export class ScenarioReplayPlan {
|
|
|
1455
1060
|
const startedAt = Date.now();
|
|
1456
1061
|
let computed;
|
|
1457
1062
|
try {
|
|
1458
|
-
computed = reportingStage(onStep,
|
|
1459
|
-
this.enterFramesAt(flatIndex, walk);
|
|
1460
|
-
const respParams = resp.get(entry.frame.id) ?? {};
|
|
1461
|
-
return this.inputOf(entry, this.paramsFor(entry), entry.frame.intent, respParams);
|
|
1462
|
-
});
|
|
1063
|
+
computed = reportingStage(onStep, step, "tool_input_logic", {}, startedAt, () => this.inputOf(step, this.params, this.intent, resp));
|
|
1463
1064
|
}
|
|
1464
1065
|
catch (err) {
|
|
1465
1066
|
if (!stopOnFailure)
|
|
@@ -1467,14 +1068,14 @@ export class ScenarioReplayPlan {
|
|
|
1467
1068
|
stopped = {
|
|
1468
1069
|
kind: "step_failed",
|
|
1469
1070
|
stepIndex: step.stepIndex,
|
|
1470
|
-
|
|
1071
|
+
position,
|
|
1471
1072
|
stage: "tool_input_logic",
|
|
1472
1073
|
error: errText(err),
|
|
1473
1074
|
...(err instanceof RecordedValueError ? { reason: "not_run" } : {}),
|
|
1474
1075
|
};
|
|
1475
1076
|
break;
|
|
1476
1077
|
}
|
|
1477
|
-
const call = await this.callStep(
|
|
1078
|
+
const call = await this.callStep(step, position, computed, startedAt, execute, recordedOutputFor, onStep, opts, opts.deadline);
|
|
1478
1079
|
if (call.stopped) {
|
|
1479
1080
|
skipped += 1;
|
|
1480
1081
|
stopped = call.stopped;
|
|
@@ -1493,7 +1094,6 @@ export class ScenarioReplayPlan {
|
|
|
1493
1094
|
errored += 1;
|
|
1494
1095
|
onStep?.({
|
|
1495
1096
|
step,
|
|
1496
|
-
entry,
|
|
1497
1097
|
input: computed,
|
|
1498
1098
|
output: response,
|
|
1499
1099
|
outcome: "failed",
|
|
@@ -1502,11 +1102,11 @@ export class ScenarioReplayPlan {
|
|
|
1502
1102
|
toolMissing: isMissingToolError(toolError) || undefined,
|
|
1503
1103
|
ms: Date.now() - startedAt,
|
|
1504
1104
|
});
|
|
1505
|
-
entries.push({ toolName: step.toolName
|
|
1105
|
+
entries.push({ toolName: step.toolName, input: bundleInput(computed), response, recorded });
|
|
1506
1106
|
stopped = {
|
|
1507
1107
|
kind: "step_failed",
|
|
1508
1108
|
stepIndex: step.stepIndex,
|
|
1509
|
-
|
|
1109
|
+
position,
|
|
1510
1110
|
stage: "tool_call",
|
|
1511
1111
|
error: toolError,
|
|
1512
1112
|
};
|
|
@@ -1517,24 +1117,23 @@ export class ScenarioReplayPlan {
|
|
|
1517
1117
|
// too: `toolOutputLogic` was authored against exactly this shape.
|
|
1518
1118
|
let derivedKeys = [];
|
|
1519
1119
|
try {
|
|
1520
|
-
reportingStage(onStep,
|
|
1120
|
+
reportingStage(onStep, step, "tool_output_logic", computed, startedAt, () => {
|
|
1521
1121
|
if (step.toolOutputLogic) {
|
|
1522
|
-
const derived = evalToolOutputLogic(step.toolOutputLogic, response, this.
|
|
1122
|
+
const derived = evalToolOutputLogic(step.toolOutputLogic, response, this.params, this.intent, resp);
|
|
1523
1123
|
derivedKeys = Object.keys(derived);
|
|
1524
|
-
Object.assign(resp
|
|
1124
|
+
Object.assign(resp, derived);
|
|
1525
1125
|
}
|
|
1526
|
-
this.leaveFramesAt(flatIndex, walk);
|
|
1527
1126
|
}, response);
|
|
1528
1127
|
}
|
|
1529
1128
|
catch (err) {
|
|
1530
1129
|
if (!stopOnFailure)
|
|
1531
1130
|
throw err;
|
|
1532
1131
|
// The tool ran; its output is real and belongs in front of the model.
|
|
1533
|
-
entries.push({ toolName: step.toolName
|
|
1132
|
+
entries.push({ toolName: step.toolName, input: bundleInput(computed), response, recorded });
|
|
1534
1133
|
stopped = {
|
|
1535
1134
|
kind: "step_failed",
|
|
1536
1135
|
stepIndex: step.stepIndex,
|
|
1537
|
-
|
|
1136
|
+
position,
|
|
1538
1137
|
stage: "tool_output_logic",
|
|
1539
1138
|
error: errText(err),
|
|
1540
1139
|
};
|
|
@@ -1549,7 +1148,6 @@ export class ScenarioReplayPlan {
|
|
|
1549
1148
|
errored += 1;
|
|
1550
1149
|
onStep?.({
|
|
1551
1150
|
step,
|
|
1552
|
-
entry,
|
|
1553
1151
|
input: computed,
|
|
1554
1152
|
output: response,
|
|
1555
1153
|
outcome: error ? "failed" : "executed",
|
|
@@ -1560,14 +1158,13 @@ export class ScenarioReplayPlan {
|
|
|
1560
1158
|
ms: Date.now() - startedAt,
|
|
1561
1159
|
});
|
|
1562
1160
|
}
|
|
1563
|
-
entries.push({ toolName: step.toolName
|
|
1161
|
+
entries.push({ toolName: step.toolName, input: bundleInput(computed), response, recorded });
|
|
1564
1162
|
}
|
|
1565
1163
|
return {
|
|
1566
1164
|
text: assembleBundle(entries, maxChars, {
|
|
1567
1165
|
// A bundle that stops short of the task — in front of a parked step, or
|
|
1568
|
-
// at a step that broke — must never say "the work is done".
|
|
1569
|
-
|
|
1570
|
-
handover: (opts.handover === true && flatIndex <= this.stopAt) || stopped !== undefined,
|
|
1166
|
+
// at a step that broke — must never say "the work is done".
|
|
1167
|
+
handover: opts.handover === true || stopped !== undefined,
|
|
1571
1168
|
subTask: this.subTask,
|
|
1572
1169
|
reportTool: opts.reportTool,
|
|
1573
1170
|
}),
|
|
@@ -1578,7 +1175,7 @@ export class ScenarioReplayPlan {
|
|
|
1578
1175
|
notTaken,
|
|
1579
1176
|
left: walk.left,
|
|
1580
1177
|
stopped,
|
|
1581
|
-
endedAt:
|
|
1178
|
+
endedAt: position,
|
|
1582
1179
|
};
|
|
1583
1180
|
}
|
|
1584
1181
|
/**
|
|
@@ -1614,10 +1211,7 @@ export class ScenarioReplayPlan {
|
|
|
1614
1211
|
while (!this.isDone()) {
|
|
1615
1212
|
// The steps this request does not need are left out, and said so in the
|
|
1616
1213
|
// bundle; a test that says "the other half" stops the run here (AC5).
|
|
1617
|
-
|
|
1618
|
-
// out stops the run (`result_map`) — or, without `stopOnFailure`, its
|
|
1619
|
-
// throw propagates, as any logic throw does.
|
|
1620
|
-
const settled = this.settleTests(onStep, { rethrow: !stopOnFailure });
|
|
1214
|
+
const settled = this.settleTests(onStep);
|
|
1621
1215
|
left.push(...settled.left);
|
|
1622
1216
|
for (const l of settled.left) {
|
|
1623
1217
|
this.passOver(l, recordedOutputFor);
|
|
@@ -1630,12 +1224,11 @@ export class ScenarioReplayPlan {
|
|
|
1630
1224
|
}
|
|
1631
1225
|
if (this.isDone())
|
|
1632
1226
|
break;
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
// Where this step sits in the flat list — what a stop reports. Taken now,
|
|
1227
|
+
const step = this.steps[this.stepIndex];
|
|
1228
|
+
// Where this step sits in the chain — what a stop reports. Taken now,
|
|
1636
1229
|
// because the cursor has moved on by the time some stops are written.
|
|
1637
|
-
const
|
|
1638
|
-
const before = this.stopBefore(
|
|
1230
|
+
const position = this.stepIndex;
|
|
1231
|
+
const before = this.stopBefore(step, position, opts.signal, deadline);
|
|
1639
1232
|
if (before) {
|
|
1640
1233
|
stopped = before;
|
|
1641
1234
|
if (before.reason === "budget")
|
|
@@ -1647,7 +1240,7 @@ export class ScenarioReplayPlan {
|
|
|
1647
1240
|
const startedAt = Date.now();
|
|
1648
1241
|
let computed;
|
|
1649
1242
|
try {
|
|
1650
|
-
computed = reportingStage(onStep,
|
|
1243
|
+
computed = reportingStage(onStep, step, "tool_input_logic", {}, startedAt, () => this.toolInputForCurrentStep());
|
|
1651
1244
|
}
|
|
1652
1245
|
catch (err) {
|
|
1653
1246
|
if (!stopOnFailure)
|
|
@@ -1655,14 +1248,14 @@ export class ScenarioReplayPlan {
|
|
|
1655
1248
|
stopped = {
|
|
1656
1249
|
kind: "step_failed",
|
|
1657
1250
|
stepIndex: step.stepIndex,
|
|
1658
|
-
|
|
1251
|
+
position,
|
|
1659
1252
|
stage: "tool_input_logic",
|
|
1660
1253
|
error: errText(err),
|
|
1661
1254
|
...(err instanceof RecordedValueError ? { reason: "not_run" } : {}),
|
|
1662
1255
|
};
|
|
1663
1256
|
break;
|
|
1664
1257
|
}
|
|
1665
|
-
const call = await this.callStep(
|
|
1258
|
+
const call = await this.callStep(step, position, computed, startedAt, execute, recordedOutputFor, onStep, opts, deadline);
|
|
1666
1259
|
if (call.stopped) {
|
|
1667
1260
|
skipped += 1;
|
|
1668
1261
|
stopped = call.stopped;
|
|
@@ -1681,7 +1274,6 @@ export class ScenarioReplayPlan {
|
|
|
1681
1274
|
errored += 1;
|
|
1682
1275
|
onStep?.({
|
|
1683
1276
|
step,
|
|
1684
|
-
entry,
|
|
1685
1277
|
input: computed,
|
|
1686
1278
|
output: response,
|
|
1687
1279
|
outcome: "failed",
|
|
@@ -1690,12 +1282,12 @@ export class ScenarioReplayPlan {
|
|
|
1690
1282
|
toolMissing: isMissingToolError(toolError) || undefined,
|
|
1691
1283
|
ms: Date.now() - startedAt,
|
|
1692
1284
|
});
|
|
1693
|
-
entries.push({ toolName: step.toolName
|
|
1285
|
+
entries.push({ toolName: step.toolName, input: bundleInput(computed), response, recorded });
|
|
1694
1286
|
this.stepIndex += 1;
|
|
1695
1287
|
stopped = {
|
|
1696
1288
|
kind: "step_failed",
|
|
1697
1289
|
stepIndex: step.stepIndex,
|
|
1698
|
-
|
|
1290
|
+
position,
|
|
1699
1291
|
stage: "tool_call",
|
|
1700
1292
|
error: toolError,
|
|
1701
1293
|
};
|
|
@@ -1704,17 +1296,17 @@ export class ScenarioReplayPlan {
|
|
|
1704
1296
|
}
|
|
1705
1297
|
let derivedKeys;
|
|
1706
1298
|
try {
|
|
1707
|
-
derivedKeys = reportingStage(onStep,
|
|
1299
|
+
derivedKeys = reportingStage(onStep, step, "tool_output_logic", computed, startedAt, () => this.applyOutput(response), response);
|
|
1708
1300
|
}
|
|
1709
1301
|
catch (err) {
|
|
1710
1302
|
if (!stopOnFailure)
|
|
1711
1303
|
throw err;
|
|
1712
1304
|
// The tool ran; its output is real and belongs in front of the model.
|
|
1713
|
-
entries.push({ toolName: step.toolName
|
|
1305
|
+
entries.push({ toolName: step.toolName, input: bundleInput(computed), response, recorded });
|
|
1714
1306
|
stopped = {
|
|
1715
1307
|
kind: "step_failed",
|
|
1716
1308
|
stepIndex: step.stepIndex,
|
|
1717
|
-
|
|
1309
|
+
position,
|
|
1718
1310
|
stage: "tool_output_logic",
|
|
1719
1311
|
error: errText(err),
|
|
1720
1312
|
};
|
|
@@ -1729,7 +1321,6 @@ export class ScenarioReplayPlan {
|
|
|
1729
1321
|
errored += 1;
|
|
1730
1322
|
onStep?.({
|
|
1731
1323
|
step,
|
|
1732
|
-
entry,
|
|
1733
1324
|
input: computed,
|
|
1734
1325
|
output: response,
|
|
1735
1326
|
outcome: error ? "failed" : "executed",
|
|
@@ -1740,17 +1331,16 @@ export class ScenarioReplayPlan {
|
|
|
1740
1331
|
ms: Date.now() - startedAt,
|
|
1741
1332
|
});
|
|
1742
1333
|
}
|
|
1743
|
-
entries.push({ toolName: step.toolName
|
|
1334
|
+
entries.push({ toolName: step.toolName, input: bundleInput(computed), response, recorded });
|
|
1744
1335
|
}
|
|
1745
|
-
// Every planned step ran and the plan ends in front of a parked one
|
|
1746
|
-
// when a test left out a sub-task that reaches past it (AC5).
|
|
1336
|
+
// Every planned step ran and the plan ends in front of a parked one.
|
|
1747
1337
|
if (!stopped && this.atStop()) {
|
|
1748
1338
|
// `known_bad_step` here means "in front of the plan's end": the
|
|
1749
1339
|
// controller reads the served reason and names the kind (D8).
|
|
1750
1340
|
stopped = {
|
|
1751
1341
|
kind: "known_bad_step",
|
|
1752
|
-
stepIndex: this.
|
|
1753
|
-
|
|
1342
|
+
stepIndex: this.steps[this.stopAt].stepIndex,
|
|
1343
|
+
position: this.stopAt,
|
|
1754
1344
|
};
|
|
1755
1345
|
}
|
|
1756
1346
|
return {
|
|
@@ -1775,12 +1365,12 @@ export class ScenarioReplayPlan {
|
|
|
1775
1365
|
* A reason not to *start* the next step: the caller is gone, or the time is
|
|
1776
1366
|
* up. Undefined means go ahead.
|
|
1777
1367
|
*/
|
|
1778
|
-
stopBefore(
|
|
1368
|
+
stopBefore(step, position, signal, deadline) {
|
|
1779
1369
|
if (signal?.aborted) {
|
|
1780
|
-
return { kind: "step_failed", stepIndex:
|
|
1370
|
+
return { kind: "step_failed", stepIndex: step.stepIndex, position, reason: "cancelled" };
|
|
1781
1371
|
}
|
|
1782
1372
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
1783
|
-
return { kind: "step_failed", stepIndex:
|
|
1373
|
+
return { kind: "step_failed", stepIndex: step.stepIndex, position, reason: "budget" };
|
|
1784
1374
|
}
|
|
1785
1375
|
return undefined;
|
|
1786
1376
|
}
|
|
@@ -1789,10 +1379,9 @@ export class ScenarioReplayPlan {
|
|
|
1789
1379
|
* that may stand in for it (only under `recordedFallback`, only for a step
|
|
1790
1380
|
* that could not be sent), or with the stop a missing result means.
|
|
1791
1381
|
*/
|
|
1792
|
-
async callStep(
|
|
1382
|
+
async callStep(step, position, computed, startedAt, execute, recordedOutputFor, onStep, opts,
|
|
1793
1383
|
/** The driver's deadline: a recorded-output lookup is not waited on past it. */
|
|
1794
1384
|
deadline) {
|
|
1795
|
-
const step = entry.step;
|
|
1796
1385
|
try {
|
|
1797
1386
|
return { response: await execute(step, computed), recorded: false };
|
|
1798
1387
|
}
|
|
@@ -1802,11 +1391,10 @@ export class ScenarioReplayPlan {
|
|
|
1802
1391
|
const standIn = opts.recordedFallback === true && reason === "not_run"
|
|
1803
1392
|
? ((typeof step.recordedOutput === "string" && step.recordedOutput.length > 0
|
|
1804
1393
|
? step.recordedOutput
|
|
1805
|
-
: undefined) ?? (await this.recordedWithin(recordedOutputFor,
|
|
1394
|
+
: undefined) ?? (await this.recordedWithin(recordedOutputFor, step, deadline)))
|
|
1806
1395
|
: undefined;
|
|
1807
1396
|
onStep?.({
|
|
1808
1397
|
step,
|
|
1809
|
-
entry,
|
|
1810
1398
|
input: computed,
|
|
1811
1399
|
// The stand-in is what the step's output logic runs on from here.
|
|
1812
1400
|
output: standIn,
|
|
@@ -1821,7 +1409,7 @@ export class ScenarioReplayPlan {
|
|
|
1821
1409
|
stopped: {
|
|
1822
1410
|
kind: "step_failed",
|
|
1823
1411
|
stepIndex: step.stepIndex,
|
|
1824
|
-
|
|
1412
|
+
position,
|
|
1825
1413
|
stage: "tool_call",
|
|
1826
1414
|
error,
|
|
1827
1415
|
reason,
|
|
@@ -1839,14 +1427,14 @@ export class ScenarioReplayPlan {
|
|
|
1839
1427
|
* breaker, and a lookup that has not answered in time is no recorded output.
|
|
1840
1428
|
* A sliver of time left is not worth a request, nor a verdict on the service.
|
|
1841
1429
|
*/
|
|
1842
|
-
async recordedWithin(recordedOutputFor,
|
|
1430
|
+
async recordedWithin(recordedOutputFor, step, deadline) {
|
|
1843
1431
|
if (!recordedOutputFor)
|
|
1844
1432
|
return undefined;
|
|
1845
1433
|
if (deadline !== undefined && deadline - Date.now() < MIN_RECORDED_LOOKUP_MS)
|
|
1846
1434
|
return undefined;
|
|
1847
1435
|
let pending;
|
|
1848
1436
|
try {
|
|
1849
|
-
pending = recordedOutputFor(
|
|
1437
|
+
pending = recordedOutputFor(step);
|
|
1850
1438
|
}
|
|
1851
1439
|
catch {
|
|
1852
1440
|
return undefined;
|