@basein/runner 0.2.12 → 0.3.0
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 +540 -369
- package/dist/auth/client.d.ts +65 -3
- package/dist/auth/client.js +305 -53
- package/dist/bin/bir-hooks.d.ts +72 -31
- package/dist/bin/bir-hooks.js +176 -284
- package/dist/bin/bir-proxy.d.ts +17 -2
- package/dist/bin/bir-proxy.js +222 -28
- package/dist/bin/bir-scenario.d.ts +61 -21
- package/dist/bin/bir-scenario.js +593 -122
- package/dist/bin/bir.d.ts +12 -6
- package/dist/bin/bir.js +2935 -489
- package/dist/bin/investigate.js +40 -15
- package/dist/bin/scenario-edit.d.ts +7 -1
- package/dist/bin/scenario-edit.js +43 -21
- package/dist/bin/scenario-liveness.d.ts +194 -0
- package/dist/bin/scenario-liveness.js +459 -0
- package/dist/bin/scenario-stderr.d.ts +43 -0
- package/dist/bin/scenario-stderr.js +76 -0
- package/dist/bin/service-read.d.ts +88 -0
- package/dist/bin/service-read.js +128 -0
- package/dist/bin/setup.d.ts +19 -0
- package/dist/bin/setup.js +146 -20
- package/dist/config/adapters/claude-code.d.ts +92 -14
- package/dist/config/adapters/claude-code.js +291 -101
- package/dist/config/adapters/generic.d.ts +4 -1
- package/dist/config/adapters/generic.js +13 -14
- package/dist/config/generate.d.ts +392 -14
- package/dist/config/generate.js +666 -19
- package/dist/config/mcp-approval.d.ts +76 -0
- package/dist/config/mcp-approval.js +210 -0
- package/dist/config/node-path.d.ts +42 -0
- package/dist/config/node-path.js +102 -0
- package/dist/config/overrides.d.ts +62 -0
- package/dist/config/overrides.js +136 -0
- package/dist/config/resolve.d.ts +70 -8
- package/dist/config/resolve.js +186 -15
- package/dist/control/client.d.ts +22 -0
- package/dist/control/client.js +26 -7
- package/dist/control/correlation.d.ts +23 -5
- package/dist/control/correlation.js +0 -0
- package/dist/control/daemon.d.ts +400 -18
- package/dist/control/daemon.js +1105 -102
- package/dist/control/discovery.d.ts +77 -5
- package/dist/control/discovery.js +186 -11
- package/dist/control/ensure-hook.d.ts +61 -0
- package/dist/control/ensure-hook.js +160 -8
- package/dist/control/paths.d.ts +26 -0
- package/dist/control/paths.js +34 -0
- package/dist/control/pin-guard.d.ts +83 -0
- package/dist/control/pin-guard.js +534 -0
- package/dist/control/recorder-keeper.d.ts +229 -0
- package/dist/control/recorder-keeper.js +721 -0
- package/dist/control/recorder-main.d.ts +73 -0
- package/dist/control/recorder-main.js +778 -0
- package/dist/control/server.d.ts +707 -31
- package/dist/control/server.js +2786 -302
- package/dist/control/transcript.d.ts +21 -1
- package/dist/control/transcript.js +378 -112
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/jsonrpc/framing.d.ts +76 -12
- package/dist/jsonrpc/framing.js +216 -59
- package/dist/proxy/intercept.d.ts +49 -5
- package/dist/proxy/intercept.js +188 -59
- package/dist/proxy/relay.d.ts +16 -0
- package/dist/proxy/relay.js +77 -5
- package/dist/proxy/revive.d.ts +122 -0
- package/dist/proxy/revive.js +251 -0
- package/dist/proxy/session.d.ts +222 -14
- package/dist/proxy/session.js +708 -99
- package/dist/record/recorder.d.ts +51 -3
- package/dist/record/recorder.js +3 -0
- package/dist/record/redact.d.ts +103 -9
- package/dist/record/redact.js +659 -30
- package/dist/record/remote-recorder.d.ts +327 -32
- package/dist/record/remote-recorder.js +1517 -187
- package/dist/record/switchable-recorder.d.ts +126 -0
- package/dist/record/switchable-recorder.js +351 -0
- package/dist/record/truncate.d.ts +40 -1
- package/dist/record/truncate.js +111 -4
- package/dist/replay/bash-channel.d.ts +35 -0
- package/dist/replay/bash-channel.js +133 -0
- package/dist/replay/bundle.d.ts +10 -0
- package/dist/replay/bundle.js +10 -0
- package/dist/replay/controller.d.ts +369 -20
- package/dist/replay/controller.js +1176 -107
- package/dist/replay/derive.d.ts +1 -0
- package/dist/replay/derive.js +81 -24
- package/dist/replay/executor.d.ts +51 -16
- package/dist/replay/executor.js +114 -41
- package/dist/replay/handover.d.ts +54 -1
- package/dist/replay/handover.js +55 -5
- package/dist/replay/logic.d.ts +62 -10
- package/dist/replay/logic.js +140 -22
- package/dist/replay/plan.d.ts +201 -27
- package/dist/replay/plan.js +581 -141
- package/dist/replay/safety.d.ts +72 -0
- package/dist/replay/safety.js +303 -0
- package/dist/replay/source-run.d.ts +10 -0
- package/dist/replay/source-run.js +80 -35
- package/dist/replay/targets.d.ts +15 -1
- package/dist/replay/targets.js +62 -4
- package/dist/replay/tool-error.d.ts +15 -1
- package/dist/replay/tool-error.js +33 -1
- package/dist/replay/types.d.ts +7 -4
- package/dist/upstream/client.d.ts +56 -3
- package/dist/upstream/client.js +61 -4
- package/dist/upstream/headers.d.ts +43 -0
- package/dist/upstream/headers.js +90 -0
- package/dist/upstream/http-client.d.ts +213 -22
- package/dist/upstream/http-client.js +1034 -168
- package/dist/upstream/http-transport.d.ts +143 -0
- package/dist/upstream/http-transport.js +391 -0
- package/dist/upstream/lazy-client.d.ts +8 -0
- package/dist/upstream/lazy-client.js +29 -1
- package/dist/upstream/sse.d.ts +78 -0
- package/dist/upstream/sse.js +189 -0
- package/dist/upstream/stdio-client.d.ts +61 -1
- package/dist/upstream/stdio-client.js +356 -33
- package/dist/util/atomic-write.d.ts +29 -0
- package/dist/util/atomic-write.js +96 -0
- package/dist/util/journal.d.ts +15 -0
- package/dist/util/journal.js +48 -3
- package/dist/util/log.d.ts +10 -0
- package/dist/util/log.js +28 -1
- package/dist/util/off-switch.d.ts +46 -0
- package/dist/util/off-switch.js +91 -0
- package/dist/util/service-health.d.ts +133 -0
- package/dist/util/service-health.js +217 -0
- package/docs/BaseInstRunner.md +694 -621
- package/docs/calculatedReplay.md +1625 -1304
- package/docs/calculatedReplayGuide.md +1212 -1087
- package/docs/installRun.md +1013 -772
- package/docs/loginWeb.md +627 -607
- package/docs/mcpmark.md +49 -16
- package/docs/my-first-sample.md +569 -545
- package/docs/quickstart.md +384 -311
- package/docs/t-bench.md +406 -394
- package/package.json +58 -57
- package/production.md +1371 -0
- package/scripts/install.ps1 +97 -6
- package/scripts/install.sh +104 -7
|
@@ -16,6 +16,17 @@ export interface RunMetrics {
|
|
|
16
16
|
costUsd?: number;
|
|
17
17
|
durationMs?: number;
|
|
18
18
|
}
|
|
19
|
+
/** How long a caller is prepared to wait for {@link Recorder.flush}. */
|
|
20
|
+
export interface FlushOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Stop waiting after this long; the sends carry on in the background. A
|
|
23
|
+
* recorder picks a bounded default when this is absent (RemoteRecorder: 2 s,
|
|
24
|
+
* `BIR_FLUSH_TIMEOUT_MS`), because a flush behind a dead service must never
|
|
25
|
+
* be what holds a hook or a shutdown — and it stops waiting sooner, at once,
|
|
26
|
+
* when the service is known to be failing (the health verdict's cool-down).
|
|
27
|
+
*/
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}
|
|
19
30
|
export interface Recorder {
|
|
20
31
|
/**
|
|
21
32
|
* Create the run; returns the client-generated runId synchronously.
|
|
@@ -57,9 +68,39 @@ export interface Recorder {
|
|
|
57
68
|
answer: string;
|
|
58
69
|
}): string;
|
|
59
70
|
finishRun(runId: string, finalOutput?: string, metrics?: RunMetrics): void;
|
|
60
|
-
/**
|
|
61
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Await every queued send for a run. Called once at session end. Bounded:
|
|
73
|
+
* see {@link FlushOptions}.
|
|
74
|
+
*/
|
|
75
|
+
flush(runId: string, opts?: FlushOptions): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What a recorder can say about its own sending, for `/health` and
|
|
79
|
+
* `bir doctor`. The queue a hook server keeps drains instantly; the real
|
|
80
|
+
* backlog is here, so this is where "lossy" is actually known.
|
|
81
|
+
*/
|
|
82
|
+
export interface RecorderHealth {
|
|
83
|
+
/**
|
|
84
|
+
* `recording`, or why nothing is being sent: `signed_out` (credentials.json
|
|
85
|
+
* was removed — `bir logout`), `auth_rejected` (the service refused the
|
|
86
|
+
* session and there was nothing newer to adopt), `other_service` (the
|
|
87
|
+
* credentials now belong to a different service address).
|
|
88
|
+
*/
|
|
89
|
+
state: "recording" | "signed_out" | "auth_rejected" | "other_service";
|
|
90
|
+
/** Sends queued and not yet started. */
|
|
91
|
+
pendingSends: number;
|
|
92
|
+
/** Their approximate size. */
|
|
93
|
+
pendingBytes: number;
|
|
94
|
+
/** Sends dropped since start: the backlog bound, a signed-out session, a run the service never created. */
|
|
95
|
+
droppedSends: number;
|
|
96
|
+
/** Sends that were tried and failed since start. */
|
|
97
|
+
failedSends: number;
|
|
98
|
+
}
|
|
99
|
+
/** Optional capability: a recorder that knows its own backlog. */
|
|
100
|
+
export interface RecorderHealthReporter {
|
|
101
|
+
recorderHealth(): RecorderHealth;
|
|
62
102
|
}
|
|
103
|
+
export declare function isRecorderHealthReporter(r: Recorder): r is Recorder & RecorderHealthReporter;
|
|
63
104
|
/**
|
|
64
105
|
* A similar-prompt hit reported by `POST /recordings/runs`. On a hit the server
|
|
65
106
|
* does **not** create a run, so every later step post for that id would 404 —
|
|
@@ -200,9 +241,16 @@ export interface IntentMatchAnswer {
|
|
|
200
241
|
bestStepSimilarity?: number;
|
|
201
242
|
segmentWouldArm?: SegmentWouldArm;
|
|
202
243
|
}
|
|
244
|
+
/** How long a probe's caller will wait; the request is abandoned past it. */
|
|
245
|
+
export interface IntentMatchOptions {
|
|
246
|
+
/** Abort the request (and free its socket) when the caller stops waiting. */
|
|
247
|
+
signal?: AbortSignal;
|
|
248
|
+
/** A deadline for the request itself. A recorder picks a short default. */
|
|
249
|
+
timeoutMs?: number;
|
|
250
|
+
}
|
|
203
251
|
/** Optional capability: intent matching needs a service. */
|
|
204
252
|
export interface IntentMatcher {
|
|
205
|
-
matchIntent(req: IntentMatchRequest): Promise<IntentMatchAnswer | null>;
|
|
253
|
+
matchIntent(req: IntentMatchRequest, opts?: IntentMatchOptions): Promise<IntentMatchAnswer | null>;
|
|
206
254
|
}
|
|
207
255
|
export declare function isIntentMatcher(r: Recorder): r is Recorder & IntentMatcher;
|
|
208
256
|
/**
|
package/dist/record/recorder.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* service needs no new endpoints for v1: a BaseInstRunner run is the same row
|
|
6
6
|
* shape as an RRepeat one, discriminated only by `metadata.recorder`.
|
|
7
7
|
*/
|
|
8
|
+
export function isRecorderHealthReporter(r) {
|
|
9
|
+
return typeof r.recorderHealth === "function";
|
|
10
|
+
}
|
|
8
11
|
export function isMatchAware(r) {
|
|
9
12
|
return typeof r.getMatch === "function";
|
|
10
13
|
}
|
package/dist/record/redact.d.ts
CHANGED
|
@@ -4,28 +4,122 @@
|
|
|
4
4
|
* The proxy sits on the credential path for every wrapped server (§9), and every
|
|
5
5
|
* argument and result it sees crosses a process boundary and then the network.
|
|
6
6
|
* So redaction runs on the recording copy of both arguments *and* results,
|
|
7
|
-
* before the step report is even queued
|
|
7
|
+
* before the step report is even queued — and once more, as the last line of
|
|
8
|
+
* defence, on every free-text field `RemoteRecorder` sends (the prompt, the
|
|
9
|
+
* answer, the reasoning, error messages), because those never pass through a
|
|
10
|
+
* structured payload at all.
|
|
8
11
|
*
|
|
9
12
|
* Three rules, in the order they fire:
|
|
10
|
-
* 1. **By key.** A value under a key
|
|
11
|
-
* wholesale, whatever its type. `authorization: { scheme, token }`
|
|
12
|
-
* both halves, because the object as a whole is the secret.
|
|
13
|
+
* 1. **By key.** A value under a key that names a secret ({@link isSecretKey})
|
|
14
|
+
* is replaced wholesale, whatever its type. `authorization: { scheme, token }`
|
|
15
|
+
* loses both halves, because the object as a whole is the secret.
|
|
13
16
|
* 2. **`env` blocks.** An object under an `env` key has *every* value replaced.
|
|
14
17
|
* MCP server configs put credentials there under arbitrary names, so no key
|
|
15
18
|
* pattern would catch them.
|
|
16
|
-
* 3. **By shape.** A string that looks like a bearer token, a
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* 3. **By shape.** A string that looks like a credential — a bearer token, a
|
|
20
|
+
* JWT, a provider key, a PEM private key, a password in a URL, a
|
|
21
|
+
* `DB_PASSWORD=…` line of a `.env` file, an `X-Api-Key:` header — is
|
|
22
|
+
* replaced wherever it appears, including inside free text, where key
|
|
23
|
+
* matching cannot reach. Reading `.env` or `~/.aws/credentials` is a routine
|
|
24
|
+
* thing for an agent to do; its result is recorded like any other.
|
|
25
|
+
*
|
|
26
|
+
* KEYS ARE MATCHED ON WHOLE NAME SEGMENTS, not substrings. The old rule was one
|
|
27
|
+
* unanchored alternation, so `author`, `max_tokens`, `nextPageToken` and
|
|
28
|
+
* `tokenizer` were all "secrets" — the recording lost real arguments (numbers
|
|
29
|
+
* became the string "[redacted]") and a scenario built from it had nothing to
|
|
30
|
+
* replay. Names are split on camelCase, snake_case, kebab-case and dots, and a
|
|
31
|
+
* segment has to *be* the secret word (`token`, `accessToken`, `x-api-key`), not
|
|
32
|
+
* merely contain it. The names 0.2.12's substring rule caught that do hold
|
|
33
|
+
* credentials are still caught: plurals (`tokens`, `api_keys`), an `auth` that
|
|
34
|
+
* ends the name (`basic_auth`, `X-Upstream-Auth`), `oauth`, `authentication`,
|
|
35
|
+
* `auth_code` — and a number under the secret itself (`token: 482913`, an OTP).
|
|
36
|
+
*
|
|
37
|
+
* TEXT RULES ARE ANCHORED TO WHERE SECRETS ARE WRITTEN, not to every
|
|
38
|
+
* `name = value` in sight. A recorded Read of source code must survive: its
|
|
39
|
+
* bytes are what a scenario's output logic is authored against, and
|
|
40
|
+
* `const token = await getToken()` is not a secret. So assignments are only
|
|
41
|
+
* redacted in env/ini/YAML line form, shell `NAME=value` form, header form and
|
|
42
|
+
* JSON `"key": "value"` form, and never when the value is visibly code (a call,
|
|
43
|
+
* a member access, a `${reference}`, a type in a declaration such as
|
|
44
|
+
* `password: string`, or a variable named for the same thing such as
|
|
45
|
+
* `token: newToken`). A separator never spans a line break: an empty
|
|
46
|
+
* `DB_PASSWORD=` must not take the next line with it.
|
|
47
|
+
*
|
|
48
|
+
* JSON TEXT IS REDACTED AS JSON. A serialized tool payload goes through
|
|
49
|
+
* {@link redactJsonText}, which parses it and runs the rules on each string
|
|
50
|
+
* leaf — the same pass its producer's {@link redact} made — so the result is
|
|
51
|
+
* always valid JSON and, when nothing new was found, the producer's bytes
|
|
52
|
+
* exactly (live replay serializes a result the same way and compares). The
|
|
53
|
+
* rules still tolerate JSON-escaped text (a string leaf that holds a JSON
|
|
54
|
+
* document, a preview of one): they never start a match inside an escape
|
|
55
|
+
* sequence and never replace a quote or a backslash, so an escaped document
|
|
56
|
+
* stays parseable.
|
|
19
57
|
*
|
|
20
58
|
* Redaction never throws and never mutates its input: a cycle, a `BigInt`, a
|
|
21
59
|
* getter that explodes — each degrades to a placeholder rather than losing the
|
|
22
60
|
* step or, worse, taking the session down.
|
|
23
61
|
*/
|
|
24
62
|
export declare const REDACTED = "[redacted]";
|
|
25
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* The substring rule this module used before key segments. Kept exported for
|
|
65
|
+
* anything that imported it, and applied again under `BIR_REDACT_LEGACY_KEYS=1`.
|
|
66
|
+
*
|
|
67
|
+
* @deprecated over-matches ordinary names (`author`, `max_tokens`); use {@link isSecretKey}.
|
|
68
|
+
*/
|
|
26
69
|
export declare const SECRET_KEY: RegExp;
|
|
27
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* A string leaf longer than this is cut before its shapes are scanned. Every
|
|
72
|
+
* consumer caps a recorded payload far below it (64 KiB), so the cut is never
|
|
73
|
+
* visible in what is sent — it only stops a 20 MB tool result from costing a
|
|
74
|
+
* few hundred milliseconds of regex work on a hook's path.
|
|
75
|
+
*/
|
|
76
|
+
export declare const REDACT_SCAN_MAX_CHARS: number;
|
|
77
|
+
/**
|
|
78
|
+
* How strongly a key names a secret. `strong` redacts any value but a boolean
|
|
79
|
+
* — the secret itself (`password`, `token`, `api_key`, `mfa_token`) may well
|
|
80
|
+
* be a number: a one-time code, a PIN, a numeric key. `weak` (a session id,
|
|
81
|
+
* something *about* a token such as `token_id`, a list such as `tokens`)
|
|
82
|
+
* keeps numbers and booleans too, which are ids, counters and flags there far
|
|
83
|
+
* more often than they are credentials.
|
|
84
|
+
*/
|
|
85
|
+
export type SecretKeyStrength = "strong" | "weak";
|
|
86
|
+
export declare function secretKeyStrength(name: string): SecretKeyStrength | undefined;
|
|
87
|
+
/** True when a value under `name` is a secret (see {@link secretKeyStrength}). */
|
|
88
|
+
export declare function isSecretKey(name: string): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* The same question for an UPPER_SNAKE environment variable name, where the
|
|
91
|
+
* words run together (`PGPASSWORD`, `GITHUB_TOKEN`, `AWS_SECRET_ACCESS_KEY`)
|
|
92
|
+
* and substring matching is the convention rather than the mistake — minus
|
|
93
|
+
* the counters (`MAX_TOKENS`, `TOKENIZERS_PARALLELISM`) and file pointers
|
|
94
|
+
* (`PASSWORD_FILE`, the Docker secrets convention).
|
|
95
|
+
*/
|
|
96
|
+
export declare function isSecretEnvName(name: string): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Replace every secret-shaped run inside a string.
|
|
99
|
+
*
|
|
100
|
+
* Idempotent: redacting the result again changes nothing. That is what lets a
|
|
101
|
+
* payload be redacted by its producer and again at the recorder without its
|
|
102
|
+
* bytes moving (live replay compares against them). One pass is not always a
|
|
103
|
+
* fixed point — a match one rule declined (`Authorization: Authorization: …`)
|
|
104
|
+
* can hide a secret that only shows once a later rule has run — so a pass that
|
|
105
|
+
* changed something is followed by another, until one changes nothing.
|
|
106
|
+
*/
|
|
28
107
|
export declare function redactText(text: string): string;
|
|
108
|
+
/**
|
|
109
|
+
* Redact a string that holds JSON — a serialized tool input, result or error —
|
|
110
|
+
* and return JSON, always.
|
|
111
|
+
*
|
|
112
|
+
* Running {@link redactText} over the serialized text could not promise that:
|
|
113
|
+
* a rule that met `\n` after an empty `KEY=` wrote `KEY=\[redacted]`, and the
|
|
114
|
+
* service stored a payload no parser accepts. So the text is parsed and every
|
|
115
|
+
* string leaf gets the rules as plain text, exactly as its producer's
|
|
116
|
+
* {@link redact} did. When that finds nothing new — the normal case, since
|
|
117
|
+
* producers redact first — the original bytes come back untouched: live replay
|
|
118
|
+
* serializes a result the same way and compares against what was recorded.
|
|
119
|
+
* Text that is not JSON (a truncated preview, a producer's plain error) gets
|
|
120
|
+
* {@link redactText}: there is no structure to keep.
|
|
121
|
+
*/
|
|
122
|
+
export declare function redactJsonText(text: string): string;
|
|
29
123
|
/**
|
|
30
124
|
* Deep-clone `value` with secrets removed. The result is always JSON-safe, so
|
|
31
125
|
* the caller can serialise it without a second failure mode.
|