@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.
- 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 +2787 -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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recorder-keeper — the background recorder's sign-in, kept current without
|
|
3
|
+
* ever standing between Claude Code and a hook.
|
|
4
|
+
*
|
|
5
|
+
* WHAT WAS WRONG. `bir-hooks` used to settle its sign-in *before* it listened:
|
|
6
|
+
* a `GET /health` to check the address, then — whenever the access token had
|
|
7
|
+
* expired, which is every first session of the day — a `POST /auth/refresh`,
|
|
8
|
+
* neither with a timeout. A service that was slow, blackholed, or behind a
|
|
9
|
+
* proxy that accepts and never answers held the port closed for 10-300 s, and
|
|
10
|
+
* every hook of every session in that window was refused (Claude Code shows
|
|
11
|
+
* each as a hook error). Worse, a refresh that failed for *any* reason — the
|
|
12
|
+
* Wi-Fi not up yet after a wake — was read as "not signed in": the recorder
|
|
13
|
+
* built a `NullRecorder`, logged "run `bir login`", and recorded nothing for
|
|
14
|
+
* the rest of its life, which is days. Following that advice changed nothing.
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT DOES NOW. The recorder is decided from disk alone — the stored
|
|
17
|
+
* address and credentials.json — so it exists before the port is bound, with
|
|
18
|
+
* no network at all. An expired access token is not a reason to stop: the
|
|
19
|
+
* `RemoteRecorder` refreshes on its first 401 and adopts a sibling's refresh.
|
|
20
|
+
* Then, in the background and bounded by timeouts, this keeper:
|
|
21
|
+
*
|
|
22
|
+
* - refreshes the token a little before it expires (and right away when it
|
|
23
|
+
* already has), so the first prompt of the day does not pay for it;
|
|
24
|
+
* - notices a sign-in appearing, changing or being removed on disk — `bir
|
|
25
|
+
* login`, `bir setup`, `bir logout`, a sibling's refresh — and swaps the
|
|
26
|
+
* backend of the {@link SwitchableRecorder} the control server holds. No
|
|
27
|
+
* restart. A signed-out recorder checks again the moment a prompt arrives,
|
|
28
|
+
* so the prompt after `bir login` is already recorded;
|
|
29
|
+
* - tells a *rejected* sign-in (the API answered 400/401/403 with its own
|
|
30
|
+
* JSON error) from a *missing* service (no answer, a timeout, 5xx, 429, or
|
|
31
|
+
* a page that is not the API — a WAF's or a captive portal's HTML 403 is
|
|
32
|
+
* not a verdict on the sign-in). Only the first stops recording and says
|
|
33
|
+
* "run `bir login`", and even then the refusal is asked once more half an
|
|
34
|
+
* hour later before it is final; the second keeps the saved sign-in,
|
|
35
|
+
* retries with backoff, and says so.
|
|
36
|
+
*
|
|
37
|
+
* NOTHING HERE IS ON A HOT PATH. Every network call is in a timer or behind a
|
|
38
|
+
* hook's answer, has a timeout, and reports to `serviceHealth`, so a hook that
|
|
39
|
+
* *does* wait for the service (a prompt match) knows not to while it is down.
|
|
40
|
+
* While the runner is switched off (`bir off`) the keeper makes no calls.
|
|
41
|
+
*/
|
|
42
|
+
import { type AuthSession } from "../auth/client.js";
|
|
43
|
+
import { type Recorder } from "../record/recorder.js";
|
|
44
|
+
import { SwitchableRecorder } from "../record/switchable-recorder.js";
|
|
45
|
+
/** Why the recorder is (or is not) recording right now. */
|
|
46
|
+
export type KeeperState =
|
|
47
|
+
/** A session and an address: steps go to the service. */
|
|
48
|
+
"recording"
|
|
49
|
+
/** A session, and a plain-http address still being checked for an https redirect. */
|
|
50
|
+
| "settling"
|
|
51
|
+
/** No service address at all. */
|
|
52
|
+
| "no_address"
|
|
53
|
+
/** No credentials.json. */
|
|
54
|
+
| "signed_out"
|
|
55
|
+
/** Credentials issued by another service than the configured one. */
|
|
56
|
+
| "other_service"
|
|
57
|
+
/** The service refused the saved refresh token. */
|
|
58
|
+
| "rejected";
|
|
59
|
+
interface Decision {
|
|
60
|
+
state: KeeperState;
|
|
61
|
+
baseUrl?: string;
|
|
62
|
+
session?: AuthSession;
|
|
63
|
+
/** For `other_service`: the address the saved sign-in belongs to. */
|
|
64
|
+
issuer?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* True for a plain-http address that is not this machine. Such an address may
|
|
68
|
+
* sit behind a TLS proxy that answers `308 → https`, and following that drops
|
|
69
|
+
* the bearer token (auth/client.ts, normalizeAuthUrl) — so nothing is sent to
|
|
70
|
+
* it until that has been checked. https needs no check, and a loopback address
|
|
71
|
+
* is neither redirected by a TLS front nor readable on the network.
|
|
72
|
+
*/
|
|
73
|
+
export declare function needsSettling(url: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* What the files on disk say the recorder should be. No network.
|
|
76
|
+
*
|
|
77
|
+
* `settled` maps a configured plain-http address to what its check found.
|
|
78
|
+
*/
|
|
79
|
+
export declare function decideFromDisk(settled?: ReadonlyMap<string, string>): Decision;
|
|
80
|
+
export type RefreshOutcome = {
|
|
81
|
+
kind: "ok";
|
|
82
|
+
session: AuthSession;
|
|
83
|
+
}
|
|
84
|
+
/** The API answered and refused this refresh token. */
|
|
85
|
+
| {
|
|
86
|
+
kind: "rejected";
|
|
87
|
+
status: number;
|
|
88
|
+
}
|
|
89
|
+
/** No usable answer: network, timeout, 5xx, 429, or something that is not the API. */
|
|
90
|
+
| {
|
|
91
|
+
kind: "unreachable";
|
|
92
|
+
reason: string;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* One `POST /auth/refresh`, bounded, reported to `serviceHealth`.
|
|
96
|
+
*
|
|
97
|
+
* `authenticate()` answers "no session" for every failure alike, which is what
|
|
98
|
+
* turned a network blip into "run `bir login`". This one says which it was.
|
|
99
|
+
*/
|
|
100
|
+
export declare function refreshSession(baseUrl: string, session: AuthSession, fetchImpl?: typeof fetch, timeoutMs?: number, now?: () => number): Promise<RefreshOutcome>;
|
|
101
|
+
export interface RecorderKeeperOptions {
|
|
102
|
+
/** The project directory: decides the per-project off switch. */
|
|
103
|
+
cwd: string;
|
|
104
|
+
/** Injected by tests; defaults to the global fetch. */
|
|
105
|
+
fetchImpl?: typeof fetch;
|
|
106
|
+
now?: () => number;
|
|
107
|
+
/** How often the background check runs. Default 60 s. */
|
|
108
|
+
tickMs?: number;
|
|
109
|
+
/** When the first check runs after {@link RecorderKeeper.start}. Default 250 ms. */
|
|
110
|
+
firstTickMs?: number;
|
|
111
|
+
/** Refresh this long before the access token expires. Default 10 min. */
|
|
112
|
+
refreshAheadMs?: number;
|
|
113
|
+
/**
|
|
114
|
+
* Do not refresh an already-expired token this soon after a run started: the
|
|
115
|
+
* backend's own 401-refresh is likely in flight, and two refreshes of one
|
|
116
|
+
* rotating token lose one of them. Default 20 s.
|
|
117
|
+
*/
|
|
118
|
+
quietMs?: number;
|
|
119
|
+
/** After a rejected refresh, how long to wait before re-reading the file. Default 1 s. */
|
|
120
|
+
rejectRecheckMs?: number;
|
|
121
|
+
/**
|
|
122
|
+
* After a rejected refresh, when to try it once more before the refusal is
|
|
123
|
+
* final. Default 30 min. Something that is not the API can still answer in
|
|
124
|
+
* its place with JSON; this bounds what such an answer costs.
|
|
125
|
+
*/
|
|
126
|
+
rejectRetryMs?: number;
|
|
127
|
+
/** Builds the backend for a session. Injected by tests. */
|
|
128
|
+
makeRemote?: (baseUrl: string, session: AuthSession) => Recorder;
|
|
129
|
+
}
|
|
130
|
+
export declare class RecorderKeeper {
|
|
131
|
+
readonly recorder: SwitchableRecorder;
|
|
132
|
+
/**
|
|
133
|
+
* The one backend used whenever nothing is recorded. One per process, not
|
|
134
|
+
* one per sign-out: a `NullRecorder` numbers its runs itself, and a fresh
|
|
135
|
+
* one after every logout or settle handed out `run_local_1` again — two
|
|
136
|
+
* runs under one id in the journal `bir investigate` reads.
|
|
137
|
+
*/
|
|
138
|
+
private readonly idle;
|
|
139
|
+
private readonly opts;
|
|
140
|
+
private readonly now;
|
|
141
|
+
private readonly fetchImpl;
|
|
142
|
+
private stateNow;
|
|
143
|
+
/** The address and session the current backend was built from (when recording). */
|
|
144
|
+
private baseUrl?;
|
|
145
|
+
private session?;
|
|
146
|
+
/** The decision waiting for its plain-http address to be checked. */
|
|
147
|
+
private pending?;
|
|
148
|
+
/** configured http address → what the check found. */
|
|
149
|
+
private readonly settled;
|
|
150
|
+
private settling?;
|
|
151
|
+
private diskStamp;
|
|
152
|
+
/** A refresh token the service refused; a file still holding it is not a sign-in. */
|
|
153
|
+
private rejectedRefreshToken?;
|
|
154
|
+
/** The refusal waiting for its one more try (see {@link RecorderKeeperOptions.rejectRetryMs}). */
|
|
155
|
+
private refused?;
|
|
156
|
+
private nextRefreshAt;
|
|
157
|
+
private refreshFailures;
|
|
158
|
+
private lastRunStartedAt;
|
|
159
|
+
private ticking;
|
|
160
|
+
private timers;
|
|
161
|
+
constructor(opts: RecorderKeeperOptions);
|
|
162
|
+
/** Why the recorder is or is not recording. */
|
|
163
|
+
get state(): KeeperState;
|
|
164
|
+
/**
|
|
165
|
+
* What `/health` reports as `recording`. True while a plain-http address is
|
|
166
|
+
* being checked too: there is a sign-in, and recording starts the moment the
|
|
167
|
+
* address answers — a "no session" there would send people to `bir login`
|
|
168
|
+
* for nothing.
|
|
169
|
+
*/
|
|
170
|
+
get recording(): boolean;
|
|
171
|
+
/** The account recordings land in, for `/health`. */
|
|
172
|
+
get account(): string | undefined;
|
|
173
|
+
/** The service address replay should call, while there is a session for it. */
|
|
174
|
+
get authUrl(): string | undefined;
|
|
175
|
+
/** The live access token (C5): read on every use, never kept. */
|
|
176
|
+
currentAccessToken(): string | undefined;
|
|
177
|
+
/** Start the background checks. Timers are unref'd: they never keep the process up. */
|
|
178
|
+
start(): void;
|
|
179
|
+
stop(): void;
|
|
180
|
+
/**
|
|
181
|
+
* One background pass: look at the files, check a plain-http address,
|
|
182
|
+
* refresh a token that is due. Never throws; never overlaps itself.
|
|
183
|
+
*/
|
|
184
|
+
tick(): Promise<void>;
|
|
185
|
+
/**
|
|
186
|
+
* Re-read the sign-in and the address if either file changed. Local and
|
|
187
|
+
* cheap: two `stat`s, and a read only when one of them moved.
|
|
188
|
+
*/
|
|
189
|
+
checkDisk(): void;
|
|
190
|
+
private stamp;
|
|
191
|
+
private makeRemote;
|
|
192
|
+
/** Act on a decision: swap the backend when it changes what is recorded where. */
|
|
193
|
+
private apply;
|
|
194
|
+
private install;
|
|
195
|
+
private goIdle;
|
|
196
|
+
private explain;
|
|
197
|
+
/**
|
|
198
|
+
* Check a plain-http address for an https redirect, once it answers. Until
|
|
199
|
+
* then nothing is sent to it: a token sent before the check could go out in
|
|
200
|
+
* the clear to a host that would have redirected it.
|
|
201
|
+
*/
|
|
202
|
+
private settle;
|
|
203
|
+
/** Refresh the access token when it is due, telling a refusal from an outage. */
|
|
204
|
+
private maybeRefresh;
|
|
205
|
+
/**
|
|
206
|
+
* Save a rotated session whatever happened meanwhile — the service has spent
|
|
207
|
+
* the refresh token on disk — unless the file no longer holds the one spent
|
|
208
|
+
* (a logout removed it, or a new sign-in replaced it), which is not ours to
|
|
209
|
+
* overwrite.
|
|
210
|
+
*/
|
|
211
|
+
private saveRefreshed;
|
|
212
|
+
/**
|
|
213
|
+
* Decide from the files as they are now, whatever the last stamp said: after
|
|
214
|
+
* a logout this goes idle, after a sign-in as someone else it records to
|
|
215
|
+
* them, and after a sibling's refresh of the same sign-in it adopts the
|
|
216
|
+
* sibling's token (the `same` branch of {@link apply}).
|
|
217
|
+
*/
|
|
218
|
+
private decideFromDiskNow;
|
|
219
|
+
/**
|
|
220
|
+
* A refusal is asked once more, {@link REJECT_RETRY_MS} later, before it is
|
|
221
|
+
* final: whatever stands in front of the API can answer for it in JSON too,
|
|
222
|
+
* and a sign-in that was never really refused should not cost more than
|
|
223
|
+
* that half hour. A second refusal is final until the file changes (`bir
|
|
224
|
+
* login`); no answer at all is asked again after another interval.
|
|
225
|
+
*/
|
|
226
|
+
private maybeRetryRefused;
|
|
227
|
+
}
|
|
228
|
+
export {};
|
|
229
|
+
//# sourceMappingURL=recorder-keeper.d.ts.map
|