@basein/runner 0.2.12 → 0.3.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.
Files changed (142) hide show
  1. package/README.md +540 -369
  2. package/dist/auth/client.d.ts +65 -3
  3. package/dist/auth/client.js +305 -53
  4. package/dist/bin/bir-hooks.d.ts +72 -31
  5. package/dist/bin/bir-hooks.js +176 -284
  6. package/dist/bin/bir-proxy.d.ts +17 -2
  7. package/dist/bin/bir-proxy.js +222 -28
  8. package/dist/bin/bir-scenario.d.ts +61 -21
  9. package/dist/bin/bir-scenario.js +593 -122
  10. package/dist/bin/bir.d.ts +12 -6
  11. package/dist/bin/bir.js +2935 -489
  12. package/dist/bin/investigate.js +40 -15
  13. package/dist/bin/scenario-edit.d.ts +7 -1
  14. package/dist/bin/scenario-edit.js +43 -21
  15. package/dist/bin/scenario-liveness.d.ts +194 -0
  16. package/dist/bin/scenario-liveness.js +459 -0
  17. package/dist/bin/scenario-stderr.d.ts +43 -0
  18. package/dist/bin/scenario-stderr.js +76 -0
  19. package/dist/bin/service-read.d.ts +88 -0
  20. package/dist/bin/service-read.js +128 -0
  21. package/dist/bin/setup.d.ts +19 -0
  22. package/dist/bin/setup.js +146 -20
  23. package/dist/config/adapters/claude-code.d.ts +92 -14
  24. package/dist/config/adapters/claude-code.js +291 -101
  25. package/dist/config/adapters/generic.d.ts +4 -1
  26. package/dist/config/adapters/generic.js +13 -14
  27. package/dist/config/generate.d.ts +392 -14
  28. package/dist/config/generate.js +666 -19
  29. package/dist/config/mcp-approval.d.ts +76 -0
  30. package/dist/config/mcp-approval.js +210 -0
  31. package/dist/config/node-path.d.ts +42 -0
  32. package/dist/config/node-path.js +102 -0
  33. package/dist/config/overrides.d.ts +62 -0
  34. package/dist/config/overrides.js +136 -0
  35. package/dist/config/resolve.d.ts +70 -8
  36. package/dist/config/resolve.js +186 -15
  37. package/dist/control/client.d.ts +22 -0
  38. package/dist/control/client.js +26 -7
  39. package/dist/control/correlation.d.ts +23 -5
  40. package/dist/control/correlation.js +0 -0
  41. package/dist/control/daemon.d.ts +400 -18
  42. package/dist/control/daemon.js +1105 -102
  43. package/dist/control/discovery.d.ts +77 -5
  44. package/dist/control/discovery.js +186 -11
  45. package/dist/control/ensure-hook.d.ts +61 -0
  46. package/dist/control/ensure-hook.js +160 -8
  47. package/dist/control/paths.d.ts +26 -0
  48. package/dist/control/paths.js +34 -0
  49. package/dist/control/pin-guard.d.ts +83 -0
  50. package/dist/control/pin-guard.js +534 -0
  51. package/dist/control/recorder-keeper.d.ts +229 -0
  52. package/dist/control/recorder-keeper.js +721 -0
  53. package/dist/control/recorder-main.d.ts +73 -0
  54. package/dist/control/recorder-main.js +778 -0
  55. package/dist/control/server.d.ts +707 -31
  56. package/dist/control/server.js +2787 -302
  57. package/dist/control/transcript.d.ts +21 -1
  58. package/dist/control/transcript.js +378 -112
  59. package/dist/index.d.ts +5 -1
  60. package/dist/index.js +5 -1
  61. package/dist/jsonrpc/framing.d.ts +76 -12
  62. package/dist/jsonrpc/framing.js +216 -59
  63. package/dist/proxy/intercept.d.ts +49 -5
  64. package/dist/proxy/intercept.js +188 -59
  65. package/dist/proxy/relay.d.ts +16 -0
  66. package/dist/proxy/relay.js +77 -5
  67. package/dist/proxy/revive.d.ts +122 -0
  68. package/dist/proxy/revive.js +251 -0
  69. package/dist/proxy/session.d.ts +222 -14
  70. package/dist/proxy/session.js +708 -99
  71. package/dist/record/recorder.d.ts +51 -3
  72. package/dist/record/recorder.js +3 -0
  73. package/dist/record/redact.d.ts +103 -9
  74. package/dist/record/redact.js +659 -30
  75. package/dist/record/remote-recorder.d.ts +327 -32
  76. package/dist/record/remote-recorder.js +1517 -187
  77. package/dist/record/switchable-recorder.d.ts +126 -0
  78. package/dist/record/switchable-recorder.js +351 -0
  79. package/dist/record/truncate.d.ts +40 -1
  80. package/dist/record/truncate.js +111 -4
  81. package/dist/replay/bash-channel.d.ts +35 -0
  82. package/dist/replay/bash-channel.js +133 -0
  83. package/dist/replay/bundle.d.ts +10 -0
  84. package/dist/replay/bundle.js +10 -0
  85. package/dist/replay/controller.d.ts +369 -20
  86. package/dist/replay/controller.js +1176 -107
  87. package/dist/replay/derive.d.ts +1 -0
  88. package/dist/replay/derive.js +81 -24
  89. package/dist/replay/executor.d.ts +51 -16
  90. package/dist/replay/executor.js +114 -41
  91. package/dist/replay/handover.d.ts +54 -1
  92. package/dist/replay/handover.js +55 -5
  93. package/dist/replay/logic.d.ts +62 -10
  94. package/dist/replay/logic.js +140 -22
  95. package/dist/replay/plan.d.ts +201 -27
  96. package/dist/replay/plan.js +581 -141
  97. package/dist/replay/safety.d.ts +72 -0
  98. package/dist/replay/safety.js +303 -0
  99. package/dist/replay/source-run.d.ts +10 -0
  100. package/dist/replay/source-run.js +80 -35
  101. package/dist/replay/targets.d.ts +15 -1
  102. package/dist/replay/targets.js +62 -4
  103. package/dist/replay/tool-error.d.ts +15 -1
  104. package/dist/replay/tool-error.js +33 -1
  105. package/dist/replay/types.d.ts +7 -4
  106. package/dist/upstream/client.d.ts +56 -3
  107. package/dist/upstream/client.js +61 -4
  108. package/dist/upstream/headers.d.ts +43 -0
  109. package/dist/upstream/headers.js +90 -0
  110. package/dist/upstream/http-client.d.ts +213 -22
  111. package/dist/upstream/http-client.js +1034 -168
  112. package/dist/upstream/http-transport.d.ts +143 -0
  113. package/dist/upstream/http-transport.js +391 -0
  114. package/dist/upstream/lazy-client.d.ts +8 -0
  115. package/dist/upstream/lazy-client.js +29 -1
  116. package/dist/upstream/sse.d.ts +78 -0
  117. package/dist/upstream/sse.js +189 -0
  118. package/dist/upstream/stdio-client.d.ts +61 -1
  119. package/dist/upstream/stdio-client.js +356 -33
  120. package/dist/util/atomic-write.d.ts +29 -0
  121. package/dist/util/atomic-write.js +96 -0
  122. package/dist/util/journal.d.ts +15 -0
  123. package/dist/util/journal.js +48 -3
  124. package/dist/util/log.d.ts +10 -0
  125. package/dist/util/log.js +28 -1
  126. package/dist/util/off-switch.d.ts +46 -0
  127. package/dist/util/off-switch.js +91 -0
  128. package/dist/util/service-health.d.ts +133 -0
  129. package/dist/util/service-health.js +217 -0
  130. package/docs/BaseInstRunner.md +694 -621
  131. package/docs/calculatedReplay.md +1625 -1304
  132. package/docs/calculatedReplayGuide.md +1212 -1087
  133. package/docs/installRun.md +1013 -772
  134. package/docs/loginWeb.md +627 -607
  135. package/docs/mcpmark.md +49 -16
  136. package/docs/my-first-sample.md +569 -545
  137. package/docs/quickstart.md +384 -311
  138. package/docs/t-bench.md +406 -394
  139. package/package.json +58 -57
  140. package/production.md +1371 -0
  141. package/scripts/install.ps1 +97 -6
  142. 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
- /** Await every queued send for a run. Called once at session end. */
61
- flush(runId: string): Promise<void>;
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
  /**
@@ -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
  }
@@ -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 matching {@link SECRET_KEY} is replaced
11
- * wholesale, whatever its type. `authorization: { scheme, token }` loses
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 JWT, an
17
- * `sk-`/`ghp_`-style key or an AWS access key id is replaced wherever it
18
- * appears — including inside free text, where key-matching cannot reach.
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
- /** Keys whose values are secret regardless of shape. */
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
- /** Replace every secret-shaped run inside a string. */
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.