@basein/runner 0.3.9 → 0.3.11
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 +47 -6
- package/dist/auth/client.d.ts +15 -0
- package/dist/auth/client.js +34 -12
- package/dist/bin/bir-hooks.d.ts +1 -1
- package/dist/bin/bir-hooks.js +1 -1
- package/dist/bin/bir.d.ts +2 -0
- package/dist/bin/bir.js +60 -6
- package/dist/bin/investigate.d.ts +5 -0
- package/dist/bin/investigate.js +117 -15
- package/dist/bin/setup.d.ts +12 -0
- package/dist/bin/setup.js +185 -56
- package/dist/bin/update.d.ts +81 -0
- package/dist/bin/update.js +194 -0
- package/dist/config/adapters/claude-code.d.ts +18 -1
- package/dist/config/adapters/claude-code.js +30 -3
- package/dist/config/generate.d.ts +14 -0
- package/dist/control/daemon.d.ts +26 -1
- package/dist/control/daemon.js +53 -3
- package/dist/control/pin-guard.d.ts +6 -1
- package/dist/control/pin-guard.js +15 -1
- package/dist/control/recorder-keeper.js +2 -1
- package/dist/control/server.d.ts +205 -2
- package/dist/control/server.js +766 -56
- package/dist/control/transcript.d.ts +4 -0
- package/dist/control/transcript.js +4 -0
- package/dist/proxy/session.js +1 -0
- package/dist/record/housekeeping.d.ts +7 -1
- package/dist/record/housekeeping.js +11 -3
- package/dist/record/recorder.d.ts +34 -0
- package/dist/record/remote-recorder.d.ts +6 -0
- package/dist/record/remote-recorder.js +31 -4
- package/dist/record/switchable-recorder.js +1 -0
- package/dist/replay/controller.d.ts +139 -3
- package/dist/replay/controller.js +248 -24
- package/dist/replay/derive.js +2 -0
- package/dist/replay/handover.d.ts +13 -1
- package/dist/replay/handover.js +19 -0
- package/dist/replay/safety.d.ts +15 -4
- package/dist/replay/safety.js +19 -6
- package/dist/replay/source-run.js +2 -1
- package/dist/util/service-headers.d.ts +24 -0
- package/dist/util/service-headers.js +47 -0
- package/docs/BaseInstRunner.md +115 -1
- package/docs/calculatedReplay.md +209 -32
- package/docs/calculatedReplayGuide.md +133 -12
- package/docs/installRun.md +91 -10
- package/docs/quickstart.md +64 -9
- package/package.json +1 -1
- package/production.md +83 -19
- package/scripts/install.ps1 +25 -1
|
@@ -44,6 +44,10 @@ export declare function forgetTranscripts(): void;
|
|
|
44
44
|
* learned from a tool, not from the user (segmented.md R-PARAM-2).
|
|
45
45
|
*
|
|
46
46
|
* "This prompt" is everything after the last user-role line that carries text.
|
|
47
|
+
* That is only true once the prompt is in the transcript, and at the prompt
|
|
48
|
+
* hook it is not yet: asked there, this returns the *previous* prompt's
|
|
49
|
+
* results. So the prompt hook does not ask (control/server.ts,
|
|
50
|
+
* `recentResultsFor`) — a prompt just submitted has no results anyway.
|
|
47
51
|
* A transcript compacted mid-turn yields whatever follows that line, which may
|
|
48
52
|
* be nothing; a transcript with no user prompt at all (a run opened by a proxy
|
|
49
53
|
* step) yields nothing. Results with `is_error: true` are kept: a failure is
|
|
@@ -410,6 +410,10 @@ export function forgetTranscripts() {
|
|
|
410
410
|
* learned from a tool, not from the user (segmented.md R-PARAM-2).
|
|
411
411
|
*
|
|
412
412
|
* "This prompt" is everything after the last user-role line that carries text.
|
|
413
|
+
* That is only true once the prompt is in the transcript, and at the prompt
|
|
414
|
+
* hook it is not yet: asked there, this returns the *previous* prompt's
|
|
415
|
+
* results. So the prompt hook does not ask (control/server.ts,
|
|
416
|
+
* `recentResultsFor`) — a prompt just submitted has no results anyway.
|
|
413
417
|
* A transcript compacted mid-turn yields whatever follows that line, which may
|
|
414
418
|
* be nothing; a transcript with no user prompt at all (a run opened by a proxy
|
|
415
419
|
* step) yields nothing. Results with `is_error: true` are kept: a failure is
|
package/dist/proxy/session.js
CHANGED
|
@@ -716,6 +716,7 @@ export class ProxySession {
|
|
|
716
716
|
this.recorder = await this.buildRecorder();
|
|
717
717
|
this.runId = this.recorder.startRun("", {
|
|
718
718
|
recorder: "baseinstrunner",
|
|
719
|
+
runnerVersion: packageVersion(),
|
|
719
720
|
tier: "standalone",
|
|
720
721
|
host: { app: process.env.BIR_HOST_APP ?? "unknown", version: process.env.BIR_HOST_VERSION },
|
|
721
722
|
wrappedServers: [this.serverName],
|
|
@@ -68,8 +68,14 @@ export declare const RUN_SCENARIO_TOOL = "mcp__bir__run_scenario";
|
|
|
68
68
|
* Whether one call is host housekeeping: never recorded as a step, never a
|
|
69
69
|
* divergence from a plan. `toolInput` is what the hook saw (`tool_input`); only
|
|
70
70
|
* a `Bash` call's `command` is read from it.
|
|
71
|
+
*
|
|
72
|
+
* `otherHost` is a session whose host is not Claude Code. Its tool names are
|
|
73
|
+
* its own: a tool it calls `TodoWrite` or `Bash` is the application's work and
|
|
74
|
+
* is recorded like any other. Only the runner's own `bir` server stays
|
|
75
|
+
* housekeeping there. (A host with bookkeeping tools of its own does not
|
|
76
|
+
* report them at all.)
|
|
71
77
|
*/
|
|
72
|
-
export declare function isHousekeepingCall(toolName: string, toolInput?: unknown): boolean;
|
|
78
|
+
export declare function isHousekeepingCall(toolName: string, toolInput?: unknown, otherHost?: boolean): boolean;
|
|
73
79
|
/**
|
|
74
80
|
* {@link isBirOnlyCommand} for PowerShell, whose rules differ enough that the
|
|
75
81
|
* Bash reader would get it wrong: a backslash is a path separator, not an
|
|
@@ -76,12 +76,20 @@ export const RUN_SCENARIO_TOOL = `${SCENARIO_SERVER_PREFIX}run_scenario`;
|
|
|
76
76
|
* Whether one call is host housekeeping: never recorded as a step, never a
|
|
77
77
|
* divergence from a plan. `toolInput` is what the hook saw (`tool_input`); only
|
|
78
78
|
* a `Bash` call's `command` is read from it.
|
|
79
|
+
*
|
|
80
|
+
* `otherHost` is a session whose host is not Claude Code. Its tool names are
|
|
81
|
+
* its own: a tool it calls `TodoWrite` or `Bash` is the application's work and
|
|
82
|
+
* is recorded like any other. Only the runner's own `bir` server stays
|
|
83
|
+
* housekeeping there. (A host with bookkeeping tools of its own does not
|
|
84
|
+
* report them at all.)
|
|
79
85
|
*/
|
|
80
|
-
export function isHousekeepingCall(toolName, toolInput) {
|
|
81
|
-
if (HOUSEKEEPING_TOOLS.has(toolName))
|
|
82
|
-
return true;
|
|
86
|
+
export function isHousekeepingCall(toolName, toolInput, otherHost = false) {
|
|
83
87
|
if (toolName.startsWith(SCENARIO_SERVER_PREFIX))
|
|
84
88
|
return toolName !== RUN_SCENARIO_TOOL;
|
|
89
|
+
if (otherHost)
|
|
90
|
+
return false;
|
|
91
|
+
if (HOUSEKEEPING_TOOLS.has(toolName))
|
|
92
|
+
return true;
|
|
85
93
|
if (toolName === "Bash" || toolName === "PowerShell") {
|
|
86
94
|
const command = toolInput?.command;
|
|
87
95
|
if (typeof command !== "string")
|
|
@@ -11,10 +11,31 @@ export interface StartRunOptions {
|
|
|
11
11
|
scenarioId: string;
|
|
12
12
|
stepIndex: number;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* The run's `framework`, when the session named a host that is not Claude
|
|
16
|
+
* Code: its `app`, or `app:scope` (`langchain`, `langchain:billing`). The
|
|
17
|
+
* service keeps a plan inside the host that recorded it by this text.
|
|
18
|
+
* Omitted, the recorder's own framework is sent, as before.
|
|
19
|
+
*/
|
|
20
|
+
framework?: string;
|
|
14
21
|
}
|
|
15
22
|
export interface RunMetrics {
|
|
16
23
|
costUsd?: number;
|
|
17
24
|
durationMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Why the run ended before its turn did, when it did. Sent as the run's
|
|
27
|
+
* `error`, cleaned and cut like any text; such a run has no final answer.
|
|
28
|
+
*/
|
|
29
|
+
error?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The caller's word on whether steps of this run were lost. Present only
|
|
32
|
+
* for a run whose finish may carry the service's `incomplete` mark: the
|
|
33
|
+
* recorder adds the step sends it lost itself, and sends the mark when
|
|
34
|
+
* either says so. A run that carries it is never calculated.
|
|
35
|
+
*/
|
|
36
|
+
incomplete?: {
|
|
37
|
+
lossy: boolean;
|
|
38
|
+
};
|
|
18
39
|
}
|
|
19
40
|
/** How long a caller is prepared to wait for {@link Recorder.flush}. */
|
|
20
41
|
export interface FlushOptions {
|
|
@@ -148,6 +169,13 @@ export interface RunMatch {
|
|
|
148
169
|
verified?: MatchVerdict;
|
|
149
170
|
/** The frozen name, beside the runtime `intent` the plan runs on (R-INTENT-12). */
|
|
150
171
|
intentName?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Why there is a match and no scenario to run, when the service says:
|
|
174
|
+
* `switched_off`, `repairing`, or `runner_update_required` — this runner is
|
|
175
|
+
* older than the plan needs (util/service-headers.ts). Absent or null on an
|
|
176
|
+
* ordinary hand-out, and from an older service.
|
|
177
|
+
*/
|
|
178
|
+
withheld?: string | null;
|
|
151
179
|
}
|
|
152
180
|
/** Optional capability: `null` means "fresh run, keep recording". */
|
|
153
181
|
export interface MatchAware {
|
|
@@ -206,6 +234,12 @@ export interface IntentMatchRequest {
|
|
|
206
234
|
acceptSegments?: boolean;
|
|
207
235
|
/** Always true from this runner (R-CALL-28). */
|
|
208
236
|
supportsCalls?: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* The host the turn runs in, as its runs' `framework` says it (see
|
|
239
|
+
* {@link StartRunOptions.framework}). Left out for Claude Code, which is
|
|
240
|
+
* what a missing value means to the service.
|
|
241
|
+
*/
|
|
242
|
+
framework?: string;
|
|
209
243
|
}
|
|
210
244
|
/** Which text a step's intent came from. */
|
|
211
245
|
export type IntentSourceName = "text" | "thinking" | "tool";
|
|
@@ -215,6 +215,12 @@ export declare class RemoteRecorder implements Recorder, RecorderHealthReporter
|
|
|
215
215
|
/** Backfill a step's `context` (the model's reasoning) after the fact. */
|
|
216
216
|
backfillContext(runId: string, stepId: string, context: string): void;
|
|
217
217
|
finishRun(runId: string, finalOutput?: string, metrics?: RunMetrics): void;
|
|
218
|
+
/**
|
|
219
|
+
* The service's mark for a run that is not whole, or undefined for one that
|
|
220
|
+
* is. Read when the finish is sent, not when it was queued: a step dropped
|
|
221
|
+
* or refused in between is as lost as one before.
|
|
222
|
+
*/
|
|
223
|
+
private incompleteOf;
|
|
218
224
|
/**
|
|
219
225
|
* Report what a matched turn cost (docs/calculatedReplay.md §11.2).
|
|
220
226
|
*
|
|
@@ -50,6 +50,7 @@ import { randomUUID } from "node:crypto";
|
|
|
50
50
|
import { statSync } from "node:fs";
|
|
51
51
|
import { credentialsPath, describeBody, isAuthResponse, issuedFor, loadCredentials, saveCredentials, } from "../auth/client.js";
|
|
52
52
|
import { errText, logDetail, logEvery, logLine } from "../util/log.js";
|
|
53
|
+
import { serviceHeaders } from "../util/service-headers.js";
|
|
53
54
|
import { isUnhealthyStatus, serviceHealth, serviceTimeoutMs, timeoutSignal, } from "../util/service-health.js";
|
|
54
55
|
import { redactJsonText, redactText } from "./redact.js";
|
|
55
56
|
import { capJsonText, capText, DEFAULT_MAX_PAYLOAD_BYTES, preCut, REPORT_PAYLOAD_MAX_BYTES, } from "./truncate.js";
|
|
@@ -465,7 +466,8 @@ export class RemoteRecorder {
|
|
|
465
466
|
try {
|
|
466
467
|
const body = await this.request("POST", "/recordings/runs", JSON.stringify({
|
|
467
468
|
id,
|
|
468
|
-
|
|
469
|
+
// The session's own host, when it named one that is not Claude Code.
|
|
470
|
+
framework: options?.framework ?? this.framework,
|
|
469
471
|
input: out(prompt),
|
|
470
472
|
metadata,
|
|
471
473
|
// A fragment (fallbk.md D7). Omitted, not null, so an older service
|
|
@@ -649,12 +651,30 @@ export class RemoteRecorder {
|
|
|
649
651
|
}
|
|
650
652
|
finishRun(runId, finalOutput, metrics) {
|
|
651
653
|
const output = pend(finalOutput, TEXT_CAPS.answer);
|
|
652
|
-
|
|
654
|
+
// An exception's text, more often than not: it can hold a password or a
|
|
655
|
+
// user's data, so it is cleaned and cut like every other text sent.
|
|
656
|
+
const error = pend(metrics?.error, TEXT_CAPS.reportError);
|
|
657
|
+
const asked = metrics?.incomplete;
|
|
658
|
+
this.enqueueRun(runId, "finish", sizeOf(output, error), () =>
|
|
659
|
+
// `error` and `incomplete` are omitted, not nulled, when absent, so the
|
|
660
|
+
// finish of an ordinary run is the body it has always been.
|
|
661
|
+
this.post(`/recordings/runs/${runId}/finish`, {
|
|
653
662
|
finalOutput: out(output),
|
|
663
|
+
error: out(error),
|
|
654
664
|
originalCostUsd: metrics?.costUsd,
|
|
655
665
|
originalMs: metrics?.durationMs,
|
|
666
|
+
incomplete: asked ? this.incompleteOf(runId, asked.lossy) : undefined,
|
|
656
667
|
}));
|
|
657
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* The service's mark for a run that is not whole, or undefined for one that
|
|
671
|
+
* is. Read when the finish is sent, not when it was queued: a step dropped
|
|
672
|
+
* or refused in between is as lost as one before.
|
|
673
|
+
*/
|
|
674
|
+
incompleteOf(runId, lossy) {
|
|
675
|
+
const lostSteps = this.runs.get(runId)?.lostSteps ?? 0;
|
|
676
|
+
return lossy || lostSteps > 0 ? { lostSteps, lossy } : undefined;
|
|
677
|
+
}
|
|
658
678
|
/**
|
|
659
679
|
* Report what a matched turn cost (docs/calculatedReplay.md §11.2).
|
|
660
680
|
*
|
|
@@ -805,6 +825,7 @@ export class RemoteRecorder {
|
|
|
805
825
|
lastUsed: Date.now(),
|
|
806
826
|
failed: 0,
|
|
807
827
|
dropped: 0,
|
|
828
|
+
lostSteps: 0,
|
|
808
829
|
};
|
|
809
830
|
return entry;
|
|
810
831
|
}
|
|
@@ -1038,8 +1059,11 @@ export class RemoteRecorder {
|
|
|
1038
1059
|
// A matched turn records nothing by design: nothing was lost.
|
|
1039
1060
|
if (why !== "not_created") {
|
|
1040
1061
|
this.droppedSends += 1;
|
|
1041
|
-
if (run)
|
|
1062
|
+
if (run) {
|
|
1042
1063
|
run.dropped += 1;
|
|
1064
|
+
if (job.kind === "step")
|
|
1065
|
+
run.lostSteps += 1;
|
|
1066
|
+
}
|
|
1043
1067
|
}
|
|
1044
1068
|
if (job.kind === "finish" && run && job.runId)
|
|
1045
1069
|
this.summarize(job.runId, run);
|
|
@@ -1071,6 +1095,8 @@ export class RemoteRecorder {
|
|
|
1071
1095
|
const status = err instanceof ServiceError ? err.status : undefined;
|
|
1072
1096
|
if (run && job.runId) {
|
|
1073
1097
|
run.failed += 1;
|
|
1098
|
+
if (job.kind === "step")
|
|
1099
|
+
run.lostSteps += 1;
|
|
1074
1100
|
if (job.kind === "create") {
|
|
1075
1101
|
// A definite refusal means there is no run; an ambiguous failure (a
|
|
1076
1102
|
// timeout, a 5xx) may have created it, and the first step will say.
|
|
@@ -1369,6 +1395,7 @@ export class RemoteRecorder {
|
|
|
1369
1395
|
headers: {
|
|
1370
1396
|
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
1371
1397
|
...(opts.anonymous ? {} : { authorization: `Bearer ${token}` }),
|
|
1398
|
+
...serviceHeaders(),
|
|
1372
1399
|
},
|
|
1373
1400
|
body,
|
|
1374
1401
|
signal,
|
|
@@ -1596,7 +1623,7 @@ export class RemoteRecorder {
|
|
|
1596
1623
|
try {
|
|
1597
1624
|
res = await fetch(`${this.baseUrl}/auth/refresh`, {
|
|
1598
1625
|
method: "POST",
|
|
1599
|
-
headers: { "content-type": "application/json" },
|
|
1626
|
+
headers: { "content-type": "application/json", ...serviceHeaders() },
|
|
1600
1627
|
body: JSON.stringify({ refreshToken: this.session.refreshToken }),
|
|
1601
1628
|
signal,
|
|
1602
1629
|
});
|
|
@@ -16,6 +16,7 @@ import { type ReplayMode, type StepReach } from "./coverage.js";
|
|
|
16
16
|
import { deriveParameters, type DeriveContext } from "./derive.js";
|
|
17
17
|
import { ProxyWorkQueue } from "./executor.js";
|
|
18
18
|
import { ScenarioReplayPlan } from "./plan.js";
|
|
19
|
+
import { type HostSinks } from "./safety.js";
|
|
19
20
|
import { SourceRunOutputs } from "./source-run.js";
|
|
20
21
|
import { type FlatStop } from "./flatten.js";
|
|
21
22
|
import { type ExecutionOutcome, type ExecutionStepResult, type FallbackKind, type SerializedScenario, type SerializedScenarioStep } from "./types.js";
|
|
@@ -99,6 +100,57 @@ export interface PostToolOptions {
|
|
|
99
100
|
uncertain?: boolean;
|
|
100
101
|
/** The call was never sent: its upstream was already gone. Nothing of it happened. */
|
|
101
102
|
notSent?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* A host that is not Claude Code stopped the step itself, and says so: the
|
|
105
|
+
* tool never ran and the host wrote its result (`rejected` — a person said
|
|
106
|
+
* no or answered in its place, or a call limit blocked it), or it ran with
|
|
107
|
+
* arguments a person chose (`edited`). Neither is a fault of the plan: the
|
|
108
|
+
* step is recorded `skipped`, never `failed`, and the plan hands over.
|
|
109
|
+
*/
|
|
110
|
+
hostStop?: HostStop;
|
|
111
|
+
}
|
|
112
|
+
/** How a host that is not Claude Code stopped a planned step (see {@link PostToolOptions.hostStop}). */
|
|
113
|
+
export type HostStop = "rejected" | "edited";
|
|
114
|
+
/**
|
|
115
|
+
* What the control server knows of a session whose host is not Claude Code —
|
|
116
|
+
* a LangChain application, say — handed to {@link ReplayController.arm} and
|
|
117
|
+
* kept on the plan's state.
|
|
118
|
+
*
|
|
119
|
+
* In such a host the tools are the application's own and only it can run
|
|
120
|
+
* them: it steps through the plan by asking for each step's arguments, and
|
|
121
|
+
* its tool names carry none of Claude Code's meanings (`Bash`, `Read`,
|
|
122
|
+
* `TodoWrite`, an `mcp__` prefix). Most of what replay decides by a tool's
|
|
123
|
+
* name is decided where there is no session to ask, so the fact travels with
|
|
124
|
+
* the plan.
|
|
125
|
+
*/
|
|
126
|
+
export interface HostContext {
|
|
127
|
+
/** The host's `app`, for the audit lines (`langchain`). */
|
|
128
|
+
app: string;
|
|
129
|
+
/**
|
|
130
|
+
* The sinks the host declared for its own tools. Always present, empty or
|
|
131
|
+
* not: being there is what tells the safety rules whose names these are
|
|
132
|
+
* (safety.ts, `HostSinks`).
|
|
133
|
+
*/
|
|
134
|
+
sinks: HostSinks;
|
|
135
|
+
/**
|
|
136
|
+
* The run's adapter did not ask for a plan with its prompt: it only
|
|
137
|
+
* records. A match is then declined at gate 2, under its own code.
|
|
138
|
+
*/
|
|
139
|
+
recordsOnly?: boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* An armed plan as data, for a host that steps through it itself. The same
|
|
143
|
+
* steps the directive lists — those the plan will run — by their position.
|
|
144
|
+
*/
|
|
145
|
+
export interface PlanData {
|
|
146
|
+
scenarioId: string | null;
|
|
147
|
+
mode: ReplayMode;
|
|
148
|
+
steps: Array<{
|
|
149
|
+
stepIndex: number;
|
|
150
|
+
toolName: string;
|
|
151
|
+
}>;
|
|
152
|
+
/** Only when the plan stops before its end: the first step that will not run. */
|
|
153
|
+
stopAt?: number;
|
|
102
154
|
}
|
|
103
155
|
/**
|
|
104
156
|
* The longest a direct run may take by its budgets, end to end.
|
|
@@ -431,12 +483,38 @@ export interface ReplayState {
|
|
|
431
483
|
* is waiting on the service, and obeys its breaker (util/service-health.ts).
|
|
432
484
|
*/
|
|
433
485
|
deriveOnService?: boolean;
|
|
486
|
+
/**
|
|
487
|
+
* Set when the session's host is not Claude Code (see {@link HostContext}).
|
|
488
|
+
* Such a plan is always `steer` with every step `live`, and nothing here
|
|
489
|
+
* runs a step of it or reads a Claude Code meaning into a tool's name.
|
|
490
|
+
*/
|
|
491
|
+
host?: HostContext;
|
|
492
|
+
/**
|
|
493
|
+
* The host stopped a planned step itself ({@link PostToolOptions.hostStop}).
|
|
494
|
+
* It decides what the report may claim: after `rejected` the task ended
|
|
495
|
+
* early, so the turn is no measure of it; after either, its cost is no
|
|
496
|
+
* sample of what the task costs unaided.
|
|
497
|
+
*/
|
|
498
|
+
hostStop?: HostStop;
|
|
434
499
|
}
|
|
435
500
|
/**
|
|
436
501
|
* Why a match did not arm. One code per gate of the ladder, so the caller can
|
|
437
502
|
* act on a decline without parsing the sentence that explains it.
|
|
438
503
|
*/
|
|
439
|
-
export type DeclineCode = "replay_disabled" | "not_ready" | "flatten_failed" | "known_bad_first_step" | "nondeterministic_first_step" | "unusable_first_step" | "coverage" | "missing_target" | "no_derive_key"
|
|
504
|
+
export type DeclineCode = "replay_disabled" | "not_ready" | "flatten_failed" | "known_bad_first_step" | "nondeterministic_first_step" | "unusable_first_step" | "coverage" | "missing_target" | "no_derive_key"
|
|
505
|
+
/** The service handed over no plan: this runner is too old for it. */
|
|
506
|
+
| "runner_update_required"
|
|
507
|
+
/** The session's host only records: its adapter asked for no plan with the prompt. */
|
|
508
|
+
| "host_records_only"
|
|
509
|
+
/** The plan names a tool the host's agent does not hold. */
|
|
510
|
+
| "host_tool_missing"
|
|
511
|
+
/**
|
|
512
|
+
* No gate said no: the plan was good, and late. Not ready when the prompt
|
|
513
|
+
* had to be answered (`arm_timeout`), or ready just after it had been
|
|
514
|
+
* (`arm_unannounced`). Set by the control server, where a prompt's wait
|
|
515
|
+
* ends, so the report can say why a matched turn was not steered.
|
|
516
|
+
*/
|
|
517
|
+
| "arm_timeout" | "arm_unannounced";
|
|
440
518
|
/** What `PreToolUse` should do about this call. */
|
|
441
519
|
export type PreToolAction =
|
|
442
520
|
/** Pin the arguments and let the real tool run. */
|
|
@@ -515,8 +593,46 @@ export declare class ReplayController {
|
|
|
515
593
|
* Always returns a state — a declined match still has a ticket to redeem and a
|
|
516
594
|
* baseline sample to contribute, and losing that is how a savings ledger ends
|
|
517
595
|
* up with a denominator nobody measured.
|
|
596
|
+
*
|
|
597
|
+
* `host` is given for a session whose host is not Claude Code, and is kept
|
|
598
|
+
* on the state (see {@link HostContext}).
|
|
599
|
+
*/
|
|
600
|
+
arm(match: RunMatchLike, prompt: string, wrapped: ReadonlySet<string>, armedBy?: "prompt" | "intent", ctx?: DeriveContext, host?: HostContext): Promise<ReplayState>;
|
|
601
|
+
/**
|
|
602
|
+
* The armed plan as data: the steps it will run, in order, and where it
|
|
603
|
+
* stops when that is before its end. For a host that steps through the plan
|
|
604
|
+
* itself and must not have to pick the tool names out of a directive
|
|
605
|
+
* written for a model — a reworded sentence would break every adapter in
|
|
606
|
+
* the field. Built from the same entries the directive lists.
|
|
607
|
+
*/
|
|
608
|
+
planData(state: ReplayState): PlanData | undefined;
|
|
609
|
+
/**
|
|
610
|
+
* Take back a plan that armed and has run nothing, as a decline.
|
|
611
|
+
*
|
|
612
|
+
* For what `arm` could not know: which tools the agent of another host
|
|
613
|
+
* holds. Such a host shows its tools only when its model is about to be
|
|
614
|
+
* called, with the first request for a step, and a plan that names a tool
|
|
615
|
+
* the agent lacks cannot be finished there. It ends before any step runs,
|
|
616
|
+
* booked like any gate's decline: `not_steered` under `code`, no step
|
|
617
|
+
* marked failed or missing — either would count against a plan that is
|
|
618
|
+
* not at fault. Returns false, changing nothing, once a step has run.
|
|
619
|
+
*/
|
|
620
|
+
declineArmed(state: ReplayState, code: DeclineCode, why: string): boolean;
|
|
621
|
+
/**
|
|
622
|
+
* Hand a live plan over for something its host did, not the plan: the
|
|
623
|
+
* model made a call of its own while the plan was live (`own_call`), the
|
|
624
|
+
* run was closed with the plan still live (`closed`), or the host was
|
|
625
|
+
* answered without a step's arguments because they were not ready in time
|
|
626
|
+
* (`late`).
|
|
627
|
+
*
|
|
628
|
+
* Only for a host that is not Claude Code, where a call the plan did not
|
|
629
|
+
* hand out is by definition the model's own — it is never pinned and never
|
|
630
|
+
* answered with a bundle. No step is marked failed, so nothing is counted
|
|
631
|
+
* against the plan; the outcome is `fell_back` when a step ran and `failed`
|
|
632
|
+
* when none did, as for every hand-over. The note is the caller's to
|
|
633
|
+
* deliver ({@link takeNote}). Returns false when there was no live plan.
|
|
518
634
|
*/
|
|
519
|
-
|
|
635
|
+
hostHandOver(state: ReplayState, why: "own_call" | "closed" | "late"): boolean;
|
|
520
636
|
/**
|
|
521
637
|
* The directive to inject via `additionalContext`, or undefined when declined.
|
|
522
638
|
*
|
|
@@ -559,8 +675,12 @@ export declare class ReplayController {
|
|
|
559
675
|
* Deliver text to the model in place of the tool call it was about to make:
|
|
560
676
|
* as genuine command output for `Bash`, as the denial reason otherwise — the
|
|
561
677
|
* two channels a divergence bundle already uses.
|
|
678
|
+
*
|
|
679
|
+
* `otherHost` is a session whose host is not Claude Code: a tool it calls
|
|
680
|
+
* `Bash` is its own, and its command is never rewritten. The text is always
|
|
681
|
+
* the refusal reason there.
|
|
562
682
|
*/
|
|
563
|
-
deliverInstead(text: string, toolName: string): PreToolAction;
|
|
683
|
+
deliverInstead(text: string, toolName: string, otherHost?: boolean): PreToolAction;
|
|
564
684
|
/**
|
|
565
685
|
* `PreToolUse`, while a plan is active.
|
|
566
686
|
*
|
|
@@ -639,6 +759,13 @@ export declare class ReplayController {
|
|
|
639
759
|
* moves from `failed` to `fell_back` when it is the first that did.
|
|
640
760
|
*/
|
|
641
761
|
private lateThread;
|
|
762
|
+
/**
|
|
763
|
+
* Record a step its host stopped ({@link PostToolOptions.hostStop}) as
|
|
764
|
+
* `skipped`, in plain words, and remember the stop for the report. Never
|
|
765
|
+
* `failed`: the service counts failures against the plan. And not counted
|
|
766
|
+
* in `stepsPinned`: the plan's own call did not run.
|
|
767
|
+
*/
|
|
768
|
+
private hostStopped;
|
|
642
769
|
/** Whether a pinned call's output is threaded from the proxy's report (§7.2). */
|
|
643
770
|
threadsFromProxy(state: ReplayState, toolUseId: string): boolean;
|
|
644
771
|
/**
|
|
@@ -685,6 +812,13 @@ export declare class ReplayController {
|
|
|
685
812
|
siblings?: readonly ReplayState[];
|
|
686
813
|
/** The control server's own run id for this turn — journal-only, never sent. */
|
|
687
814
|
runId?: string;
|
|
815
|
+
/**
|
|
816
|
+
* The run was closed before its task ended for the caller — it failed,
|
|
817
|
+
* went quiet, or stopped with the plan still live. Its cost is no
|
|
818
|
+
* measure of the task: reported unmeasured, and never a sample of the
|
|
819
|
+
* usual cost.
|
|
820
|
+
*/
|
|
821
|
+
cutShort?: boolean;
|
|
688
822
|
}): ExecutionReport | undefined;
|
|
689
823
|
/**
|
|
690
824
|
* The step a failed replay is fairly blamed on: the first whose own logic
|
|
@@ -893,6 +1027,8 @@ export interface RunMatchLike {
|
|
|
893
1027
|
verified?: MatchVerdict;
|
|
894
1028
|
/** The frozen name, beside the runtime intent the plan runs on (R-INTENT-12). */
|
|
895
1029
|
intentName?: string;
|
|
1030
|
+
/** Why the service handed over no plan, when it says. Read for `runner_update_required`. */
|
|
1031
|
+
withheld?: string | null;
|
|
896
1032
|
}
|
|
897
1033
|
/**
|
|
898
1034
|
* The old rule, for a service that sends no `stop`: the position of the first
|