@basein/runner 0.3.1 → 0.3.3
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/bin/bir-hooks.d.ts +12 -0
- package/dist/bin/bir-hooks.js +12 -0
- package/dist/control/recorder-main.js +13 -0
- package/dist/control/server.js +12 -0
- package/dist/replay/builtins.d.ts +135 -0
- package/dist/replay/builtins.js +674 -0
- package/dist/replay/controller.d.ts +56 -0
- package/dist/replay/controller.js +72 -5
- package/dist/replay/coverage.d.ts +20 -7
- package/dist/replay/coverage.js +27 -10
- package/docs/calculatedReplay.md +15 -3
- package/docs/calculatedReplayGuide.md +4 -2
- package/package.json +1 -1
- package/production.md +2 -0
package/README.md
CHANGED
|
@@ -413,6 +413,8 @@ what to do when they register, so there is one switch and no second copy of it:
|
|
|
413
413
|
|---|---|
|
|
414
414
|
| `BIR_REPLAY` | `0` turns replay off and any other value on, over the project's stored switch (`bir replay on` / `off`; a first plain `bir install` stores off). Nothing below matters while it is off. |
|
|
415
415
|
| `BIR_REPLAY_ALLOW_SERVERS` | Server keys bir may call unattended: **direct** execution, and a steered plan's recovery. Unset means the list `bir replay allow` stored for the project, else every wrapped server; narrowing it is the recommendation. |
|
|
416
|
+
| `BIR_BUILTIN_TOOLS` | Built-in tools bir may execute itself during a replay — in the session's own project directory, and confined to it. Default `Read,Glob,Grep` (read-only). Add `Edit,Write,Bash` only deliberately: a runner-side step never reaches Claude Code's permission prompts. `none` turns it off, and a built-in step hands the turn back as before. |
|
|
417
|
+
| `BIR_BASH_ALLOW` | Command prefixes a runner-side `Bash` step may run (`*` for all). Unset, `Bash` never executes on the runner, even when `BIR_BUILTIN_TOOLS` lists it. A plan's Bash steps share a working directory, as Claude Code's do (variables and functions do not persist, also as in Claude Code). |
|
|
416
418
|
| `BIR_MIN_STEER_SIMILARITY` | Minimum match similarity to replay (default `0.92`, above the service's own `0.9` detection threshold). |
|
|
417
419
|
| `BIR_DERIVE_API_KEY` | **Optional.** Working out what a new request is about — which fleet, which file, which date — is done for you by the service on its own key, as long as you are signed in. Set this (an Anthropic key) only to keep that reading on your machine, on your key. Signed out *and* unset, a scenario whose values change between requests is declined rather than replayed on stale ones. |
|
|
418
420
|
| `ANTHROPIC_API_KEY` | Not used by the background recorder, which has Claude Code's environment and would bill your own key — unless `BIR_USE_ANTHROPIC_API_KEY=1`. A `bir-hooks` run by hand in a terminal uses that terminal's key. |
|
package/dist/bin/bir-hooks.d.ts
CHANGED
|
@@ -61,6 +61,18 @@
|
|
|
61
61
|
* mode; every other tool call is asked as usual.
|
|
62
62
|
* BIR_REPLAY_ALLOW_SERVERS comma-separated server keys eligible for *direct*
|
|
63
63
|
* execution. Unset means every wrapped server
|
|
64
|
+
* BIR_BUILTIN_TOOLS comma-separated built-ins the runner may execute
|
|
65
|
+
* itself during a replay, in the session's own
|
|
66
|
+
* project directory and confined to it
|
|
67
|
+
* (replay/builtins.ts). Default Read,Glob,Grep —
|
|
68
|
+
* the read-only set. Add Edit,Write,Bash only
|
|
69
|
+
* deliberately: a runner-side step never reaches
|
|
70
|
+
* the host's permission prompts. `none` turns
|
|
71
|
+
* built-ins off, and then a built-in step hands
|
|
72
|
+
* the turn back, as before
|
|
73
|
+
* BIR_BASH_ALLOW command prefixes a runner-side Bash step may run
|
|
74
|
+
* (`*` for all). Unset, Bash never executes on the
|
|
75
|
+
* runner even when BIR_BUILTIN_TOOLS lists it
|
|
64
76
|
* BIR_MIN_STEER_SIMILARITY minimum match similarity to replay (default 0.92)
|
|
65
77
|
* BIR_DERIVE_API_KEY OPTIONAL. Derivation — reading what this turn acts
|
|
66
78
|
* on — is done by the service for a signed-in
|
package/dist/bin/bir-hooks.js
CHANGED
|
@@ -61,6 +61,18 @@
|
|
|
61
61
|
* mode; every other tool call is asked as usual.
|
|
62
62
|
* BIR_REPLAY_ALLOW_SERVERS comma-separated server keys eligible for *direct*
|
|
63
63
|
* execution. Unset means every wrapped server
|
|
64
|
+
* BIR_BUILTIN_TOOLS comma-separated built-ins the runner may execute
|
|
65
|
+
* itself during a replay, in the session's own
|
|
66
|
+
* project directory and confined to it
|
|
67
|
+
* (replay/builtins.ts). Default Read,Glob,Grep —
|
|
68
|
+
* the read-only set. Add Edit,Write,Bash only
|
|
69
|
+
* deliberately: a runner-side step never reaches
|
|
70
|
+
* the host's permission prompts. `none` turns
|
|
71
|
+
* built-ins off, and then a built-in step hands
|
|
72
|
+
* the turn back, as before
|
|
73
|
+
* BIR_BASH_ALLOW command prefixes a runner-side Bash step may run
|
|
74
|
+
* (`*` for all). Unset, Bash never executes on the
|
|
75
|
+
* runner even when BIR_BUILTIN_TOOLS lists it
|
|
64
76
|
* BIR_MIN_STEER_SIMILARITY minimum match similarity to replay (default 0.92)
|
|
65
77
|
* BIR_DERIVE_API_KEY OPTIONAL. Derivation — reading what this turn acts
|
|
66
78
|
* on — is done by the service for a signed-in
|
|
@@ -26,6 +26,7 @@ import { DISCOVERY_CHECK_MS, HEARTBEAT_MS, pidAlive, readDiscovery, removeDiscov
|
|
|
26
26
|
import { DAEMON_CWD_ENV, DAEMON_ENV, DAEMON_LOG_ENV, DERIVE_KEY_ENV, DaemonLog, USE_API_KEY_ENV, clearPortProblem, compareVersions, probeHealth, projectEndpoint, rediscover, validPort, writePortProblem, } from "./daemon.js";
|
|
27
27
|
import { configDir, normalizePath, supersededLogPath } from "./paths.js";
|
|
28
28
|
import { RecorderKeeper } from "./recorder-keeper.js";
|
|
29
|
+
import { parseBashAllow, parseBuiltinTools } from "../replay/builtins.js";
|
|
29
30
|
import { parseAllowList } from "../replay/coverage.js";
|
|
30
31
|
import { sweepDeliveries } from "../replay/bash-channel.js";
|
|
31
32
|
import { exemptLoopbackFromProxy, isWrapped, projectRecord, projectToken, readSidecar, } from "../config/generate.js";
|
|
@@ -177,10 +178,18 @@ function buildReplayOptions(keeper, policy, key) {
|
|
|
177
178
|
? parseAllowList(policy.allowServers.join(","))
|
|
178
179
|
: undefined;
|
|
179
180
|
const minSimilarity = Number.isFinite(envMin) ? envMin : (policy?.minSimilarity ?? 0.92);
|
|
181
|
+
// Which built-ins the runner may execute itself (replay/builtins.ts):
|
|
182
|
+
// read-only by default, `BIR_BUILTIN_TOOLS` to change, `BIR_BASH_ALLOW`
|
|
183
|
+
// before a `Bash` step may run at all. Environment-only knobs, like
|
|
184
|
+
// `BIR_REPLAY_RECORDED_OUTPUTS`.
|
|
185
|
+
const builtinTools = parseBuiltinTools(process.env.BIR_BUILTIN_TOOLS);
|
|
186
|
+
const bashAllow = parseBashAllow(process.env.BIR_BASH_ALLOW);
|
|
180
187
|
const opts = {
|
|
181
188
|
enabled,
|
|
182
189
|
minSimilarity,
|
|
183
190
|
allowServers,
|
|
191
|
+
builtinTools,
|
|
192
|
+
bashAllow,
|
|
184
193
|
budgets: {
|
|
185
194
|
// What a prompt may wait for its match (K5). Past it the prompt goes on
|
|
186
195
|
// without one; a service slower than this must not cost every prompt.
|
|
@@ -237,6 +246,10 @@ function buildReplayOptions(keeper, policy, key) {
|
|
|
237
246
|
logLine("replay.enabled", {
|
|
238
247
|
minSimilarity: opts.minSimilarity,
|
|
239
248
|
allowServers: allowServers ? [...allowServers].join(",") : "(all wrapped)",
|
|
249
|
+
// Which built-ins may run on the runner itself, and whether Bash may at
|
|
250
|
+
// all — loud for the same reason as the rest of this line.
|
|
251
|
+
builtins: builtinTools.size > 0 ? [...builtinTools].join(",") : "(none)",
|
|
252
|
+
bashAllow: builtinTools.has("Bash") ? (bashAllow ? bashAllow.join(",") : "(unset — Bash never runs here)") : undefined,
|
|
240
253
|
// Who reads what this turn acts on (segmented.md R-PARAM-5). Named at
|
|
241
254
|
// startup because the third state is a replay that silently never runs.
|
|
242
255
|
derive: key.source ??
|
package/dist/control/server.js
CHANGED
|
@@ -1546,6 +1546,10 @@ export class ControlServer {
|
|
|
1546
1546
|
return undefined;
|
|
1547
1547
|
}
|
|
1548
1548
|
state.sessionId = session.sessionId;
|
|
1549
|
+
// The project directory an allow-listed built-in step executes in, and
|
|
1550
|
+
// the jail it may not leave (replay/builtins.ts). The session's own cwd
|
|
1551
|
+
// from the hook payloads, never the daemon's.
|
|
1552
|
+
state.sessionCwd = session.cwd ?? this.opts.cwd;
|
|
1549
1553
|
this.declineWithoutDeliveryTool(state);
|
|
1550
1554
|
// A steer plan with a shell or file-write step can only be followed if the
|
|
1551
1555
|
// directive shows its computed arguments (the pin guard holds those steps
|
|
@@ -1643,6 +1647,10 @@ export class ControlServer {
|
|
|
1643
1647
|
return undefined;
|
|
1644
1648
|
}
|
|
1645
1649
|
state.sessionId = session.sessionId;
|
|
1650
|
+
// The project directory an allow-listed built-in step executes in, and
|
|
1651
|
+
// the jail it may not leave (replay/builtins.ts). The session's own cwd
|
|
1652
|
+
// from the hook payloads, never the daemon's.
|
|
1653
|
+
state.sessionCwd = session.cwd ?? this.opts.cwd;
|
|
1646
1654
|
run.replay = state;
|
|
1647
1655
|
run.replays.push(state);
|
|
1648
1656
|
return undefined;
|
|
@@ -3014,6 +3022,10 @@ export class ControlServer {
|
|
|
3014
3022
|
return undefined;
|
|
3015
3023
|
}
|
|
3016
3024
|
state.sessionId = session.sessionId;
|
|
3025
|
+
// The project directory an allow-listed built-in step executes in, and
|
|
3026
|
+
// the jail it may not leave (replay/builtins.ts). The session's own cwd
|
|
3027
|
+
// from the hook payloads, never the daemon's.
|
|
3028
|
+
state.sessionCwd = session.cwd ?? this.opts.cwd;
|
|
3017
3029
|
this.declineWithoutDeliveryTool(state);
|
|
3018
3030
|
if (!state.plan) {
|
|
3019
3031
|
// A declined intent hit reports nothing: the turn's cost is not a
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builtins — running a scenario's built-in steps (`Read`, `Glob`, `Grep`,
|
|
3
|
+
* `Edit`, `Write`, `Bash`) on the runner itself (docs/calculatedReplay.md §5).
|
|
4
|
+
*
|
|
5
|
+
* A built-in lives inside the host session, not behind any MCP server, so there
|
|
6
|
+
* is no proxy to relay it to: historically every built-in step threw `not_run`
|
|
7
|
+
* and the plan handed the task back to the model. This module is the other
|
|
8
|
+
* option: execute the step here, against the *session's own project directory*,
|
|
9
|
+
* so a chain that starts with a `Read` no longer costs a hand-over.
|
|
10
|
+
*
|
|
11
|
+
* THE RULES, because this bypasses the host's permission prompts entirely:
|
|
12
|
+
*
|
|
13
|
+
* - **Default deny beyond read-only.** Which built-ins may run at all is
|
|
14
|
+
* `BIR_BUILTIN_TOOLS` ({@link parseBuiltinTools}), and its default is the
|
|
15
|
+
* read-only set (`Read,Glob,Grep`). `Edit`, `Write` and `Bash` are an
|
|
16
|
+
* explicit, per-environment opt-in — a recorded `rm` must never run
|
|
17
|
+
* because a prompt looked similar.
|
|
18
|
+
* - **Path jail.** Every path is resolved against, and confined to, the
|
|
19
|
+
* session's project directory ({@link jailed}), symlinks included. The
|
|
20
|
+
* daemon's own cwd is never used: it is `~/.baseinstrunner`, not the
|
|
21
|
+
* project (§5.1 of the change plan).
|
|
22
|
+
* - **Bash runs nothing unless `BIR_BASH_ALLOW` says so.** A command must
|
|
23
|
+
* match one of its prefixes ({@link bashAllowed}); unset means `Bash`
|
|
24
|
+
* never executes here, even when allow-listed.
|
|
25
|
+
* - **Same failure contract as an MCP step.** A step that *cannot* run here
|
|
26
|
+
* throws {@link BuiltinNotRunnable} (→ `not_run`: the session can still do
|
|
27
|
+
* it, or a recorded output may stand in under
|
|
28
|
+
* `BIR_REPLAY_RECORDED_OUTPUTS=1`). One that ran out of time throws
|
|
29
|
+
* {@link BuiltinTimeout} (→ `uncertain`). Any other throw is a step that
|
|
30
|
+
* ran and failed (→ `failed`), never silently threaded as a success.
|
|
31
|
+
*
|
|
32
|
+
* Outputs mimic the **hook view** of each tool — Claude Code's `tool_response`
|
|
33
|
+
* as `PostToolUse` reports it — because that is exactly what the recorder
|
|
34
|
+
* stored for built-in steps (control/server.ts, `onToolPost`) and therefore
|
|
35
|
+
* what each step's `toolOutputLogic` was authored against. The shapes are a
|
|
36
|
+
* best effort and may drift from a future host's, the same caveat the
|
|
37
|
+
* hook-view threading fallback already carries ("shapes may differ").
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* The built-ins bir may stand in for or execute when `BIR_BUILTIN_TOOLS` is
|
|
41
|
+
* unset: the read-only set. Reading a file the session itself would read
|
|
42
|
+
* without a prompt is the risk floor; anything that writes or runs is not.
|
|
43
|
+
*/
|
|
44
|
+
export declare const DEFAULT_BUILTIN_TOOLS: readonly string[];
|
|
45
|
+
/** The built-ins {@link runBuiltin} actually implements. */
|
|
46
|
+
export declare const EXECUTABLE_BUILTINS: ReadonlySet<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Parse `BIR_BUILTIN_TOOLS`. Unset means {@link DEFAULT_BUILTIN_TOOLS}; set,
|
|
49
|
+
* it is the whole list — `none` (or an empty value) turns built-ins off
|
|
50
|
+
* entirely. Unlike `BIR_REPLAY_ALLOW_SERVERS`, an explicit empty list is
|
|
51
|
+
* honoured: there is a non-empty default to fall back from, so emptiness here
|
|
52
|
+
* is a choice, not an accident.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseBuiltinTools(value: string | undefined): ReadonlySet<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Parse `BIR_BASH_ALLOW`: comma-separated command prefixes, `*` for
|
|
57
|
+
* everything. Undefined (unset or blank) means `Bash` never executes on the
|
|
58
|
+
* runner — the flag is the opt-in, exactly like the tool list itself.
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseBashAllow(value: string | undefined): readonly string[] | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Whether `command` matches the `BIR_BASH_ALLOW` prefixes. A prefix that ends
|
|
63
|
+
* mid-word must end the command's word too: `git` allows `git status` and
|
|
64
|
+
* `git`, never `gitk` — an operator who wrote `git` meant git, and the
|
|
65
|
+
* trimming in {@link parseBashAllow} must not widen that.
|
|
66
|
+
*/
|
|
67
|
+
export declare function bashAllowed(command: string, allow: readonly string[] | undefined): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The step cannot run here — policy, not breakage: the tool is not
|
|
70
|
+
* implemented, its path leaves the project, `Bash` is not allowed, or the
|
|
71
|
+
* input is not even the right shape. Maps to `not_run`: nothing happened, the
|
|
72
|
+
* session may still do it, a recorded output may stand in.
|
|
73
|
+
*/
|
|
74
|
+
export declare class BuiltinNotRunnable extends Error {
|
|
75
|
+
constructor(message: string);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The step started and was cut off by its clock. For `Bash` the command may
|
|
79
|
+
* have done part of its work; for a walk there is simply no result. Either
|
|
80
|
+
* way nothing may be asserted, which is `uncertain` — the same word an MCP
|
|
81
|
+
* step's timeout gets.
|
|
82
|
+
*/
|
|
83
|
+
export declare class BuiltinTimeout extends Error {
|
|
84
|
+
constructor(message: string);
|
|
85
|
+
}
|
|
86
|
+
export interface BuiltinContext {
|
|
87
|
+
/**
|
|
88
|
+
* The session's project directory — the jail every path resolves against.
|
|
89
|
+
* The caller (controller.ts) refuses to execute at all without one; the
|
|
90
|
+
* daemon's own cwd is never a substitute.
|
|
91
|
+
*/
|
|
92
|
+
cwd: string;
|
|
93
|
+
/** The step's clock, the same budget an MCP dispatch would get. */
|
|
94
|
+
timeoutMs?: number;
|
|
95
|
+
/** Parsed `BIR_BASH_ALLOW` ({@link parseBashAllow}). */
|
|
96
|
+
bashAllow?: readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* Where this plan's *previous* `Bash` step left its shell — the host's own
|
|
99
|
+
* semantics, which a recorded chain was written against: the working
|
|
100
|
+
* directory persists between Bash calls, while variables and functions do
|
|
101
|
+
* not (each call is a fresh shell off the profile). Absent, or pointing at
|
|
102
|
+
* a directory that no longer exists, the step starts in {@link cwd}.
|
|
103
|
+
*/
|
|
104
|
+
bashCwd?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Told where a `Bash` step's shell ended up (`$PWD`, as a path the next
|
|
107
|
+
* spawn can use), so the caller can carry it into the plan's next Bash
|
|
108
|
+
* step. Best effort: a command that `exit`s explicitly, or is cut off by
|
|
109
|
+
* its clock, reports nothing and the previous directory stands.
|
|
110
|
+
*/
|
|
111
|
+
onBashCwd?: (pwd: string) => void;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Execute one built-in step and return its hook-shaped result (an object; the
|
|
115
|
+
* caller serializes). Throws per the failure contract above.
|
|
116
|
+
*/
|
|
117
|
+
export declare function runBuiltin(toolName: string, input: Record<string, unknown>, ctx: BuiltinContext): Promise<unknown>;
|
|
118
|
+
/**
|
|
119
|
+
* Which `bash` a runner-side step uses. On Windows the host's own Bash tool
|
|
120
|
+
* is Git Bash, which is rarely on the daemon's PATH — so the standard
|
|
121
|
+
* Git-for-Windows locations are preferred over PATH, where `bash.exe` may
|
|
122
|
+
* even be the WSL stub (a different filesystem view entirely). Resolved once.
|
|
123
|
+
*/
|
|
124
|
+
export declare function bashExecutable(): string;
|
|
125
|
+
/**
|
|
126
|
+
* A glob pattern as a RegExp over `/`-separated relative paths: `**` crosses
|
|
127
|
+
* directories, `*` and `?` stay inside one, `{a,b}` alternates, `[…]` passes
|
|
128
|
+
* through as a character class. Matched against the whole relative path, as
|
|
129
|
+
* the host's Glob does — a bare `*.ts` is the top level only (Grep's filter
|
|
130
|
+
* widens a bare pattern itself, ripgrep-style). Enough for the patterns
|
|
131
|
+
* Claude Code writes; a pattern beyond it simply matches nothing it should
|
|
132
|
+
* not.
|
|
133
|
+
*/
|
|
134
|
+
export declare function globToRegExp(pattern: string): RegExp;
|
|
135
|
+
//# sourceMappingURL=builtins.d.ts.map
|