@bridge4dev/runner 0.46.1 → 0.47.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 +44 -2
- package/dist/adapters/claude.js +6 -2
- package/dist/agent-auto-update.d.ts +75 -0
- package/dist/agent-auto-update.js +134 -0
- package/dist/agent-binary.d.ts +48 -0
- package/dist/agent-binary.js +55 -0
- package/dist/agent-cleanup.d.ts +78 -0
- package/dist/agent-cleanup.js +184 -0
- package/dist/agent-install.d.ts +140 -0
- package/dist/agent-install.js +475 -0
- package/dist/agent-registry.d.ts +223 -0
- package/dist/agent-registry.js +131 -0
- package/dist/agent-versions.d.ts +93 -0
- package/dist/agent-versions.js +157 -0
- package/dist/auth-relay.d.ts +9 -1
- package/dist/auth-relay.js +3 -1
- package/dist/commit-message.js +5 -0
- package/dist/config.d.ts +44 -4
- package/dist/config.js +43 -0
- package/dist/index.js +46 -12
- package/dist/protocol.d.ts +155 -28
- package/dist/protocol.js +33 -1
- package/dist/recipe-schema.d.ts +12 -12
- package/dist/self-update.d.ts +14 -0
- package/dist/self-update.js +45 -13
- package/dist/supervisor.d.ts +155 -2
- package/dist/supervisor.js +393 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/self-update.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
-
import { findClaudeCli } from './agent-binary.js';
|
|
6
|
+
import { findClaudeCli, USE_BUNDLED_CLAUDE } from './agent-binary.js';
|
|
7
7
|
import { log } from './log.js';
|
|
8
8
|
import { stateDir } from './paths.js';
|
|
9
9
|
import { RUNNER_VERSION } from './version.js';
|
|
@@ -237,18 +237,32 @@ function installArgs(source, prefix) {
|
|
|
237
237
|
// npm cheerfully installs a SECOND copy somewhere the service does not exec,
|
|
238
238
|
// reports success, and the runner restarts on the old version forever.
|
|
239
239
|
//
|
|
240
|
-
// `--
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
240
|
+
// `--omit=optional` since 0.47.0, and it used to be its exact opposite.
|
|
241
|
+
//
|
|
242
|
+
// Until C3 the Claude CLI a session ran on WAS an optional platform package of
|
|
243
|
+
// the Agent SDK inside this package, so `--include=optional` was stated
|
|
244
|
+
// explicitly (ticket #225): a single `omit=optional` inherited from an
|
|
245
|
+
// `.npmrc` produced a runner that could not start one Claude session, and npm
|
|
246
|
+
// called that a success. Sessions now run on the system `claude` — installed,
|
|
247
|
+
// measured and updated like any other agent — so the 215 MB binary in here is
|
|
248
|
+
// no longer used by anything, and downloading it on every update would be a
|
|
249
|
+
// tax on every dev server for a file nothing opens.
|
|
250
|
+
//
|
|
251
|
+
// Read this beside `npmInstallArgs` in `agent-install.ts`, which says
|
|
252
|
+
// `--include=optional` and is not a contradiction: DIFFERENT packages. The
|
|
253
|
+
// runner no longer wants Claude's platform binary; `@openai/codex` keeps its
|
|
254
|
+
// own 320 MB executable in exactly such an optional package, and installing
|
|
255
|
+
// Codex without it yields a shim that cannot run — with npm exiting 0 either
|
|
256
|
+
// way (гоча #297, and #415 for the Codex half).
|
|
257
|
+
//
|
|
258
|
+
// C4 is what makes this safe: the bundled-binary check below is conditional on
|
|
259
|
+
// `USE_BUNDLED_CLAUDE`, so an update that no longer ships the binary is not
|
|
260
|
+
// read as a broken install and rolled back on every single machine, forever.
|
|
247
261
|
return [
|
|
248
262
|
'install',
|
|
249
263
|
'-g',
|
|
250
264
|
'--ignore-scripts',
|
|
251
|
-
'--
|
|
265
|
+
'--omit=optional',
|
|
252
266
|
'--loglevel=error',
|
|
253
267
|
...(prefix ? ['--prefix', prefix] : []),
|
|
254
268
|
source,
|
|
@@ -269,7 +283,7 @@ export function manualUpdateCommand(tarballUrl, options = {}) {
|
|
|
269
283
|
const prefix = options.prefix === undefined ? installPrefixFor(packageDir) : options.prefix;
|
|
270
284
|
const user = options.user ?? os.userInfo().username;
|
|
271
285
|
const uid = options.uid ?? (typeof process.getuid === 'function' ? process.getuid() : -1);
|
|
272
|
-
const install = ['npm install -g --ignore-scripts --
|
|
286
|
+
const install = ['npm install -g --ignore-scripts --omit=optional --loglevel=error']
|
|
273
287
|
.concat(prefix ? [`--prefix ${prefix}`] : [])
|
|
274
288
|
.concat([tarballUrl])
|
|
275
289
|
.join(' ');
|
|
@@ -367,6 +381,7 @@ export async function selfUpdate(options) {
|
|
|
367
381
|
}
|
|
368
382
|
// Derived once and threaded through every npm call below.
|
|
369
383
|
const prefix = installPrefixFor(packageDir);
|
|
384
|
+
const usesBundledClaude = options.bundledClaude ?? USE_BUNDLED_CLAUDE;
|
|
370
385
|
// Pack the current version FIRST: without a rollback artefact there is no
|
|
371
386
|
// honest way back if the new build turns out to be broken.
|
|
372
387
|
const rollbackDir = path.join(stateDir(), 'rollback');
|
|
@@ -442,7 +457,14 @@ export async function selfUpdate(options) {
|
|
|
442
457
|
// One repair attempt first, because that is what the failure usually deserves:
|
|
443
458
|
// a 300 MB optional package that did not download is a network hiccup, not a
|
|
444
459
|
// broken release, and reinstalling it is cheaper for the user than a rollback.
|
|
445
|
-
|
|
460
|
+
//
|
|
461
|
+
// Conditional since C4, and it has to be: after `USE_BUNDLED_CLAUDE` flips,
|
|
462
|
+
// the runner is installed WITHOUT that optional package on purpose (`--omit=
|
|
463
|
+
// optional`, −215 MB), so an unconditional check would find it missing and
|
|
464
|
+
// roll back every single runner update, for ever. The check is not deleted —
|
|
465
|
+
// while the bundled binary is what sessions run on, its absence is still the
|
|
466
|
+
// 2026-08-11 outage waiting to happen.
|
|
467
|
+
if (usesBundledClaude && !findClaudeCli(newPackageDir)) {
|
|
446
468
|
log.warn('self-update: the Claude CLI is missing from the new build — repairing', {
|
|
447
469
|
packageDir: newPackageDir,
|
|
448
470
|
});
|
|
@@ -453,7 +475,7 @@ export async function selfUpdate(options) {
|
|
|
453
475
|
log.warn('self-update: the repair install failed', { error: describe(error) });
|
|
454
476
|
}
|
|
455
477
|
}
|
|
456
|
-
if (!findClaudeCli(newPackageDir)) {
|
|
478
|
+
if (usesBundledClaude && !findClaudeCli(newPackageDir)) {
|
|
457
479
|
log.error('self-update: still no Claude CLI after the repair — rolling back', {
|
|
458
480
|
packageDir: newPackageDir,
|
|
459
481
|
});
|
|
@@ -465,7 +487,7 @@ export async function selfUpdate(options) {
|
|
|
465
487
|
}
|
|
466
488
|
catch (rollbackError) {
|
|
467
489
|
return fail(`The new version is broken (${detail}) and the rollback failed too (${describe(rollbackError)}). ` +
|
|
468
|
-
`Restore it on the server with: npm install -g --
|
|
490
|
+
`Restore it on the server with: npm install -g --omit=optional${prefix ? ` --prefix ${prefix}` : ''} ${rollbackTarball}`, { rollbackTarball, ...(toVersion ? { toVersion } : {}) });
|
|
469
491
|
}
|
|
470
492
|
}
|
|
471
493
|
// The service unit may be pinned to a file inside the directory this update
|
|
@@ -553,4 +575,14 @@ function describe(error) {
|
|
|
553
575
|
const meaningful = npmErrors || raw;
|
|
554
576
|
return meaningful.length > 400 ? `…${meaningful.slice(-400)}` : meaningful;
|
|
555
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* The same reading of a failed child process, for `agent-install.ts`.
|
|
580
|
+
*
|
|
581
|
+
* Exported rather than copied: installing an agent CLI shells out to the very
|
|
582
|
+
* same npm, and a second implementation would drift into reporting the ERESOLVE
|
|
583
|
+
* wall this one exists to suppress.
|
|
584
|
+
*/
|
|
585
|
+
export function describeCommandFailure(error) {
|
|
586
|
+
return describe(error);
|
|
587
|
+
}
|
|
556
588
|
//# sourceMappingURL=self-update.js.map
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { JournalStore } from './journal.js';
|
|
2
2
|
import { proposeCommitMessage } from './commit-message.js';
|
|
3
3
|
import { selfUpdate, type SelfUpdateOutcome } from './self-update.js';
|
|
4
|
+
import { installAgent } from './agent-install.js';
|
|
5
|
+
import { pruneNativeClaudeVersions } from './agent-cleanup.js';
|
|
6
|
+
import { type AgentVersionsMeasurement } from './agent-versions.js';
|
|
4
7
|
import type { RunnerWsClient } from './ws-client.js';
|
|
5
8
|
import type { SessionDescriptor } from './protocol.js';
|
|
6
9
|
import type { AgentAdapter } from './adapters/types.js';
|
|
@@ -53,6 +56,50 @@ export interface SupervisorOptions {
|
|
|
53
56
|
* announced and no rewind can be started.
|
|
54
57
|
*/
|
|
55
58
|
checkpointsEnabled?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Test seam for the agent version measurement.
|
|
61
|
+
*
|
|
62
|
+
* A seam and not a detail: the real one spawns `claude`, `codex` and their
|
|
63
|
+
* doctors, and this class is constructed in every supervisor test — an
|
|
64
|
+
* unstubbed probe would run four processes per test file, on machines that
|
|
65
|
+
* may have neither CLI.
|
|
66
|
+
*/
|
|
67
|
+
measureAgentVersions?: (options?: {
|
|
68
|
+
force?: boolean;
|
|
69
|
+
}) => Promise<AgentVersionsMeasurement>;
|
|
70
|
+
/**
|
|
71
|
+
* `[agents] install_enabled` from the runner's own config (#371), by the same
|
|
72
|
+
* rule as `[verify]` and `[checkpoints]`. Announced AND enforced: an API that
|
|
73
|
+
* has not noticed the veto still cannot install anything here.
|
|
74
|
+
*/
|
|
75
|
+
agentInstallEnabled?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Test seam for installing an agent CLI. The real one downloads 200–330 MB
|
|
78
|
+
* and writes into the npm prefix — not something a unit test may do.
|
|
79
|
+
*/
|
|
80
|
+
installAgent?: typeof installAgent;
|
|
81
|
+
/**
|
|
82
|
+
* Test seam for the Р12 sweep of old Claude version files.
|
|
83
|
+
*
|
|
84
|
+
* A seam because the real one deletes 300 MB executables out of `$HOME`, and
|
|
85
|
+
* the thing worth testing HERE is not the deletion — that is
|
|
86
|
+
* `agent-cleanup.test.ts` — but the gate in front of it: no sessions, no
|
|
87
|
+
* install running. A test that had to move `$HOME` to prove a conditional
|
|
88
|
+
* would be proving the wrong thing in the wrong place.
|
|
89
|
+
*/
|
|
90
|
+
pruneAgentVersions?: typeof pruneNativeClaudeVersions;
|
|
91
|
+
/**
|
|
92
|
+
* Test seam: how often the Р12 sweep runs, in ms — the first pass and every
|
|
93
|
+
* one after it.
|
|
94
|
+
*
|
|
95
|
+
* The same reason `emptyTurnSettleMs` exists. The real numbers are an hour
|
|
96
|
+
* and a day; no suite can wait either out, and fake timers are not an option
|
|
97
|
+
* here — they fire the WS client's liveness watchdog, which terminates the
|
|
98
|
+
* socket as «half-dead» and leaves the supervisor receiving nothing at all.
|
|
99
|
+
* Without this seam the gate in front of the sweep would never be executed by
|
|
100
|
+
* a test. Never set in production.
|
|
101
|
+
*/
|
|
102
|
+
agentCleanupMs?: number;
|
|
56
103
|
/**
|
|
57
104
|
* How long a turn that produced nothing is held before it counts (#300).
|
|
58
105
|
*
|
|
@@ -117,12 +164,118 @@ export declare class Supervisor {
|
|
|
117
164
|
* than having no restore point for that one message.
|
|
118
165
|
*/
|
|
119
166
|
private readonly repoLockDepth;
|
|
120
|
-
/**
|
|
121
|
-
|
|
167
|
+
/**
|
|
168
|
+
* One install at a time on this machine, whoever asked for it.
|
|
169
|
+
*
|
|
170
|
+
* `self_update` and `agent_install` both end in `npm install -g` into the
|
|
171
|
+
* SAME npm prefix under the dedicated-user layout, so two of them at once is
|
|
172
|
+
* a real conflict rather than a theoretical one. The API takes a Redis lock
|
|
173
|
+
* before pressing either button, but that lock cannot cover an install the
|
|
174
|
+
* runner starts by itself (stage D) — and the runner has no Redis. This is
|
|
175
|
+
* the only lock that sees both.
|
|
176
|
+
*
|
|
177
|
+
* Holds the holder's name rather than a boolean, so the refusal can say
|
|
178
|
+
* which of the two is running.
|
|
179
|
+
*/
|
|
180
|
+
private installInFlight;
|
|
122
181
|
/** Session 14: one project-recipe run per machine, and its verdict queue. */
|
|
123
182
|
private readonly verify;
|
|
124
183
|
private readonly verifyReports;
|
|
125
184
|
constructor(ws: RunnerWsClient, opts: SupervisorOptions);
|
|
185
|
+
/** How often the agent versions are re-derived. See the constructor. */
|
|
186
|
+
private static readonly AGENT_VERSIONS_INTERVAL_MS;
|
|
187
|
+
private readonly agentVersionsTimer;
|
|
188
|
+
/**
|
|
189
|
+
* Tell the API which agents this machine has and at which versions.
|
|
190
|
+
*
|
|
191
|
+
* Never throws and never blocks its caller: a machine where a probe hangs
|
|
192
|
+
* must still start sessions. A frame that could not be written is simply not
|
|
193
|
+
* sent — the next `hello_ack` or the hourly tick carries the same fact, and
|
|
194
|
+
* the measurement behind it is cached, so retrying is nearly free.
|
|
195
|
+
*/
|
|
196
|
+
/**
|
|
197
|
+
* Why an install cannot start right now, in words for the person who pressed.
|
|
198
|
+
*
|
|
199
|
+
* `null` means the way is clear.
|
|
200
|
+
*/
|
|
201
|
+
private installBusyReason;
|
|
202
|
+
/**
|
|
203
|
+
* A version change that has happened but has not reached the API yet.
|
|
204
|
+
*
|
|
205
|
+
* `ws.send` returns false when the socket is down, and an install finishing
|
|
206
|
+
* during a reconnect is not a rare case — it is a 300 MB download that takes
|
|
207
|
+
* minutes. The VERSION recovers on its own (the hourly tick and every
|
|
208
|
+
* `hello_ack` re-send the measurement), but `reason` and `changed` are the
|
|
209
|
+
* audit line itself: dropped, «why is my Codex suddenly different» has no
|
|
210
|
+
* answer anywhere, which is precisely what Р14 exists to prevent.
|
|
211
|
+
*
|
|
212
|
+
* Kept in memory rather than on disk on purpose. A daemon that died mid-install
|
|
213
|
+
* has a bigger hole than one lost line, and persisting it would risk filing an
|
|
214
|
+
* install that a rollback then undid.
|
|
215
|
+
*/
|
|
216
|
+
private readonly pendingVersionChanges;
|
|
217
|
+
/**
|
|
218
|
+
* Two installs cannot overlap — the local lock sees to that — but two can
|
|
219
|
+
* both FINISH inside one outage, and each is a line the log owes. A queue
|
|
220
|
+
* rather than a slot, because the second would otherwise overwrite the first
|
|
221
|
+
* and the older install would be the one that vanished.
|
|
222
|
+
*
|
|
223
|
+
* Bounded: an outage long enough to strand nine installs is one where the
|
|
224
|
+
* missing audit lines are not the problem worth solving.
|
|
225
|
+
*/
|
|
226
|
+
private static readonly MAX_PENDING_VERSION_CHANGES;
|
|
227
|
+
publishAgentVersions(reason: 'measured' | 'manual' | 'auto', changed?: {
|
|
228
|
+
agent: string;
|
|
229
|
+
from: string | null;
|
|
230
|
+
to: string;
|
|
231
|
+
}): Promise<void>;
|
|
232
|
+
private queueVersionChange;
|
|
233
|
+
/**
|
|
234
|
+
* Р13: the agent of a starting session, moved forward in the background.
|
|
235
|
+
*
|
|
236
|
+
* The whole mechanism hangs off ONE fact — whether the API put a version in
|
|
237
|
+
* the descriptor. It does that only for machines whose «Auto-update agents»
|
|
238
|
+
* switch is on, so there is no second copy of the setting here to disagree
|
|
239
|
+
* with it, and a runner that is told nothing does nothing.
|
|
240
|
+
*
|
|
241
|
+
* Never throws and never reports anything into the session feed. A person who
|
|
242
|
+
* pressed «start» asked for a session; a failed background download is not
|
|
243
|
+
* their business, and a scary red line about one would be worse than the
|
|
244
|
+
* silence. Where it IS visible is the server card: the `agent_versions` frame
|
|
245
|
+
* at the end goes out after every outcome, so a machine that ended up without
|
|
246
|
+
* a working agent says so within seconds rather than at the next hourly tick.
|
|
247
|
+
*/
|
|
248
|
+
private maybeAutoUpdateAgent;
|
|
249
|
+
/** Once a day, per Р12 — the vendor adds at most one file a day. */
|
|
250
|
+
private static readonly AGENT_CLEANUP_INTERVAL_MS;
|
|
251
|
+
/** Long enough for `hello_ack` to have told us which sessions are live. */
|
|
252
|
+
private static readonly AGENT_CLEANUP_FIRST_DELAY_MS;
|
|
253
|
+
private readonly agentCleanupTimer;
|
|
254
|
+
private readonly agentCleanupFirstTimer;
|
|
255
|
+
/**
|
|
256
|
+
* Throw away the Claude version files nobody will run again (Р12, §4.7).
|
|
257
|
+
*
|
|
258
|
+
* Gated on NO LIVE AGENT PROCESS — `liveSessionCount`, not `sessions.size`.
|
|
259
|
+
*
|
|
260
|
+
* The stricter reading («nothing tracked at all») was the first version of
|
|
261
|
+
* this gate and it made the feature dead: parked sessions sit in that map
|
|
262
|
+
* until somebody closes them, so on any machine actually in use the sweep
|
|
263
|
+
* would never once have run, and Р12 would have shipped as a no-op nobody
|
|
264
|
+
* noticed. A parked session is not a risk to a version file either — it has
|
|
265
|
+
* no process, and when it wakes it relaunches through the launcher, which is
|
|
266
|
+
* the one file this sweep never touches.
|
|
267
|
+
*
|
|
268
|
+
* What guards a version somebody IS running is the per-file check in
|
|
269
|
+
* `agent-cleanup.ts` (`/proc/<pid>/exe`), which is the plan's own mechanism —
|
|
270
|
+
* «проверка по списку процессов». This gate is the cheap belt beside it: it
|
|
271
|
+
* keeps the sweep away from the minutes when Claude is most likely to be
|
|
272
|
+
* mid-spawn, where the process check has a window it cannot see.
|
|
273
|
+
*
|
|
274
|
+
* Also skipped while an install holds the lock: `claude install <version>` is
|
|
275
|
+
* rewriting the launcher right then, and «which file is current» has no stable
|
|
276
|
+
* answer until it finishes.
|
|
277
|
+
*/
|
|
278
|
+
private sweepOldAgentVersions;
|
|
126
279
|
/** How often the seat report re-derives the truth. See the constructor. */
|
|
127
280
|
private static readonly SLOTS_REPORT_INTERVAL_MS;
|
|
128
281
|
private readonly slotsTimer;
|