@basein/runner 0.2.0 → 0.2.1
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 +2 -0
- package/dist/auth/client.d.ts +12 -0
- package/dist/auth/client.js +41 -0
- package/dist/bin/bir-hooks.d.ts +13 -0
- package/dist/bin/bir-hooks.js +58 -0
- package/dist/bin/bir.js +42 -2
- package/dist/control/server.d.ts +84 -1
- package/dist/control/server.js +546 -51
- package/dist/control/transcript.d.ts +40 -0
- package/dist/control/transcript.js +105 -0
- package/dist/record/recorder.d.ts +178 -4
- package/dist/record/recorder.js +6 -0
- package/dist/record/remote-recorder.d.ts +20 -2
- package/dist/record/remote-recorder.js +66 -6
- package/dist/replay/bundle.d.ts +10 -1
- package/dist/replay/bundle.js +41 -3
- package/dist/replay/controller.d.ts +164 -5
- package/dist/replay/controller.js +556 -54
- package/dist/replay/coverage.js +2 -2
- package/dist/replay/derive.d.ts +20 -1
- package/dist/replay/derive.js +69 -12
- package/dist/replay/flatten.d.ts +125 -0
- package/dist/replay/flatten.js +182 -0
- package/dist/replay/handover.d.ts +60 -0
- package/dist/replay/handover.js +82 -0
- package/dist/replay/logic.d.ts +11 -0
- package/dist/replay/logic.js +17 -0
- package/dist/replay/plan.d.ts +105 -8
- package/dist/replay/plan.js +309 -47
- package/dist/replay/source-run.d.ts +24 -10
- package/dist/replay/source-run.js +65 -30
- package/dist/replay/types.d.ts +108 -5
- package/dist/replay/types.js +33 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,8 @@ the browser — the OAuth device grant, the same shape `gh auth login` uses. It
|
|
|
61
61
|
never handles a password, so it works for accounts that only sign in with
|
|
62
62
|
Google, and the link can be opened on any device, which is what makes it usable
|
|
63
63
|
on a headless runner. `--no-browser` prints the link without opening one;
|
|
64
|
+
`--token <value>` redeems a one-time setup token made on the console's *Set up
|
|
65
|
+
the runner* page, with no browser step at all (service 2026-09 or newer);
|
|
64
66
|
`--password` is the deprecated email-and-password prompt, removed next release.
|
|
65
67
|
|
|
66
68
|
Check it:
|
package/dist/auth/client.d.ts
CHANGED
|
@@ -200,6 +200,18 @@ export interface DeviceLoginOptions {
|
|
|
200
200
|
* that a script may read, and the code block is not it.
|
|
201
201
|
*/
|
|
202
202
|
export declare function deviceLogin(authUrl: string, opts?: DeviceLoginOptions): Promise<AuthSession | undefined>;
|
|
203
|
+
/**
|
|
204
|
+
* Sign in with a setup token and cache the session.
|
|
205
|
+
*
|
|
206
|
+
* A setup token is a device code the console minted already approved for the
|
|
207
|
+
* person who was signed in there (`POST /auth/device/setup-token`), so this is
|
|
208
|
+
* the browser flow with the waiting removed: one redemption, no polling. It is
|
|
209
|
+
* the secret that opens a session, so it is never printed or logged here, for
|
|
210
|
+
* the same reason a device code never is.
|
|
211
|
+
*/
|
|
212
|
+
export declare function tokenLogin(authUrl: string, token: string, opts?: {
|
|
213
|
+
fetchImpl?: typeof fetch;
|
|
214
|
+
}): Promise<AuthSession>;
|
|
203
215
|
/**
|
|
204
216
|
* Sign in with an email and a password.
|
|
205
217
|
*
|
package/dist/auth/client.js
CHANGED
|
@@ -517,6 +517,47 @@ export async function deviceLogin(authUrl, opts = {}) {
|
|
|
517
517
|
saveCredentials(session);
|
|
518
518
|
return session;
|
|
519
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Sign in with a setup token and cache the session.
|
|
522
|
+
*
|
|
523
|
+
* A setup token is a device code the console minted already approved for the
|
|
524
|
+
* person who was signed in there (`POST /auth/device/setup-token`), so this is
|
|
525
|
+
* the browser flow with the waiting removed: one redemption, no polling. It is
|
|
526
|
+
* the secret that opens a session, so it is never printed or logged here, for
|
|
527
|
+
* the same reason a device code never is.
|
|
528
|
+
*/
|
|
529
|
+
export async function tokenLogin(authUrl, token, opts = {}) {
|
|
530
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
531
|
+
const res = await fetchImpl(`${authUrl}/auth/device/token`, {
|
|
532
|
+
method: "POST",
|
|
533
|
+
headers: { "content-type": "application/json" },
|
|
534
|
+
body: JSON.stringify({ deviceCode: token.trim() }),
|
|
535
|
+
});
|
|
536
|
+
if (res.status === 404)
|
|
537
|
+
throw new DeviceFlowUnsupported();
|
|
538
|
+
if (res.ok) {
|
|
539
|
+
const session = toSession((await res.json()));
|
|
540
|
+
saveCredentials(session);
|
|
541
|
+
logLine("auth.token_login", { url: authUrl });
|
|
542
|
+
return session;
|
|
543
|
+
}
|
|
544
|
+
const body = (await res.json().catch(() => ({})));
|
|
545
|
+
switch (body.error) {
|
|
546
|
+
case "expired_token":
|
|
547
|
+
throw new DeviceFlowAborted("this setup token has expired or was already used — make a new one on the console's " +
|
|
548
|
+
"Set up the runner page and paste it within ten minutes", "expired_token");
|
|
549
|
+
case "invalid_grant":
|
|
550
|
+
case "authorization_pending":
|
|
551
|
+
case "slow_down":
|
|
552
|
+
case "access_denied":
|
|
553
|
+
// A pending or unknown code is not a setup token: the console only hands
|
|
554
|
+
// out codes that are already approved.
|
|
555
|
+
throw new DeviceFlowAborted("that is not a valid setup token — copy the whole token from the console's " +
|
|
556
|
+
"Set up the runner page", "access_denied");
|
|
557
|
+
default:
|
|
558
|
+
throw new Error(`sign-in failed: ${body.error ?? `HTTP ${res.status}`}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
520
561
|
/**
|
|
521
562
|
* A name for this machine, shown on the approval page.
|
|
522
563
|
*
|
package/dist/bin/bir-hooks.d.ts
CHANGED
|
@@ -33,6 +33,19 @@
|
|
|
33
33
|
* BIR_DERIVE_MODEL derivation model (default claude-haiku-4-5-…)
|
|
34
34
|
* BIR_MATCH_BUDGET_MS prompt-hook match wait (default 2500)
|
|
35
35
|
* BIR_DERIVE_BUDGET_MS first PreToolUse derivation wait (default 8000)
|
|
36
|
+
* BIR_DERIVE_RECENT_RESULTS how many of this prompt's tool results derivation
|
|
37
|
+
* reads, so a target named by an earlier step can be
|
|
38
|
+
* found (default 5; 0 off)
|
|
39
|
+
* BIR_INTENT_SOURCES which text a step's intent may come from
|
|
40
|
+
* (default text,thinking,tool)
|
|
41
|
+
* BIR_INTENT_REQUESTS_PER_TURN probes one turn may send (default 40).
|
|
42
|
+
* Separate from BIR_INTENT_MATCHES_PER_TURN: arming
|
|
43
|
+
* is a claim on the turn, counting hits is not
|
|
44
|
+
* BIR_SEGMENT_ARM 1 lets a handed-out segment actually run mid-task.
|
|
45
|
+
* Unset is observe-only: what would have armed is
|
|
46
|
+
* logged and nothing is replaced
|
|
47
|
+
* BIR_MIN_SEGMENT_STEER_SIMILARITY minimum similarity to steer a segment
|
|
48
|
+
* (default 0.95)
|
|
36
49
|
* BIR_REPLAY_BUDGET_MS whole-plan ceiling, direct mode (default 120000)
|
|
37
50
|
* BIR_STEP_TIMEOUT_MS one direct tools/call (default 60000)
|
|
38
51
|
*
|
package/dist/bin/bir-hooks.js
CHANGED
|
@@ -33,6 +33,19 @@
|
|
|
33
33
|
* BIR_DERIVE_MODEL derivation model (default claude-haiku-4-5-…)
|
|
34
34
|
* BIR_MATCH_BUDGET_MS prompt-hook match wait (default 2500)
|
|
35
35
|
* BIR_DERIVE_BUDGET_MS first PreToolUse derivation wait (default 8000)
|
|
36
|
+
* BIR_DERIVE_RECENT_RESULTS how many of this prompt's tool results derivation
|
|
37
|
+
* reads, so a target named by an earlier step can be
|
|
38
|
+
* found (default 5; 0 off)
|
|
39
|
+
* BIR_INTENT_SOURCES which text a step's intent may come from
|
|
40
|
+
* (default text,thinking,tool)
|
|
41
|
+
* BIR_INTENT_REQUESTS_PER_TURN probes one turn may send (default 40).
|
|
42
|
+
* Separate from BIR_INTENT_MATCHES_PER_TURN: arming
|
|
43
|
+
* is a claim on the turn, counting hits is not
|
|
44
|
+
* BIR_SEGMENT_ARM 1 lets a handed-out segment actually run mid-task.
|
|
45
|
+
* Unset is observe-only: what would have armed is
|
|
46
|
+
* logged and nothing is replaced
|
|
47
|
+
* BIR_MIN_SEGMENT_STEER_SIMILARITY minimum similarity to steer a segment
|
|
48
|
+
* (default 0.95)
|
|
36
49
|
* BIR_REPLAY_BUDGET_MS whole-plan ceiling, direct mode (default 120000)
|
|
37
50
|
* BIR_STEP_TIMEOUT_MS one direct tools/call (default 60000)
|
|
38
51
|
*
|
|
@@ -93,6 +106,29 @@ function positiveInt(value, fallback) {
|
|
|
93
106
|
const n = Number(value);
|
|
94
107
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
95
108
|
}
|
|
109
|
+
/** Like {@link positiveInt}, but 0 is a meaningful setting rather than unset. */
|
|
110
|
+
function nonNegativeInt(value, fallback) {
|
|
111
|
+
const n = Number(value);
|
|
112
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* `BIR_INTENT_SOURCES` — which text a step's intent may be read from
|
|
116
|
+
* (segmented.md R-INTENT-3). Unset or unparseable means all three.
|
|
117
|
+
*
|
|
118
|
+
* It governs only what is *sent*. Turning every source off does not stop a
|
|
119
|
+
* probe: the request still goes out with an empty text and the server builds a
|
|
120
|
+
* line from the tool name and arguments (R-HIT-4).
|
|
121
|
+
*/
|
|
122
|
+
function parseIntentSources(value) {
|
|
123
|
+
if (value === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
const allowed = ["text", "thinking", "tool"];
|
|
126
|
+
const wanted = value
|
|
127
|
+
.split(",")
|
|
128
|
+
.map((s) => s.trim().toLowerCase())
|
|
129
|
+
.filter((s) => allowed.includes(s));
|
|
130
|
+
return new Set(wanted);
|
|
131
|
+
}
|
|
96
132
|
/**
|
|
97
133
|
* Assemble the replay configuration (docs/calculatedReplay.md §5.3 of the guide).
|
|
98
134
|
*
|
|
@@ -116,6 +152,25 @@ function buildReplayOptions(auth) {
|
|
|
116
152
|
planMs: positiveInt(process.env.BIR_REPLAY_BUDGET_MS, 120_000),
|
|
117
153
|
stepMs: positiveInt(process.env.BIR_STEP_TIMEOUT_MS, 60_000),
|
|
118
154
|
},
|
|
155
|
+
// Intent matching in the ReAct loop (fallbk.md §Runner 4). On with replay;
|
|
156
|
+
// BIR_INTENT_MATCH=0 turns it off on its own.
|
|
157
|
+
intentMatch: {
|
|
158
|
+
enabled: process.env.BIR_INTENT_MATCH !== "0",
|
|
159
|
+
// 4 000, not 1 500: a request whose best segment lands in the not-clear
|
|
160
|
+
// band has one live question to ask under the server's own 2 500 ms
|
|
161
|
+
// attempt, and must still answer inside the hook's 30 s (segmented.md
|
|
162
|
+
// R-HIT-11).
|
|
163
|
+
budgetMs: positiveInt(process.env.BIR_INTENT_MATCH_BUDGET_MS, 4_000),
|
|
164
|
+
maxPerTurn: positiveInt(process.env.BIR_INTENT_MATCHES_PER_TURN, 3),
|
|
165
|
+
maxRequestsPerTurn: positiveInt(process.env.BIR_INTENT_REQUESTS_PER_TURN, 40),
|
|
166
|
+
},
|
|
167
|
+
// Observe-only until an operator has read the logs and turned it on
|
|
168
|
+
// (segmented.md R-OUT-10, R-LIFE-7). Arming is never the way to find out.
|
|
169
|
+
segmentArm: process.env.BIR_SEGMENT_ARM === "1",
|
|
170
|
+
minSegmentSimilarity: (() => {
|
|
171
|
+
const n = Number(process.env.BIR_MIN_SEGMENT_STEER_SIMILARITY);
|
|
172
|
+
return Number.isFinite(n) ? n : 0.95;
|
|
173
|
+
})(),
|
|
119
174
|
authUrl: auth.baseUrl,
|
|
120
175
|
// Read late, not captured: `RemoteRecorder` refreshes the access token as the
|
|
121
176
|
// session outlives it, and a snapshot taken here would go stale mid-run.
|
|
@@ -128,6 +183,7 @@ function buildReplayOptions(auth) {
|
|
|
128
183
|
minSimilarity: opts.minSimilarity,
|
|
129
184
|
allowServers: allowServers ? [...allowServers].join(",") : "(all wrapped)",
|
|
130
185
|
derive: process.env.ANTHROPIC_API_KEY ? "anthropic" : "recorded sample values",
|
|
186
|
+
intentMatch: opts.intentMatch?.enabled ? "on" : "off",
|
|
131
187
|
why: "matched prompts will run their calculated scenario — steered steps are auto-approved",
|
|
132
188
|
});
|
|
133
189
|
}
|
|
@@ -152,6 +208,8 @@ async function main() {
|
|
|
152
208
|
host: { app: "claude-code" },
|
|
153
209
|
correlationDecision: process.env.BIR_CORRELATION_DECISION === "ask" ? "ask" : "allow",
|
|
154
210
|
noCorrelation: process.env.BIR_NO_CORRELATION === "1",
|
|
211
|
+
deriveRecentResults: nonNegativeInt(process.env.BIR_DERIVE_RECENT_RESULTS, 5),
|
|
212
|
+
intentSources: parseIntentSources(process.env.BIR_INTENT_SOURCES),
|
|
155
213
|
replay: buildReplayOptions(auth),
|
|
156
214
|
});
|
|
157
215
|
const address = await server.listen();
|
package/dist/bin/bir.js
CHANGED
|
@@ -28,7 +28,7 @@ import { isScenarioServer, isWrapped, PACKAGE_NAME, readSidecar, scenarioEntry,
|
|
|
28
28
|
import { isRemote, resolveServers } from "../config/resolve.js";
|
|
29
29
|
import { buildHooksBlock, claudeCodePaths, fileForScope, installHooks, readTextOrNull, setServerEntry, sha256, uninstallHooks, } from "../config/adapters/claude-code.js";
|
|
30
30
|
import { readGenericServers, setGenericServerEntry } from "../config/adapters/generic.js";
|
|
31
|
-
import { AUTH_URL_HINT, DeviceFlowAborted, DeviceFlowUnsupported, authenticate, deviceLogin, describeAuthService, legacyPasswordLogin, logout, normalizeAuthUrl, resolveAuthUrl, } from "../auth/client.js";
|
|
31
|
+
import { AUTH_URL_HINT, DeviceFlowAborted, DeviceFlowUnsupported, authenticate, deviceLogin, describeAuthService, legacyPasswordLogin, tokenLogin, logout, normalizeAuthUrl, resolveAuthUrl, } from "../auth/client.js";
|
|
32
32
|
import { DEFAULT_CONTROL_PORT } from "../control/server.js";
|
|
33
33
|
import { errText } from "../util/log.js";
|
|
34
34
|
import { packageVersion } from "../util/version.js";
|
|
@@ -67,6 +67,7 @@ Options:
|
|
|
67
67
|
--json machine-readable output for status / doctor
|
|
68
68
|
--dry replay against recorded outputs only; run no real tools
|
|
69
69
|
--no-browser login: print the link and code, open nothing (SSH, headless)
|
|
70
|
+
--token <value> login: redeem a one-time setup token from the console (no browser)
|
|
70
71
|
--password login: use the old email/password prompt (deprecated)`);
|
|
71
72
|
process.exit(code);
|
|
72
73
|
}
|
|
@@ -115,6 +116,9 @@ function parseArgs(argv) {
|
|
|
115
116
|
case "--no-browser":
|
|
116
117
|
args.browser = false;
|
|
117
118
|
break;
|
|
119
|
+
case "--token":
|
|
120
|
+
args.token = argv[++i] ?? "";
|
|
121
|
+
break;
|
|
118
122
|
case "--config":
|
|
119
123
|
args.configPath = argv[++i];
|
|
120
124
|
break;
|
|
@@ -540,6 +544,20 @@ async function doctor(args) {
|
|
|
540
544
|
else {
|
|
541
545
|
notes.push(`recording to ${String(health.authUrl ?? "the configured BaseIn service")}`);
|
|
542
546
|
}
|
|
547
|
+
// Whether a recurring sub-task the service found may actually run mid-task
|
|
548
|
+
// (segmented.md R-LIFE-8). Observe-only is the default and must be visible
|
|
549
|
+
// without reading a log: an operator turns arming on by reading what *would*
|
|
550
|
+
// have armed, and needs to know which of the two states this machine is in.
|
|
551
|
+
notes.push(health.segmentArm === true
|
|
552
|
+
? "recurring sub-tasks MAY run mid-task (BIR_SEGMENT_ARM=1)"
|
|
553
|
+
: "recurring sub-tasks are observed only — nothing is replaced (BIR_SEGMENT_ARM unset)");
|
|
554
|
+
const sessions = health.sessions ?? [];
|
|
555
|
+
const probes = sessions.reduce((n, s) => n + (s.intent?.probes ?? 0), 0);
|
|
556
|
+
const armed = sessions.reduce((n, s) => n + (s.intent?.armed ?? 0), 0);
|
|
557
|
+
if (sessions.length > 0) {
|
|
558
|
+
notes.push(`mid-turn matching this session: ${probes} probe${probes === 1 ? "" : "s"}, ` +
|
|
559
|
+
`${armed} armed`);
|
|
560
|
+
}
|
|
543
561
|
}
|
|
544
562
|
else if (!resolveAuthUrl()) {
|
|
545
563
|
// No control server to ask, and nothing in this shell either. That is a
|
|
@@ -902,7 +920,29 @@ async function main() {
|
|
|
902
920
|
return 1;
|
|
903
921
|
}
|
|
904
922
|
let session;
|
|
905
|
-
if (args.
|
|
923
|
+
if (args.token !== undefined) {
|
|
924
|
+
// A setup token from the console: already approved for one account, so
|
|
925
|
+
// there is no page to open and nothing to wait for.
|
|
926
|
+
if (!args.token.trim()) {
|
|
927
|
+
process.stderr.write("[bir] --token needs the token itself: bir login --token <token>\n");
|
|
928
|
+
return 1;
|
|
929
|
+
}
|
|
930
|
+
try {
|
|
931
|
+
session = await tokenLogin(baseUrl, args.token);
|
|
932
|
+
}
|
|
933
|
+
catch (err) {
|
|
934
|
+
if (err instanceof DeviceFlowAborted) {
|
|
935
|
+
process.stderr.write(`[bir] ${err.message}\n`);
|
|
936
|
+
return 1;
|
|
937
|
+
}
|
|
938
|
+
if (err instanceof DeviceFlowUnsupported) {
|
|
939
|
+
process.stderr.write("[bir] this service does not offer setup tokens; run `bir login` without --token.\n");
|
|
940
|
+
return 1;
|
|
941
|
+
}
|
|
942
|
+
throw err;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
else if (args.password) {
|
|
906
946
|
process.stderr.write("[bir] --password is deprecated and will be removed in the next release. " +
|
|
907
947
|
"Browser sign-in works for every account, including Google-only ones.\n");
|
|
908
948
|
session = await legacyPasswordLogin(baseUrl);
|
package/dist/control/server.d.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* token on every route, and never echoes the token back. A loopback port that
|
|
24
24
|
* accepts unauthenticated step reports is a local exfiltration channel.
|
|
25
25
|
*/
|
|
26
|
+
import { type IntentSource } from "./transcript.js";
|
|
26
27
|
import { type Recorder } from "../record/recorder.js";
|
|
27
28
|
import { type ReplayOptions } from "../replay/controller.js";
|
|
28
29
|
/** The subset of the Claude Code hook payload we consume (all optional, all defensive). */
|
|
@@ -41,6 +42,13 @@ export interface HookPayload {
|
|
|
41
42
|
}
|
|
42
43
|
export interface ControlServerOptions {
|
|
43
44
|
recorder: Recorder;
|
|
45
|
+
/**
|
|
46
|
+
* Tool results of the current prompt handed to derivation
|
|
47
|
+
* (`BIR_DERIVE_RECENT_RESULTS`, segmented.md R-PARAM-2). Default 5; 0 off.
|
|
48
|
+
*/
|
|
49
|
+
deriveRecentResults?: number;
|
|
50
|
+
/** `BIR_INTENT_SOURCES` — which intent rungs may be sent (R-INTENT-3). */
|
|
51
|
+
intentSources?: ReadonlySet<IntentSource>;
|
|
44
52
|
/** Session working directory — keys the discovery file. */
|
|
45
53
|
cwd: string;
|
|
46
54
|
/** Preferred port; 0 (or a busy port) falls back to an ephemeral one. */
|
|
@@ -107,6 +115,18 @@ export declare class ControlServer {
|
|
|
107
115
|
private readonly pendingSeals;
|
|
108
116
|
/** Calculated replay. Inert unless `opts.replay.enabled`. */
|
|
109
117
|
private readonly replay;
|
|
118
|
+
/**
|
|
119
|
+
* How many of this prompt's tool results derivation may read
|
|
120
|
+
* (`BIR_DERIVE_RECENT_RESULTS`, segmented.md R-PARAM-2). A target is often
|
|
121
|
+
* named by an earlier step's output and never by the prompt.
|
|
122
|
+
*/
|
|
123
|
+
private readonly deriveRecentResults;
|
|
124
|
+
/**
|
|
125
|
+
* Which rungs of R-INTENT-1 the runner may send (`BIR_INTENT_SOURCES`). It
|
|
126
|
+
* governs only what is *sent*: with every source off, the probe still goes out
|
|
127
|
+
* with an empty text and the server builds the tool line (R-HIT-4).
|
|
128
|
+
*/
|
|
129
|
+
private readonly intentSources;
|
|
110
130
|
constructor(opts: ControlServerOptions);
|
|
111
131
|
listen(): Promise<ControlServerAddress>;
|
|
112
132
|
close(): Promise<void>;
|
|
@@ -167,6 +187,8 @@ export declare class ControlServer {
|
|
|
167
187
|
* false` and zero rather than a number that is confidently wrong.
|
|
168
188
|
*/
|
|
169
189
|
private runCost;
|
|
190
|
+
/** Transcript cost since `mark`; unmeasured (and zero) without one. */
|
|
191
|
+
private costSince;
|
|
170
192
|
private reportExecution;
|
|
171
193
|
/** Seal the run *and* wait for everything queued to reach the service. */
|
|
172
194
|
private finalizeRun;
|
|
@@ -183,6 +205,67 @@ export declare class ControlServer {
|
|
|
183
205
|
* record `tool_selected`. The hook is this step's only observer.
|
|
184
206
|
*/
|
|
185
207
|
private onToolPre;
|
|
208
|
+
/** A controller decision as a `PreToolUse` answer, or undefined to carry on. */
|
|
209
|
+
private preToolAnswer;
|
|
210
|
+
/**
|
|
211
|
+
* Recordings this turn must not count a step hit against (segmented.md
|
|
212
|
+
* R-HIT-7).
|
|
213
|
+
*
|
|
214
|
+
* A repeat of a recording's own prompt is *prompt* recurrence, counted as
|
|
215
|
+
* `iterations`, never step recurrence. Without this, three prompt repeats
|
|
216
|
+
* would reach `SEGMENT_MIN_HITS` on every step of the whole recording and the
|
|
217
|
+
* detector would build a whole-run-sized segment beside the whole-run
|
|
218
|
+
* scenario.
|
|
219
|
+
*/
|
|
220
|
+
private excludeRunsFor;
|
|
221
|
+
/**
|
|
222
|
+
* Per recording a plan of this turn came from, how far that plan got
|
|
223
|
+
* (segmented.md R-OUT-6).
|
|
224
|
+
*
|
|
225
|
+
* The server uses it to refuse a segment that starts *before* where this turn
|
|
226
|
+
* already is: re-running steps the turn has just done is worse than not
|
|
227
|
+
* replaying at all. The largest position per recording wins, and a plan
|
|
228
|
+
* declined for anything but a known-bad first step reports nothing — it ran
|
|
229
|
+
* none of the recording, so it blocks none of it.
|
|
230
|
+
*
|
|
231
|
+
* Rebuilt on every probe rather than cached: a plan that hands over between
|
|
232
|
+
* two calls changes the answer.
|
|
233
|
+
*/
|
|
234
|
+
private ranThroughFor;
|
|
235
|
+
/**
|
|
236
|
+
* Intent matching in the ReAct loop (fallbk.md §Runner 4, segmented.md 10.3).
|
|
237
|
+
*
|
|
238
|
+
* Called only while the model is driving. Every unsteered, non-housekeeping
|
|
239
|
+
* tool call is sent: the service counts it as a hit on whatever recorded step
|
|
240
|
+
* it matches, and may hand back a scenario or a segment to run in its place.
|
|
241
|
+
*
|
|
242
|
+
* A call made with **no reasoning at all** still probes (R-HIT-4): the server
|
|
243
|
+
* builds an intent from the tool name and arguments, and a step the agent took
|
|
244
|
+
* without narrating it is exactly as much a recurrence as one it explained.
|
|
245
|
+
* Every failure is a miss.
|
|
246
|
+
*/
|
|
247
|
+
private tryIntentMatch;
|
|
248
|
+
/**
|
|
249
|
+
* Schedule a fragment once a matched turn's plan has handed over (fallbk.md
|
|
250
|
+
* D7). Only a turn that is not already recording needs one: an unmatched turn
|
|
251
|
+
* whose intent-armed plan handed over is recording the model's work anyway.
|
|
252
|
+
*/
|
|
253
|
+
private noteHandover;
|
|
254
|
+
/**
|
|
255
|
+
* Open the fragment run and record from this call on (fallbk.md D7).
|
|
256
|
+
*
|
|
257
|
+
* The run id is swapped in place: steps allocated before belonged to a turn
|
|
258
|
+
* the service never created a run for, and nothing of theirs may land in the
|
|
259
|
+
* fragment — so pending correlations are latched and built-ins forgotten.
|
|
260
|
+
*/
|
|
261
|
+
private openFragment;
|
|
262
|
+
/** Resolve with `p`, or null once `ms` passes or it rejects. */
|
|
263
|
+
private withinBudget;
|
|
264
|
+
/**
|
|
265
|
+
* `PostToolUse`, and where a hand-over noticed while threading this call's
|
|
266
|
+
* output reaches the model at once, as `additionalContext` (fallbk.md D4).
|
|
267
|
+
*/
|
|
268
|
+
private onToolPost;
|
|
186
269
|
/**
|
|
187
270
|
* `PostToolUse` / `PostToolUseFailure`. For a built-in this closes the pair.
|
|
188
271
|
* For a correlated MCP call it normally does nothing — the proxy owns that
|
|
@@ -190,7 +273,7 @@ export declare class ControlServer {
|
|
|
190
273
|
* or the call never reached it), the hook's own view is recorded after
|
|
191
274
|
* {@link PROXY_REPORT_GRACE_MS} rather than the step being lost entirely.
|
|
192
275
|
*/
|
|
193
|
-
private
|
|
276
|
+
private toolPost;
|
|
194
277
|
private errorText;
|
|
195
278
|
/**
|
|
196
279
|
* `SubagentStart` / `SubagentStop`. These carry `agent_id`, which is the only
|