@basein/runner 0.3.13 → 0.3.14
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/control/server.d.ts
CHANGED
|
@@ -1073,6 +1073,31 @@ export declare class ControlServer {
|
|
|
1073
1073
|
private openFragment;
|
|
1074
1074
|
/** What the fragment's create call said: `created: false` is a hit, not a recording. */
|
|
1075
1075
|
private applyFragmentCreation;
|
|
1076
|
+
/**
|
|
1077
|
+
* Whether this matched turn's task is now the model's own, so its work is
|
|
1078
|
+
* the repeat the service offered to record (the service's newestRun.md N1):
|
|
1079
|
+
* offered, nothing recording, no fragment, and no plan of the prompt match
|
|
1080
|
+
* that ran a step or still may. A plan that handed over is the fragment's to
|
|
1081
|
+
* record (fallbk.md D7).
|
|
1082
|
+
*/
|
|
1083
|
+
private repeatDue;
|
|
1084
|
+
/**
|
|
1085
|
+
* Open the repeat run and record from this call on (newestRun.md N1).
|
|
1086
|
+
*
|
|
1087
|
+
* The same swap as {@link openFragment}: nothing allocated before it belongs
|
|
1088
|
+
* to it. Unlike a fragment it carries the turn's prompt, and its cost and
|
|
1089
|
+
* duration are the whole turn's, because no plan did any of the task. Nothing
|
|
1090
|
+
* waits for the service's answer: the steps queue behind the create, and a
|
|
1091
|
+
* create answered `id: null` — the service records no repeat of it — drops
|
|
1092
|
+
* them.
|
|
1093
|
+
*/
|
|
1094
|
+
private openRepeat;
|
|
1095
|
+
/**
|
|
1096
|
+
* Why the matched turn's plan did not run, as the service shows it: what
|
|
1097
|
+
* the service withheld it for, else the code of the gate that declined it
|
|
1098
|
+
* here, a plan that was late included.
|
|
1099
|
+
*/
|
|
1100
|
+
private repeatReason;
|
|
1076
1101
|
/** Resolve with `p`, or null once `ms` passes or it rejects. */
|
|
1077
1102
|
private withinBudget;
|
|
1078
1103
|
/**
|
package/dist/control/server.js
CHANGED
|
@@ -697,6 +697,9 @@ export class ControlServer {
|
|
|
697
697
|
}
|
|
698
698
|
: null,
|
|
699
699
|
fragment: s.run?.fragment ? { state: s.run.fragment.state } : null,
|
|
700
|
+
// The service's offer to record the turn as a recording's newest run,
|
|
701
|
+
// and what became of it (newestRun.md).
|
|
702
|
+
repeat: s.run?.repeat ? { state: s.run.repeat.state, of: s.run.repeat.runId } : null,
|
|
700
703
|
}));
|
|
701
704
|
const sending = this.recorderBacklog();
|
|
702
705
|
return {
|
|
@@ -1673,6 +1676,12 @@ export class ControlServer {
|
|
|
1673
1676
|
if (!match || run.runId !== runId)
|
|
1674
1677
|
return;
|
|
1675
1678
|
run.recording = false;
|
|
1679
|
+
// The service's offer to record the turn after all, should the model end
|
|
1680
|
+
// up doing the task itself (newestRun.md N5). Taken up at the model's
|
|
1681
|
+
// first call of its own, once no plan of this match runs (`repeatDue`).
|
|
1682
|
+
if (match.repeat?.runId && !run.repeat) {
|
|
1683
|
+
run.repeat = { runId: match.repeat.runId, withheld: match.withheld ?? undefined, state: "offered" };
|
|
1684
|
+
}
|
|
1676
1685
|
logLine("run.matched", {
|
|
1677
1686
|
run: runId,
|
|
1678
1687
|
matchedRun: match.runId,
|
|
@@ -2934,6 +2943,12 @@ export class ControlServer {
|
|
|
2934
2943
|
this.noteHandover(run);
|
|
2935
2944
|
return this.preToolAnswer(this.replay.deliverInstead(note, toolName));
|
|
2936
2945
|
}
|
|
2946
|
+
// The model is doing the matched task itself, and the service asked for
|
|
2947
|
+
// that run to be recorded as the recording's newest (newestRun.md N1).
|
|
2948
|
+
// Opened before the probe below, so the turn is recorded from this call
|
|
2949
|
+
// on like any recording turn — a plan an intent match arms in it too.
|
|
2950
|
+
if (this.repeatDue(run))
|
|
2951
|
+
this.openRepeat(session, run);
|
|
2937
2952
|
// 2. The model is driving: look for a scenario by this iteration's intent.
|
|
2938
2953
|
// Never in plan mode, where nothing may run on the user's behalf.
|
|
2939
2954
|
// If the deadline answers first, it answers `{}`: the call runs as
|
|
@@ -3266,6 +3281,9 @@ export class ControlServer {
|
|
|
3266
3281
|
});
|
|
3267
3282
|
}
|
|
3268
3283
|
this.noteHandover(run);
|
|
3284
|
+
// The model doing the matched task itself, as in Claude Code (newestRun.md N1).
|
|
3285
|
+
if (this.repeatDue(run))
|
|
3286
|
+
this.openRepeat(session, run);
|
|
3269
3287
|
const words = this.hostWords(payload);
|
|
3270
3288
|
// Counted, never waited on, and never arming: see `tryIntentMatch`.
|
|
3271
3289
|
await this.tryIntentMatch(session, run, toolName, words.intent, modelArgs);
|
|
@@ -3546,6 +3564,10 @@ export class ControlServer {
|
|
|
3546
3564
|
out.add(prompt);
|
|
3547
3565
|
if (run.fragment?.hitRunId)
|
|
3548
3566
|
out.add(run.fragment.hitRunId);
|
|
3567
|
+
// The recording the match offered a repeat of: the same recording, and
|
|
3568
|
+
// known even when the match landed too late to be armed (newestRun.md).
|
|
3569
|
+
if (run.repeat)
|
|
3570
|
+
out.add(run.repeat.runId);
|
|
3549
3571
|
return [...out];
|
|
3550
3572
|
}
|
|
3551
3573
|
/**
|
|
@@ -3985,8 +4007,16 @@ export class ControlServer {
|
|
|
3985
4007
|
/** What the fragment's create call said: `created: false` is a hit, not a recording. */
|
|
3986
4008
|
applyFragmentCreation(run, id, created) {
|
|
3987
4009
|
const f = run.fragment;
|
|
3988
|
-
if (
|
|
4010
|
+
if (!f || run.runId !== id)
|
|
4011
|
+
return;
|
|
4012
|
+
if (created.created) {
|
|
4013
|
+
// Recorded as the newest run of the fragment it is a hit on (the
|
|
4014
|
+
// service's newestRun.md N2): still that fragment's repeat, so no step
|
|
4015
|
+
// hit may be counted against it (R-HIT-7).
|
|
4016
|
+
if (created.fragmentOf)
|
|
4017
|
+
f.hitRunId = created.fragmentOf.runId;
|
|
3989
4018
|
return;
|
|
4019
|
+
}
|
|
3990
4020
|
run.recording = false;
|
|
3991
4021
|
f.state = "hit";
|
|
3992
4022
|
// Which run it hit matters from here on: this turn must not count step
|
|
@@ -4000,6 +4030,83 @@ export class ControlServer {
|
|
|
4000
4030
|
why: "a hand-over at this step was recorded before — counted as a hit, not recorded",
|
|
4001
4031
|
});
|
|
4002
4032
|
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Whether this matched turn's task is now the model's own, so its work is
|
|
4035
|
+
* the repeat the service offered to record (the service's newestRun.md N1):
|
|
4036
|
+
* offered, nothing recording, no fragment, and no plan of the prompt match
|
|
4037
|
+
* that ran a step or still may. A plan that handed over is the fragment's to
|
|
4038
|
+
* record (fallbk.md D7).
|
|
4039
|
+
*/
|
|
4040
|
+
repeatDue(run) {
|
|
4041
|
+
const r = run.repeat;
|
|
4042
|
+
if (!r || r.state !== "offered" || run.recording || run.finished || run.fragment)
|
|
4043
|
+
return false;
|
|
4044
|
+
const prompt = run.replays.find((s) => s.armedBy === "prompt");
|
|
4045
|
+
if (!prompt?.plan)
|
|
4046
|
+
return true;
|
|
4047
|
+
return prompt.retired && prompt.stepsPinned === 0 && !prompt.handover;
|
|
4048
|
+
}
|
|
4049
|
+
/**
|
|
4050
|
+
* Open the repeat run and record from this call on (newestRun.md N1).
|
|
4051
|
+
*
|
|
4052
|
+
* The same swap as {@link openFragment}: nothing allocated before it belongs
|
|
4053
|
+
* to it. Unlike a fragment it carries the turn's prompt, and its cost and
|
|
4054
|
+
* duration are the whole turn's, because no plan did any of the task. Nothing
|
|
4055
|
+
* waits for the service's answer: the steps queue behind the create, and a
|
|
4056
|
+
* create answered `id: null` — the service records no repeat of it — drops
|
|
4057
|
+
* them.
|
|
4058
|
+
*/
|
|
4059
|
+
openRepeat(session, run) {
|
|
4060
|
+
const r = run.repeat;
|
|
4061
|
+
if (!r || r.state !== "offered")
|
|
4062
|
+
return;
|
|
4063
|
+
const reason = this.repeatReason(run);
|
|
4064
|
+
const repeatOf = reason ? { runId: r.runId, reason } : { runId: r.runId };
|
|
4065
|
+
// The repeat is a run of the same host as the turn it records.
|
|
4066
|
+
const framework = frameworkOf(session);
|
|
4067
|
+
const id = this.recorder.startRun(run.input, { ...this.runMetadata(session), repeatOf }, framework ? { repeatOf, framework } : { repeatOf });
|
|
4068
|
+
for (const c of run.correlations.values()) {
|
|
4069
|
+
c.settled = true;
|
|
4070
|
+
if (c.fallback)
|
|
4071
|
+
clearTimeout(c.fallback);
|
|
4072
|
+
}
|
|
4073
|
+
run.builtIns.clear();
|
|
4074
|
+
run.runId = id;
|
|
4075
|
+
run.ordering = new StepIndexAllocator();
|
|
4076
|
+
run.recording = true;
|
|
4077
|
+
r.state = "open";
|
|
4078
|
+
logLine("run.repeat", {
|
|
4079
|
+
run: id,
|
|
4080
|
+
of: r.runId,
|
|
4081
|
+
reason,
|
|
4082
|
+
why: "the agent is doing a recorded task itself — recorded as that recording's newest run",
|
|
4083
|
+
});
|
|
4084
|
+
if (!isRunCreationAware(this.recorder))
|
|
4085
|
+
return;
|
|
4086
|
+
void this.recorder
|
|
4087
|
+
.runCreated(id)
|
|
4088
|
+
.catch(() => ({ created: true }))
|
|
4089
|
+
.then((created) => {
|
|
4090
|
+
if (created.created || run.runId !== id)
|
|
4091
|
+
return;
|
|
4092
|
+
run.recording = false;
|
|
4093
|
+
r.state = "refused";
|
|
4094
|
+
logLine("run.repeat_refused", {
|
|
4095
|
+
run: id,
|
|
4096
|
+
of: r.runId,
|
|
4097
|
+
why: "the service records no repeat of this recording — nothing recorded",
|
|
4098
|
+
});
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
/**
|
|
4102
|
+
* Why the matched turn's plan did not run, as the service shows it: what
|
|
4103
|
+
* the service withheld it for, else the code of the gate that declined it
|
|
4104
|
+
* here, a plan that was late included.
|
|
4105
|
+
*/
|
|
4106
|
+
repeatReason(run) {
|
|
4107
|
+
const prompt = run.replays.find((s) => s.armedBy === "prompt") ?? run.lateMatch;
|
|
4108
|
+
return run.repeat?.withheld ?? prompt?.declined ?? undefined;
|
|
4109
|
+
}
|
|
4003
4110
|
/** Resolve with `p`, or null once `ms` passes or it rejects. */
|
|
4004
4111
|
async withinBudget(p, ms) {
|
|
4005
4112
|
let timer;
|
|
@@ -11,6 +11,16 @@ export interface StartRunOptions {
|
|
|
11
11
|
scenarioId: string;
|
|
12
12
|
stepIndex: number;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Marks a *repeat* (the service's newestRun.md): a matched turn whose plan
|
|
16
|
+
* did not run, so the model is doing the recording's task itself. Sent only
|
|
17
|
+
* when the match offered it ({@link RunMatch.repeat}). `reason` is why the
|
|
18
|
+
* plan did not run. The service never prompt-matches a repeat.
|
|
19
|
+
*/
|
|
20
|
+
repeatOf?: {
|
|
21
|
+
runId: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
};
|
|
14
24
|
/**
|
|
15
25
|
* The run's `framework`, when the session named a host that is not Claude
|
|
16
26
|
* Code: its `app`, or `app:scope` (`langchain`, `langchain:billing`). The
|
|
@@ -176,6 +186,15 @@ export interface RunMatch {
|
|
|
176
186
|
* ordinary hand-out, and from an older service.
|
|
177
187
|
*/
|
|
178
188
|
withheld?: string | null;
|
|
189
|
+
/**
|
|
190
|
+
* The service's offer (its newestRun.md N5): should the model end up doing
|
|
191
|
+
* this task itself — the plan declined, withheld, late or off — record the
|
|
192
|
+
* turn as a repeat of `runId`, the recording's newest run. Absent or null
|
|
193
|
+
* from a service that does not record repeats: then nothing is recorded.
|
|
194
|
+
*/
|
|
195
|
+
repeat?: {
|
|
196
|
+
runId: string;
|
|
197
|
+
} | null;
|
|
179
198
|
}
|
|
180
199
|
/** Optional capability: `null` means "fresh run, keep recording". */
|
|
181
200
|
export interface MatchAware {
|
|
@@ -202,10 +221,18 @@ export declare function isMatchAware(r: Recorder): r is Recorder & MatchAware;
|
|
|
202
221
|
/** What the create call said about a run: fresh, or a hit on a fragment. */
|
|
203
222
|
export interface RunCreation {
|
|
204
223
|
created: boolean;
|
|
205
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* Set when this was a hit on an existing fragment (R-FALL-7, R-HIT-7) —
|
|
226
|
+
* with `created` too when the service recorded it all the same, as that
|
|
227
|
+
* fragment's newest run (its newestRun.md N2).
|
|
228
|
+
*/
|
|
206
229
|
fragmentOf?: {
|
|
207
230
|
runId: string;
|
|
208
231
|
};
|
|
232
|
+
/** Set when the service recorded the run as a repeat: the run it repeats. */
|
|
233
|
+
repeatOf?: {
|
|
234
|
+
runId: string;
|
|
235
|
+
};
|
|
209
236
|
}
|
|
210
237
|
export interface RunCreationAware {
|
|
211
238
|
runCreated(runId: string): Promise<RunCreation>;
|
|
@@ -475,6 +475,9 @@ export class RemoteRecorder {
|
|
|
475
475
|
// A fragment (fallbk.md D7). Omitted, not null, so an older service
|
|
476
476
|
// that does not know the field sees the body it always saw.
|
|
477
477
|
...(options?.fallbackOf ? { fallbackOf: options.fallbackOf } : {}),
|
|
478
|
+
// A repeat (the service's newestRun.md). Sent only after a match
|
|
479
|
+
// that offered it, so a service that would not know it never sees it.
|
|
480
|
+
...(options?.repeatOf ? { repeatOf: options.repeatOf } : {}),
|
|
478
481
|
// A chain with calls is inlined for this runner rather than stopped in
|
|
479
482
|
// front of its first call (segmented.md R-CALL-28).
|
|
480
483
|
supportsCalls: true,
|
|
@@ -496,7 +499,11 @@ export class RemoteRecorder {
|
|
|
496
499
|
why: "the create's first attempt failed, so the turn went ahead as a fresh one; the service kept its own run, and this one's steps are not sent",
|
|
497
500
|
});
|
|
498
501
|
}
|
|
499
|
-
entry.settle(b.matched ?? null, {
|
|
502
|
+
entry.settle(b.matched ?? null, {
|
|
503
|
+
created: b.id !== null,
|
|
504
|
+
fragmentOf: b.fragmentOf,
|
|
505
|
+
...(b.repeatOf ? { repeatOf: b.repeatOf } : {}),
|
|
506
|
+
});
|
|
500
507
|
}
|
|
501
508
|
catch (err) {
|
|
502
509
|
entry.settle(null, { created: true }); // fail-safe: on a create error, record normally
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basein/runner",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "A recording MCP proxy: sits between any MCP client and its MCP servers, executes each call on the client's behalf, and records the run as a reusable BaseIn scenario.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|