@bridge4dev/runner 0.22.1 → 0.26.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/dist/auth-relay.d.ts +33 -3
- package/dist/auth-relay.js +199 -16
- package/dist/environment.d.ts +171 -0
- package/dist/environment.js +409 -0
- package/dist/git.d.ts +10 -0
- package/dist/git.js +94 -5
- package/dist/index.js +328 -19
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +11 -0
- package/dist/protocol.d.ts +2 -2
- package/dist/self-update.d.ts +14 -0
- package/dist/self-update.js +45 -0
- package/dist/service-unit.d.ts +13 -1
- package/dist/service-unit.js +41 -10
- package/dist/supervisor.js +35 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/service-unit.js
CHANGED
|
@@ -20,10 +20,31 @@ import { systemdUserHome } from './paths.js';
|
|
|
20
20
|
export const SERVICE_NAME = 'devbridge-runner';
|
|
21
21
|
const COMMAND_NAME = 'devbridge-runner';
|
|
22
22
|
/** `<prefix>/bin/devbridge-runner` for an installed package, else the script. */
|
|
23
|
+
/**
|
|
24
|
+
* A path that will not exist after a reboot.
|
|
25
|
+
*
|
|
26
|
+
* `fnm` (and `nvm`/`volta` in the same spirit) puts the active version's `bin`
|
|
27
|
+
* into a per-shell directory under `/run/user/<uid>/fnm_multishells/…` — tmpfs,
|
|
28
|
+
* created for one shell session. `command -v devbridge-runner` resolves there,
|
|
29
|
+
* so baking it into a unit produces a service that works until the shell that
|
|
30
|
+
* installed it goes away, and then fails with status=127 forever. Observed on a
|
|
31
|
+
* real install, 2026-07-31, on a ROOT install — this is not a dedicated-user
|
|
32
|
+
* problem, it is a per-user node manager problem.
|
|
33
|
+
*/
|
|
34
|
+
export function isEphemeralPath(target) {
|
|
35
|
+
// Deliberately narrow: `/run` and `/dev/shm` are tmpfs by definition, and
|
|
36
|
+
// `fnm_multishells` is named because fnm also offers non-tmpfs layouts. `/tmp`
|
|
37
|
+
// is NOT here — a package installed there is odd but survives, and tests build
|
|
38
|
+
// their fake installs in temp directories.
|
|
39
|
+
return (/^\/(run|proc)\//.test(target) ||
|
|
40
|
+
target.startsWith('/dev/shm/') ||
|
|
41
|
+
target.includes('fnm_multishells'));
|
|
42
|
+
}
|
|
23
43
|
export function unitExecTarget(argv1 = process.argv[1] ?? '') {
|
|
24
|
-
// Invoked through the command itself: that is already the stable path
|
|
44
|
+
// Invoked through the command itself: that is already the stable path —
|
|
45
|
+
// unless it lives in a directory that disappears with the shell.
|
|
25
46
|
try {
|
|
26
|
-
if (fs.lstatSync(argv1).isSymbolicLink()) {
|
|
47
|
+
if (fs.lstatSync(argv1).isSymbolicLink() && !isEphemeralPath(path.resolve(argv1))) {
|
|
27
48
|
return { execStart: path.resolve(argv1), viaCommand: true };
|
|
28
49
|
}
|
|
29
50
|
}
|
|
@@ -43,8 +64,9 @@ export function unitExecTarget(argv1 = process.argv[1] ?? '') {
|
|
|
43
64
|
let dir = path.dirname(script);
|
|
44
65
|
for (let i = 0; i < 6; i++) {
|
|
45
66
|
const candidate = path.join(dir, 'bin', COMMAND_NAME);
|
|
46
|
-
if (fs.existsSync(candidate))
|
|
67
|
+
if (fs.existsSync(candidate) && !isEphemeralPath(candidate)) {
|
|
47
68
|
return { execStart: candidate, viaCommand: true };
|
|
69
|
+
}
|
|
48
70
|
const parent = path.dirname(dir);
|
|
49
71
|
if (parent === dir)
|
|
50
72
|
break;
|
|
@@ -56,13 +78,19 @@ export function unitExecTarget(argv1 = process.argv[1] ?? '') {
|
|
|
56
78
|
export function unitPath(home = systemdUserHome()) {
|
|
57
79
|
return path.join(home, '.config', 'systemd', 'user', `${SERVICE_NAME}.service`);
|
|
58
80
|
}
|
|
59
|
-
export function buildUnit(execStart) {
|
|
81
|
+
export function buildUnit(execStart, nodeBinary = process.execPath) {
|
|
60
82
|
const target = execStart ?? unitExecTarget().execStart;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The interpreter is ALWAYS spelled out, even for the command symlink.
|
|
85
|
+
*
|
|
86
|
+
* The file carries `#!/usr/bin/env node`, and relying on that shebang means
|
|
87
|
+
* relying on `node` being on the PATH systemd gives the service — which it is
|
|
88
|
+
* not when node came from fnm/nvm/volta. That produced `status=127` and an
|
|
89
|
+
* endless restart loop on a real machine. Node runs a file with a shebang
|
|
90
|
+
* perfectly well (it is a comment to it), so naming the interpreter costs
|
|
91
|
+
* nothing and removes the dependency entirely.
|
|
92
|
+
*/
|
|
93
|
+
const command = `${nodeBinary} ${target} daemon`;
|
|
66
94
|
return ([
|
|
67
95
|
'[Unit]',
|
|
68
96
|
'Description=DevBridge Dev Runner',
|
|
@@ -206,6 +234,9 @@ export function unitIsBroken(readFile = (p) => fs.readFileSync(p, 'utf8')) {
|
|
|
206
234
|
const target = parts[0]?.endsWith('node') ? parts[1] : parts[0];
|
|
207
235
|
if (!target)
|
|
208
236
|
return false;
|
|
209
|
-
|
|
237
|
+
// Gone already, or living somewhere that will be gone after a reboot — the
|
|
238
|
+
// second one still runs today, which is exactly why it has to be repaired
|
|
239
|
+
// before the reboot rather than after it.
|
|
240
|
+
return !fs.existsSync(target) || isEphemeralPath(target) || isEphemeralPath(parts[0] ?? '');
|
|
210
241
|
}
|
|
211
242
|
//# sourceMappingURL=service-unit.js.map
|
package/dist/supervisor.js
CHANGED
|
@@ -12,8 +12,9 @@ import { VerifyRunner, runOneOffCommand, } from './verify.js';
|
|
|
12
12
|
import { VerifyReportQueue } from './verify-queue.js';
|
|
13
13
|
import { applySession, gitBranches, gitCommit, gitDiff, gitLog, gitPush, gitRefs, gitShow, gitStatus, revertApply, gitStage, gitUnstage, gitDiscard, gitPull, gitMergeAbort, updateFromBase, workspaceState, } from './gitops.js';
|
|
14
14
|
import { fsView } from './fsview.js';
|
|
15
|
-
import { agentAuthStatuses, AuthRelay } from './auth-relay.js';
|
|
15
|
+
import { agentAuthStatuses, AuthRelay, clearAgentAuthFailure, noteAgentAuthFailure, } from './auth-relay.js';
|
|
16
16
|
import { selfUpdate } from './self-update.js';
|
|
17
|
+
import { rememberWorkspacePath } from './environment.js';
|
|
17
18
|
import { composeMessageWithAttachments, saveAttachments, } from './attachments.js';
|
|
18
19
|
export class Supervisor {
|
|
19
20
|
ws;
|
|
@@ -297,6 +298,10 @@ export class Supervisor {
|
|
|
297
298
|
* would be a lock bought for nothing.
|
|
298
299
|
*/
|
|
299
300
|
async prepareWorkspace(descriptor) {
|
|
301
|
+
// Also here, not only at bind time: a server paired before this existed has
|
|
302
|
+
// never sent a `validate_path`, and its projects would be invisible to
|
|
303
|
+
// `doctor` until somebody re-bound them.
|
|
304
|
+
rememberWorkspacePath(descriptor.workspace.path);
|
|
300
305
|
if (descriptor.workMode === 'DIRECT') {
|
|
301
306
|
return prepareDirectWorkspace(descriptor.workspace.path);
|
|
302
307
|
}
|
|
@@ -794,7 +799,12 @@ export class Supervisor {
|
|
|
794
799
|
if (!running.session)
|
|
795
800
|
return;
|
|
796
801
|
running.parkRequested = true;
|
|
802
|
+
// `code` is what the dashboard reads to tell «parked» from «your turn»
|
|
803
|
+
// (#124). The prose stays for runners older than 0.23.0, which the
|
|
804
|
+
// dashboard still matches on; delete that fallback once the fleet has
|
|
805
|
+
// moved, not before.
|
|
797
806
|
this.sendEvent(running, 'system_note', {
|
|
807
|
+
code: 'session_parked',
|
|
798
808
|
text: 'Session parked — the runner switched to another session. Send a message to resume.',
|
|
799
809
|
});
|
|
800
810
|
running.session.stop('session_parked');
|
|
@@ -867,6 +877,15 @@ export class Supervisor {
|
|
|
867
877
|
});
|
|
868
878
|
return;
|
|
869
879
|
}
|
|
880
|
+
// The retry is spent and the agent is still refused — this is the only
|
|
881
|
+
// authority on a login the credentials file cannot see through (a
|
|
882
|
+
// provider-side revocation leaves the file looking perfectly healthy).
|
|
883
|
+
// The panel is told from here, not from a guess (#121).
|
|
884
|
+
if (isAuthCode(event.code)) {
|
|
885
|
+
const refused = relayAgent(String(descriptor.agent).toLowerCase());
|
|
886
|
+
if (refused)
|
|
887
|
+
noteAgentAuthFailure(refused);
|
|
888
|
+
}
|
|
870
889
|
// Forward the code: the API stores the payload as-is, so the dashboard
|
|
871
890
|
// can offer "Sign in" instead of a dead error card.
|
|
872
891
|
this.sendEvent(running, 'error', {
|
|
@@ -906,6 +925,13 @@ export class Supervisor {
|
|
|
906
925
|
}
|
|
907
926
|
return;
|
|
908
927
|
case 'message':
|
|
928
|
+
// The provider answered, so this sign-in works — drop any refusal we
|
|
929
|
+
// are still holding against it (#121).
|
|
930
|
+
if (event.role === 'assistant') {
|
|
931
|
+
const working = relayAgent(String(descriptor.agent).toLowerCase());
|
|
932
|
+
if (working)
|
|
933
|
+
clearAgentAuthFailure(working);
|
|
934
|
+
}
|
|
909
935
|
this.sendEvent(running, 'message', { role: event.role, text: event.text });
|
|
910
936
|
return;
|
|
911
937
|
case 'thinking':
|
|
@@ -1214,6 +1240,7 @@ export class Supervisor {
|
|
|
1214
1240
|
// Parked session: the follow-up message becomes the resume prompt.
|
|
1215
1241
|
if (!this.ensureCapacity(running.descriptor.id)) {
|
|
1216
1242
|
this.sendEvent(running, 'system_note', {
|
|
1243
|
+
code: 'runner_busy',
|
|
1217
1244
|
text: (this.maxSessions === 1
|
|
1218
1245
|
? 'The runner is busy with another session — this one continues as soon as it finishes its turn.'
|
|
1219
1246
|
: `The runner is busy with ${this.maxSessions} other sessions — this one continues as soon as one of them finishes its turn.`) +
|
|
@@ -1579,6 +1606,10 @@ export class Supervisor {
|
|
|
1579
1606
|
if (!path)
|
|
1580
1607
|
return void reply({ ok: false, error: 'path argument is required' });
|
|
1581
1608
|
const validation = await validateWorkspacePath(path);
|
|
1609
|
+
// Remembered so `devbridge-runner doctor` can check the permissions
|
|
1610
|
+
// of the real projects without being told which they are.
|
|
1611
|
+
if (validation.ok)
|
|
1612
|
+
rememberWorkspacePath(path);
|
|
1582
1613
|
return void reply({
|
|
1583
1614
|
ok: validation.ok,
|
|
1584
1615
|
result: validation,
|
|
@@ -2209,6 +2240,9 @@ export class Supervisor {
|
|
|
2209
2240
|
if (!agent || !code)
|
|
2210
2241
|
return void reply({ ok: false, error: 'agent and code are required' });
|
|
2211
2242
|
const result = await this.authRelay.submitCode(agent, code);
|
|
2243
|
+
// A fresh credential outranks anything we remember about the old one.
|
|
2244
|
+
if (result.ok)
|
|
2245
|
+
clearAgentAuthFailure(agent);
|
|
2212
2246
|
return void reply({
|
|
2213
2247
|
ok: result.ok,
|
|
2214
2248
|
result,
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.26.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED