@minion-stack/shells-bridge 0.1.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/dist/acp-client.d.ts +24 -0
- package/dist/acp-client.d.ts.map +1 -0
- package/dist/acp-client.js +134 -0
- package/dist/acp-client.js.map +1 -0
- package/dist/backup.d.ts +22 -0
- package/dist/backup.d.ts.map +1 -0
- package/dist/backup.js +68 -0
- package/dist/backup.js.map +1 -0
- package/dist/bridge.d.ts +38 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +364 -0
- package/dist/bridge.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +53 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
export interface AcpClientOptions {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
cwd: string;
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
}
|
|
8
|
+
export declare class AcpClient extends EventEmitter {
|
|
9
|
+
private readonly opts;
|
|
10
|
+
private proc;
|
|
11
|
+
private nextId;
|
|
12
|
+
private pending;
|
|
13
|
+
private exited;
|
|
14
|
+
constructor(opts: AcpClientOptions);
|
|
15
|
+
start(): void;
|
|
16
|
+
/** Send a JSON-RPC request; resolves with the `result` field. */
|
|
17
|
+
call<T = unknown>(method: string, params?: unknown, timeoutMs?: number): Promise<T>;
|
|
18
|
+
/** Stop the harness gracefully. Caller may force-kill via .kill() if needed. */
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
kill(signal?: NodeJS.Signals): void;
|
|
21
|
+
private onStdoutLine;
|
|
22
|
+
private failAllPending;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=acp-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acp-client.d.ts","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AA4BD,qBAAa,SAAU,SAAQ,YAAY;IAM7B,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,IAAI,CAA+C;IAC3D,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAoF;IACnG,OAAO,CAAC,MAAM,CAAS;gBAEM,IAAI,EAAE,gBAAgB;IAInD,KAAK,IAAI,IAAI;IA0Bb,iEAAiE;IACjE,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;IAyBnF,gFAAgF;IAC1E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3B,IAAI,CAAC,MAAM,GAAE,MAAM,CAAC,OAAmB,GAAG,IAAI;IAI9C,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,cAAc;CAIvB"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Minimal ACP (Agent Client Protocol) client.
|
|
2
|
+
//
|
|
3
|
+
// ACP is JSON-RPC 2.0 over stdio. The bridge spawns the harness as a child
|
|
4
|
+
// process, writes JSON-RPC requests to its stdin, and reads responses /
|
|
5
|
+
// streaming `session/update` notifications from its stdout. stderr is logged.
|
|
6
|
+
//
|
|
7
|
+
// This file intentionally keeps the surface minimal — the bridge only needs
|
|
8
|
+
// `session/prompt`, `session/cancel`, and the `session/update` notification.
|
|
9
|
+
// Adding more methods is a one-line addition to AcpClient.callRaw().
|
|
10
|
+
//
|
|
11
|
+
// Spec ref: ACP at https://hermes-agent.nousresearch.com/docs/developer-guide/architecture
|
|
12
|
+
// (HERMES exposes ACP via `acp_adapter/`). Same shape used by Zed, JetBrains, VS Code.
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { EventEmitter } from 'node:events';
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
function isResponse(value) {
|
|
17
|
+
return typeof value.id === 'number';
|
|
18
|
+
}
|
|
19
|
+
export class AcpClient extends EventEmitter {
|
|
20
|
+
opts;
|
|
21
|
+
proc = null;
|
|
22
|
+
nextId = 1;
|
|
23
|
+
pending = new Map();
|
|
24
|
+
exited = false;
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
super();
|
|
27
|
+
this.opts = opts;
|
|
28
|
+
}
|
|
29
|
+
start() {
|
|
30
|
+
if (this.proc) {
|
|
31
|
+
throw new Error('AcpClient already started');
|
|
32
|
+
}
|
|
33
|
+
const proc = spawn(this.opts.command, this.opts.args, {
|
|
34
|
+
cwd: this.opts.cwd,
|
|
35
|
+
env: this.opts.env ?? process.env,
|
|
36
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
this.proc = proc;
|
|
39
|
+
const lines = createInterface({ input: proc.stdout });
|
|
40
|
+
lines.on('line', (line) => this.onStdoutLine(line));
|
|
41
|
+
proc.stderr.on('data', (chunk) => {
|
|
42
|
+
this.emit('stderr', chunk.toString('utf8'));
|
|
43
|
+
});
|
|
44
|
+
proc.on('exit', (code, signal) => {
|
|
45
|
+
this.exited = true;
|
|
46
|
+
this.failAllPending(new Error(`harness exited code=${code} signal=${signal ?? 'none'}`));
|
|
47
|
+
this.emit('exit', { code, signal });
|
|
48
|
+
});
|
|
49
|
+
proc.on('error', (err) => {
|
|
50
|
+
this.emit('error', err);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Send a JSON-RPC request; resolves with the `result` field. */
|
|
54
|
+
call(method, params, timeoutMs = 60_000) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
if (!this.proc || this.exited) {
|
|
57
|
+
return reject(new Error('AcpClient not started or harness exited'));
|
|
58
|
+
}
|
|
59
|
+
const id = this.nextId++;
|
|
60
|
+
const req = { jsonrpc: '2.0', id, method, params };
|
|
61
|
+
const timer = setTimeout(() => {
|
|
62
|
+
this.pending.delete(id);
|
|
63
|
+
reject(new Error(`ACP call '${method}' timed out after ${timeoutMs}ms`));
|
|
64
|
+
}, timeoutMs);
|
|
65
|
+
this.pending.set(id, {
|
|
66
|
+
resolve: (v) => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
resolve(v);
|
|
69
|
+
},
|
|
70
|
+
reject: (e) => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
reject(e);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
this.proc.stdin.write(`${JSON.stringify(req)}\n`);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Stop the harness gracefully. Caller may force-kill via .kill() if needed. */
|
|
79
|
+
async stop() {
|
|
80
|
+
if (!this.proc || this.exited)
|
|
81
|
+
return;
|
|
82
|
+
this.proc.stdin.end();
|
|
83
|
+
// Give the harness a chance to flush state.
|
|
84
|
+
await new Promise((resolve) => {
|
|
85
|
+
if (!this.proc || this.exited)
|
|
86
|
+
return resolve();
|
|
87
|
+
const t = setTimeout(() => resolve(), 5_000);
|
|
88
|
+
this.proc.once('exit', () => {
|
|
89
|
+
clearTimeout(t);
|
|
90
|
+
resolve();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
kill(signal = 'SIGTERM') {
|
|
95
|
+
if (this.proc && !this.exited)
|
|
96
|
+
this.proc.kill(signal);
|
|
97
|
+
}
|
|
98
|
+
onStdoutLine(line) {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
if (!trimmed)
|
|
101
|
+
return;
|
|
102
|
+
let msg;
|
|
103
|
+
try {
|
|
104
|
+
msg = JSON.parse(trimmed);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
this.emit('parse_error', { line: trimmed, error: err });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (isResponse(msg)) {
|
|
111
|
+
const p = this.pending.get(msg.id);
|
|
112
|
+
if (!p)
|
|
113
|
+
return;
|
|
114
|
+
this.pending.delete(msg.id);
|
|
115
|
+
if (msg.error) {
|
|
116
|
+
p.reject(new Error(`ACP error ${msg.error.code}: ${msg.error.message}`));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
p.resolve(msg.result);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Notification (no id). The only one we care about is `session/update`,
|
|
124
|
+
// but emit them all so the bridge can subscribe broadly.
|
|
125
|
+
this.emit('notification', msg);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
failAllPending(err) {
|
|
129
|
+
for (const p of this.pending.values())
|
|
130
|
+
p.reject(err);
|
|
131
|
+
this.pending.clear();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=acp-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acp-client.js","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"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"}
|
package/dist/backup.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface BackupOptions {
|
|
2
|
+
/** Path on disk to archive. */
|
|
3
|
+
workDir: string;
|
|
4
|
+
/** Remote target (e.g. "b2://minion-shells/<shellId>/"). */
|
|
5
|
+
target: string;
|
|
6
|
+
}
|
|
7
|
+
export interface BackupResult {
|
|
8
|
+
backupId: string;
|
|
9
|
+
bytes: number;
|
|
10
|
+
uploadMs: number;
|
|
11
|
+
remotePath: string;
|
|
12
|
+
}
|
|
13
|
+
/** Stream-tar workDir into the backup target. Returns metadata. */
|
|
14
|
+
export declare function backup(opts: BackupOptions): Promise<BackupResult>;
|
|
15
|
+
export interface RestoreOptions {
|
|
16
|
+
/** Where to extract. Bridge typically clears this first. */
|
|
17
|
+
workDir: string;
|
|
18
|
+
/** Full remote path of the archive (e.g. "b2://bucket/<shellId>/<backupId>.tar.gz"). */
|
|
19
|
+
remotePath: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function restore(opts: RestoreOptions): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=backup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,mEAAmE;AACnE,wBAAsB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA8BvE;AAED,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAYjE"}
|
package/dist/backup.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Backup / restore for shells.
|
|
2
|
+
//
|
|
3
|
+
// The harness is authoritative for in-VM state (per spec D3), so we tar the
|
|
4
|
+
// harness's working directory and stream-upload to the configured remote.
|
|
5
|
+
// Today only `b2://bucket/path/` URIs are supported, dispatched via the
|
|
6
|
+
// `rclone` binary which is expected to be installed in the baked image and
|
|
7
|
+
// configured with a remote named "b2".
|
|
8
|
+
//
|
|
9
|
+
// Restore is the inverse: download archive → extract into workDir → harness
|
|
10
|
+
// is started afterwards by the supervisor.
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
/** Stream-tar workDir into the backup target. Returns metadata. */
|
|
14
|
+
export async function backup(opts) {
|
|
15
|
+
const backupId = `${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
16
|
+
const remotePath = joinTarget(opts.target, `${backupId}.tar.gz`);
|
|
17
|
+
const started = Date.now();
|
|
18
|
+
// tar czf - <workDir> | rclone rcat <remotePath>
|
|
19
|
+
const tar = spawn('tar', ['czf', '-', '-C', opts.workDir, '.'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
20
|
+
const rclone = spawn('rclone', ['rcat', '--progress=false', remotePath], {
|
|
21
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
22
|
+
});
|
|
23
|
+
let bytes = 0;
|
|
24
|
+
tar.stdout.on('data', (chunk) => {
|
|
25
|
+
bytes += chunk.length;
|
|
26
|
+
});
|
|
27
|
+
tar.stdout.pipe(rclone.stdin);
|
|
28
|
+
const tarStderr = collectStderr(tar);
|
|
29
|
+
const rcloneStderr = collectStderr(rclone);
|
|
30
|
+
const [tarCode, rcloneCode] = await Promise.all([waitExit(tar), waitExit(rclone)]);
|
|
31
|
+
if (tarCode !== 0)
|
|
32
|
+
throw new Error(`tar exited ${tarCode}: ${tarStderr()}`);
|
|
33
|
+
if (rcloneCode !== 0)
|
|
34
|
+
throw new Error(`rclone rcat exited ${rcloneCode}: ${rcloneStderr()}`);
|
|
35
|
+
return {
|
|
36
|
+
backupId,
|
|
37
|
+
bytes,
|
|
38
|
+
uploadMs: Date.now() - started,
|
|
39
|
+
remotePath,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function restore(opts) {
|
|
43
|
+
// rclone cat <remotePath> | tar xzf - -C <workDir>
|
|
44
|
+
const rclone = spawn('rclone', ['cat', opts.remotePath], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
45
|
+
const tar = spawn('tar', ['xzf', '-', '-C', opts.workDir], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
46
|
+
rclone.stdout.pipe(tar.stdin);
|
|
47
|
+
const rcloneStderr = collectStderr(rclone);
|
|
48
|
+
const tarStderr = collectStderr(tar);
|
|
49
|
+
const [rcloneCode, tarCode] = await Promise.all([waitExit(rclone), waitExit(tar)]);
|
|
50
|
+
if (rcloneCode !== 0)
|
|
51
|
+
throw new Error(`rclone cat exited ${rcloneCode}: ${rcloneStderr()}`);
|
|
52
|
+
if (tarCode !== 0)
|
|
53
|
+
throw new Error(`tar exited ${tarCode}: ${tarStderr()}`);
|
|
54
|
+
}
|
|
55
|
+
function joinTarget(base, name) {
|
|
56
|
+
return base.endsWith('/') ? `${base}${name}` : `${base}/${name}`;
|
|
57
|
+
}
|
|
58
|
+
function waitExit(proc) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
proc.on('exit', (code) => resolve(code ?? -1));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function collectStderr(proc) {
|
|
64
|
+
const chunks = [];
|
|
65
|
+
proc.stderr.on('data', (c) => chunks.push(c));
|
|
66
|
+
return () => Buffer.concat(chunks).toString('utf8').slice(0, 2000);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BridgeConfig } from './config.js';
|
|
2
|
+
export declare class Bridge {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private ws;
|
|
5
|
+
private acp;
|
|
6
|
+
private activeRuns;
|
|
7
|
+
/** ACP session id → bridge runId. Used to correlate notifications back to the originating invoke. */
|
|
8
|
+
private acpSessionToRun;
|
|
9
|
+
private heartbeatTimer;
|
|
10
|
+
private heartbeatMs;
|
|
11
|
+
private reconnectDelay;
|
|
12
|
+
private shuttingDown;
|
|
13
|
+
private nextFrameId;
|
|
14
|
+
private pendingBridgeRpc;
|
|
15
|
+
constructor(config: BridgeConfig);
|
|
16
|
+
start(): void;
|
|
17
|
+
shutdown(): Promise<void>;
|
|
18
|
+
private connect;
|
|
19
|
+
private scheduleReconnect;
|
|
20
|
+
private send;
|
|
21
|
+
private emitEvent;
|
|
22
|
+
/** Bridge-initiated RPC (register, heartbeat, fatal). */
|
|
23
|
+
private callGateway;
|
|
24
|
+
private onFrame;
|
|
25
|
+
private handleResponse;
|
|
26
|
+
private handleRequest;
|
|
27
|
+
private dispatch;
|
|
28
|
+
private register;
|
|
29
|
+
private startHeartbeat;
|
|
30
|
+
private reportFatal;
|
|
31
|
+
private handleInvoke;
|
|
32
|
+
private handleCancel;
|
|
33
|
+
private handleBackup;
|
|
34
|
+
private handleRestore;
|
|
35
|
+
private onAcpNotification;
|
|
36
|
+
private emitFinal;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUhD,qBAAa,MAAM;IAaL,OAAO,CAAC,QAAQ,CAAC,MAAM;IAZnC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,UAAU,CAAgC;IAClD,qGAAqG;IACrG,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,gBAAgB,CAAoF;gBAE/E,MAAM,EAAE,YAAY;IAUjD,KAAK,IAAI,IAAI;IAgBP,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAW/B,OAAO,CAAC,OAAO;IA4Bf,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,SAAS;IAKjB,yDAAyD;IACzD,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,cAAc;YAQR,aAAa;YAeb,QAAQ;YAwBR,QAAQ;IAyBtB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,WAAW;YAgBL,YAAY;YAoBZ,YAAY;YAWZ,YAAY;YAgBZ,aAAa;IAS3B,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,SAAS;CAmBlB"}
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// Bridge core — owns the WS connection to the gateway and routes RPCs to
|
|
2
|
+
// the in-process ACP client + backup module.
|
|
3
|
+
//
|
|
4
|
+
// Lifecycle:
|
|
5
|
+
// 1. construct → start() : spawn harness, open WS, register
|
|
6
|
+
// 2. WS frame in → handleRequest() dispatches by method
|
|
7
|
+
// 3. ACP notification (session/update) → emit shell.delta event upstream
|
|
8
|
+
// 4. WS drop → reconnect with backoff (state preserved; harness keeps running)
|
|
9
|
+
// 5. SIGTERM → graceful shutdown (close WS → stop harness → exit)
|
|
10
|
+
import { WebSocket } from 'ws';
|
|
11
|
+
import { SHELLS_METHODS, SHELLS_EVENTS, } from '@minion-stack/shared';
|
|
12
|
+
import { AcpClient } from './acp-client.js';
|
|
13
|
+
import { backup, restore } from './backup.js';
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
15
|
+
import { statSync } from 'node:fs';
|
|
16
|
+
export class Bridge {
|
|
17
|
+
config;
|
|
18
|
+
ws = null;
|
|
19
|
+
acp;
|
|
20
|
+
activeRuns = new Map();
|
|
21
|
+
/** ACP session id → bridge runId. Used to correlate notifications back to the originating invoke. */
|
|
22
|
+
acpSessionToRun = new Map();
|
|
23
|
+
heartbeatTimer = null;
|
|
24
|
+
heartbeatMs;
|
|
25
|
+
reconnectDelay;
|
|
26
|
+
shuttingDown = false;
|
|
27
|
+
nextFrameId = 1;
|
|
28
|
+
pendingBridgeRpc = new Map();
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.acp = new AcpClient({
|
|
32
|
+
command: config.harnessCommand,
|
|
33
|
+
args: config.harnessArgs,
|
|
34
|
+
cwd: config.harnessWorkDir,
|
|
35
|
+
});
|
|
36
|
+
this.heartbeatMs = config.heartbeatMs;
|
|
37
|
+
this.reconnectDelay = config.reconnectMinMs;
|
|
38
|
+
}
|
|
39
|
+
start() {
|
|
40
|
+
this.acp.on('notification', (msg) => {
|
|
41
|
+
this.onAcpNotification(msg);
|
|
42
|
+
});
|
|
43
|
+
this.acp.on('stderr', (chunk) => {
|
|
44
|
+
// Forwarded to journald via stdout; image's systemd unit captures it.
|
|
45
|
+
process.stderr.write(`[harness] ${chunk}`);
|
|
46
|
+
});
|
|
47
|
+
this.acp.on('exit', ({ code, signal }) => {
|
|
48
|
+
if (this.shuttingDown)
|
|
49
|
+
return;
|
|
50
|
+
this.reportFatal('unknown', `harness exited code=${code} signal=${signal ?? 'none'}`);
|
|
51
|
+
});
|
|
52
|
+
this.acp.start();
|
|
53
|
+
this.connect();
|
|
54
|
+
}
|
|
55
|
+
async shutdown() {
|
|
56
|
+
this.shuttingDown = true;
|
|
57
|
+
if (this.heartbeatTimer)
|
|
58
|
+
clearInterval(this.heartbeatTimer);
|
|
59
|
+
if (this.ws)
|
|
60
|
+
this.ws.close(1000, 'shutdown');
|
|
61
|
+
await this.acp.stop();
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// WS lifecycle
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
connect() {
|
|
67
|
+
if (this.shuttingDown)
|
|
68
|
+
return;
|
|
69
|
+
const ws = new WebSocket(this.config.gatewayUrl, {
|
|
70
|
+
headers: { 'x-shell-id': this.config.shellId },
|
|
71
|
+
});
|
|
72
|
+
this.ws = ws;
|
|
73
|
+
ws.on('open', () => {
|
|
74
|
+
this.reconnectDelay = this.config.reconnectMinMs;
|
|
75
|
+
void this.register();
|
|
76
|
+
});
|
|
77
|
+
ws.on('message', (data) => {
|
|
78
|
+
const text = typeof data === 'string' ? data : data.toString('utf8');
|
|
79
|
+
this.onFrame(text);
|
|
80
|
+
});
|
|
81
|
+
ws.on('close', () => {
|
|
82
|
+
this.ws = null;
|
|
83
|
+
if (this.heartbeatTimer) {
|
|
84
|
+
clearInterval(this.heartbeatTimer);
|
|
85
|
+
this.heartbeatTimer = null;
|
|
86
|
+
}
|
|
87
|
+
if (!this.shuttingDown)
|
|
88
|
+
this.scheduleReconnect();
|
|
89
|
+
});
|
|
90
|
+
ws.on('error', (err) => {
|
|
91
|
+
process.stderr.write(`[bridge] ws error: ${err.message}\n`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
scheduleReconnect() {
|
|
95
|
+
const delay = this.reconnectDelay;
|
|
96
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.config.reconnectMaxMs);
|
|
97
|
+
setTimeout(() => this.connect(), delay);
|
|
98
|
+
}
|
|
99
|
+
send(frame) {
|
|
100
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
101
|
+
return;
|
|
102
|
+
this.ws.send(JSON.stringify(frame));
|
|
103
|
+
}
|
|
104
|
+
emitEvent(event, payload) {
|
|
105
|
+
const frame = { type: 'event', event, payload };
|
|
106
|
+
this.send(frame);
|
|
107
|
+
}
|
|
108
|
+
/** Bridge-initiated RPC (register, heartbeat, fatal). */
|
|
109
|
+
callGateway(method, params, timeoutMs = 30_000) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
112
|
+
return reject(new Error('not connected'));
|
|
113
|
+
}
|
|
114
|
+
const id = `b${this.nextFrameId++}`;
|
|
115
|
+
const timer = setTimeout(() => {
|
|
116
|
+
this.pendingBridgeRpc.delete(id);
|
|
117
|
+
reject(new Error(`gateway rpc '${method}' timed out`));
|
|
118
|
+
}, timeoutMs);
|
|
119
|
+
this.pendingBridgeRpc.set(id, {
|
|
120
|
+
resolve: (v) => {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
resolve(v);
|
|
123
|
+
},
|
|
124
|
+
reject: (e) => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
reject(e);
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
const frame = { type: 'req', id, method, params };
|
|
130
|
+
this.send(frame);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Frame routing
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
onFrame(raw) {
|
|
137
|
+
let frame;
|
|
138
|
+
try {
|
|
139
|
+
frame = JSON.parse(raw);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (frame.type === 'req')
|
|
145
|
+
void this.handleRequest(frame);
|
|
146
|
+
else if (frame.type === 'res')
|
|
147
|
+
this.handleResponse(frame);
|
|
148
|
+
// event frames from gateway are ignored — bridge is a leaf.
|
|
149
|
+
}
|
|
150
|
+
handleResponse(frame) {
|
|
151
|
+
const p = this.pendingBridgeRpc.get(frame.id);
|
|
152
|
+
if (!p)
|
|
153
|
+
return;
|
|
154
|
+
this.pendingBridgeRpc.delete(frame.id);
|
|
155
|
+
if (frame.ok)
|
|
156
|
+
p.resolve(frame.payload);
|
|
157
|
+
else
|
|
158
|
+
p.reject(new Error(frame.error?.message ?? 'gateway rpc failed'));
|
|
159
|
+
}
|
|
160
|
+
async handleRequest(frame) {
|
|
161
|
+
try {
|
|
162
|
+
const result = await this.dispatch(frame.method, frame.params);
|
|
163
|
+
this.send({ type: 'res', id: frame.id, ok: true, payload: result });
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
167
|
+
this.send({
|
|
168
|
+
type: 'res',
|
|
169
|
+
id: frame.id,
|
|
170
|
+
ok: false,
|
|
171
|
+
error: { code: 'BRIDGE_ERROR', message: msg },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async dispatch(method, params) {
|
|
176
|
+
switch (method) {
|
|
177
|
+
case SHELLS_METHODS.invoke:
|
|
178
|
+
return this.handleInvoke(params);
|
|
179
|
+
case SHELLS_METHODS.cancel:
|
|
180
|
+
return this.handleCancel(params);
|
|
181
|
+
case SHELLS_METHODS.backupNow:
|
|
182
|
+
return this.handleBackup(params);
|
|
183
|
+
// shells.health — bridge-local liveness, no harness call
|
|
184
|
+
case 'shells.health':
|
|
185
|
+
return { ok: true, ts: Date.now() };
|
|
186
|
+
// shells.restore — gateway calls this after wake. Bridge expects to be
|
|
187
|
+
// mid-startup with workDir already empty.
|
|
188
|
+
case 'shells.restore':
|
|
189
|
+
return this.handleRestore(params);
|
|
190
|
+
default:
|
|
191
|
+
throw new Error(`unsupported method: ${method}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Register / heartbeat
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
async register() {
|
|
198
|
+
const params = {
|
|
199
|
+
shellId: this.config.shellId,
|
|
200
|
+
deviceToken: this.config.deviceToken,
|
|
201
|
+
harness: this.config.harness,
|
|
202
|
+
harnessVersion: this.config.harnessVersion,
|
|
203
|
+
bridgeVersion: PACKAGE_VERSION,
|
|
204
|
+
capabilities: {
|
|
205
|
+
acpMethods: ['session/prompt', 'session/cancel', 'session/update'],
|
|
206
|
+
streaming: true,
|
|
207
|
+
backupKind: 'tar+rclone',
|
|
208
|
+
maxConcurrentRuns: 1,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
try {
|
|
212
|
+
const res = await this.callGateway(SHELLS_METHODS.register, params);
|
|
213
|
+
this.heartbeatMs = res.heartbeatMs ?? this.heartbeatMs;
|
|
214
|
+
this.startHeartbeat();
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
process.stderr.write(`[bridge] register failed: ${err.message}\n`);
|
|
218
|
+
// Close ws; reconnect will retry.
|
|
219
|
+
this.ws?.close();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
startHeartbeat() {
|
|
223
|
+
if (this.heartbeatTimer)
|
|
224
|
+
clearInterval(this.heartbeatTimer);
|
|
225
|
+
this.heartbeatTimer = setInterval(() => {
|
|
226
|
+
const params = {
|
|
227
|
+
shellId: this.config.shellId,
|
|
228
|
+
activeRunIds: [...this.activeRuns.keys()],
|
|
229
|
+
diskUsedMB: measureDiskMB(this.config.harnessWorkDir),
|
|
230
|
+
memoryUsedMB: measureMemoryMB(),
|
|
231
|
+
};
|
|
232
|
+
this.callGateway(SHELLS_METHODS.heartbeat, params).catch((err) => {
|
|
233
|
+
process.stderr.write(`[bridge] heartbeat failed: ${err.message}\n`);
|
|
234
|
+
});
|
|
235
|
+
}, this.heartbeatMs);
|
|
236
|
+
}
|
|
237
|
+
reportFatal(reason, message) {
|
|
238
|
+
const params = { shellId: this.config.shellId, reason, message };
|
|
239
|
+
this.callGateway(SHELLS_METHODS.fatal, params)
|
|
240
|
+
.catch(() => {
|
|
241
|
+
/* best-effort */
|
|
242
|
+
})
|
|
243
|
+
.finally(() => {
|
|
244
|
+
// systemd Restart=always will recycle us.
|
|
245
|
+
process.exit(1);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Caller-facing RPC handlers
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
async handleInvoke(params) {
|
|
252
|
+
const runId = `run_${randomUUID()}`;
|
|
253
|
+
const startedAt = Date.now();
|
|
254
|
+
this.activeRuns.set(runId, { runId, sessionId: params.sessionId, startedAt });
|
|
255
|
+
this.acpSessionToRun.set(params.sessionId, runId);
|
|
256
|
+
// Fire ACP `session/prompt` — do NOT await; bridge replies to the caller
|
|
257
|
+
// immediately with runId, then streams updates via shell.delta events.
|
|
258
|
+
this.acp
|
|
259
|
+
.call('session/prompt', { sessionId: params.sessionId, input: params.input })
|
|
260
|
+
.then((result) => {
|
|
261
|
+
this.emitFinal(runId, params.sessionId, 'final', startedAt, { result });
|
|
262
|
+
})
|
|
263
|
+
.catch((err) => {
|
|
264
|
+
this.emitFinal(runId, params.sessionId, 'error', startedAt, { errorMessage: err.message });
|
|
265
|
+
});
|
|
266
|
+
return { runId, startedAt };
|
|
267
|
+
}
|
|
268
|
+
async handleCancel(params) {
|
|
269
|
+
const run = this.activeRuns.get(params.runId);
|
|
270
|
+
if (!run)
|
|
271
|
+
return { cancelled: false };
|
|
272
|
+
try {
|
|
273
|
+
await this.acp.call('session/cancel', { sessionId: run.sessionId });
|
|
274
|
+
return { cancelled: true };
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return { cancelled: false };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async handleBackup(params) {
|
|
281
|
+
if (!this.config.backupTarget)
|
|
282
|
+
throw new Error('no backup target configured');
|
|
283
|
+
const res = await backup({
|
|
284
|
+
workDir: this.config.harnessWorkDir,
|
|
285
|
+
target: this.config.backupTarget,
|
|
286
|
+
});
|
|
287
|
+
this.emitEvent(SHELLS_EVENTS.backupDone, {
|
|
288
|
+
shellId: this.config.shellId,
|
|
289
|
+
backupId: res.backupId,
|
|
290
|
+
bytes: res.bytes,
|
|
291
|
+
uploadMs: res.uploadMs,
|
|
292
|
+
trigger: 'manual',
|
|
293
|
+
});
|
|
294
|
+
return { backupId: res.backupId, bytes: res.bytes, uploadMs: res.uploadMs };
|
|
295
|
+
}
|
|
296
|
+
async handleRestore(params) {
|
|
297
|
+
await restore({ workDir: this.config.harnessWorkDir, remotePath: params.remotePath });
|
|
298
|
+
return { ok: true };
|
|
299
|
+
}
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// ACP → upstream event translation
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
onAcpNotification(msg) {
|
|
304
|
+
if (msg.method !== 'session/update')
|
|
305
|
+
return;
|
|
306
|
+
const params = msg.params;
|
|
307
|
+
const sessionId = params?.sessionId;
|
|
308
|
+
if (!sessionId)
|
|
309
|
+
return;
|
|
310
|
+
const runId = this.acpSessionToRun.get(sessionId);
|
|
311
|
+
if (!runId)
|
|
312
|
+
return;
|
|
313
|
+
const payload = {
|
|
314
|
+
shellId: this.config.shellId,
|
|
315
|
+
runId,
|
|
316
|
+
sessionId,
|
|
317
|
+
seq: nextSeq(this.activeRuns.get(runId)),
|
|
318
|
+
acpUpdate: msg.params,
|
|
319
|
+
};
|
|
320
|
+
this.emitEvent(SHELLS_EVENTS.delta, payload);
|
|
321
|
+
}
|
|
322
|
+
emitFinal(runId, sessionId, state, startedAt, extra) {
|
|
323
|
+
const payload = {
|
|
324
|
+
shellId: this.config.shellId,
|
|
325
|
+
runId,
|
|
326
|
+
sessionId,
|
|
327
|
+
state,
|
|
328
|
+
errorMessage: extra.errorMessage,
|
|
329
|
+
durationMs: Date.now() - startedAt,
|
|
330
|
+
};
|
|
331
|
+
this.emitEvent(SHELLS_EVENTS.final, payload);
|
|
332
|
+
this.activeRuns.delete(runId);
|
|
333
|
+
this.acpSessionToRun.delete(sessionId);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Helpers
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
/** Per-run monotonic delta sequence number, stored on the ActiveRun. */
|
|
340
|
+
function nextSeq(run) {
|
|
341
|
+
if (!run)
|
|
342
|
+
return 0;
|
|
343
|
+
const r = run;
|
|
344
|
+
r._seq = (r._seq ?? 0) + 1;
|
|
345
|
+
return r._seq;
|
|
346
|
+
}
|
|
347
|
+
function measureDiskMB(path) {
|
|
348
|
+
try {
|
|
349
|
+
const stat = statSync(path);
|
|
350
|
+
// statSync().size is not directory-recursive; this is a placeholder.
|
|
351
|
+
// Real impl will shell out to `du -sm <path>` and cache for heartbeatMs.
|
|
352
|
+
return Math.round(stat.size / (1024 * 1024));
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function measureMemoryMB() {
|
|
359
|
+
const mem = process.memoryUsage();
|
|
360
|
+
return Math.round(mem.rss / (1024 * 1024));
|
|
361
|
+
}
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
363
|
+
globalThis.PACKAGE_VERSION ??= '0.1.0';
|
|
364
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,6CAA6C;AAC7C,EAAE;AACF,aAAa;AACb,+DAA+D;AAC/D,2DAA2D;AAC3D,2EAA2E;AAC3E,iFAAiF;AACjF,oEAAoE;AAEpE,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EACL,cAAc,EACd,aAAa,GAkBd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQnC,MAAM,OAAO,MAAM;IAaY;IAZrB,EAAE,GAAqB,IAAI,CAAC;IAC5B,GAAG,CAAY;IACf,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAClD,qGAAqG;IAC7F,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,cAAc,GAA0B,IAAI,CAAC;IAC7C,WAAW,CAAS;IACpB,cAAc,CAAS;IACvB,YAAY,GAAG,KAAK,CAAC;IACrB,WAAW,GAAG,CAAC,CAAC;IAChB,gBAAgB,GAAG,IAAI,GAAG,EAAyE,CAAC;IAE5G,YAA6B,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;QAC/C,IAAI,CAAC,GAAG,GAAG,IAAI,SAAS,CAAC;YACvB,OAAO,EAAE,MAAM,CAAC,cAAc;YAC9B,IAAI,EAAE,MAAM,CAAC,WAAW;YACxB,GAAG,EAAE,MAAM,CAAC,cAAc;SAC3B,CAAC,CAAC;QACH,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACtC,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;IAC9C,CAAC;IAED,KAAK;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,GAAyC,EAAE,EAAE;YACxE,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE;YACtC,sEAAsE;YACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAA0D,EAAE,EAAE;YAC/F,IAAI,IAAI,CAAC,YAAY;gBAAE,OAAO;YAC9B,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,uBAAuB,IAAI,WAAW,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;QACxF,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,IAAI,CAAC,cAAc;YAAE,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5D,IAAI,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC7C,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IAED,8EAA8E;IAC9E,eAAe;IACf,8EAA8E;IAEtE,OAAO;QACb,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO;QAC9B,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE;YAC/C,OAAO,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE;SAC/C,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QAEb,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACjB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;YACjD,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACrE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACf,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;gBACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;gBACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC7B,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,YAAY;gBAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACnD,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAuB,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,iBAAiB;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC;QAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QACpF,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAEO,IAAI,CAAC,KAAmB;QAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;YAAE,OAAO;QAC9D,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACtC,CAAC;IAEO,SAAS,CAAI,KAAa,EAAE,OAAU;QAC5C,MAAM,KAAK,GAAe,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAC5D,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED,yDAAyD;IACjD,WAAW,CAAc,MAAc,EAAE,MAAe,EAAE,SAAS,GAAG,MAAM;QAClF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACtD,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;YAC5C,CAAC;YACD,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACjC,MAAM,CAAC,IAAI,KAAK,CAAC,gBAAgB,MAAM,aAAa,CAAC,CAAC,CAAC;YACzD,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE;gBAC5B,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;oBACb,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,OAAO,CAAC,CAAM,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;oBACZ,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,MAAM,CAAC,CAAC,CAAC,CAAC;gBACZ,CAAC;aACF,CAAC,CAAC;YACH,MAAM,KAAK,GAAiB,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAChE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,gBAAgB;IAChB,8EAA8E;IAEtE,OAAO,CAAC,GAAW;QACzB,IAAI,KAA8B,CAAC;QACnC,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK;YAAE,KAAK,IAAI,CAAC,aAAa,CAAC,KAAgC,CAAC,CAAC;aAC/E,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK;YAAE,IAAI,CAAC,cAAc,CAAC,KAAiC,CAAC,CAAC;QACtF,4DAA4D;IAC9D,CAAC;IAEO,cAAc,CAAC,KAAoB;QACzC,MAAM,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,EAAE;YAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;;YAClC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,IAAI,oBAAoB,CAAC,CAAC,CAAC;IACzE,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,KAAmB;QAC7C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAC/D,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,IAAI,CAAC,IAAI,CAAC;gBACR,IAAI,EAAE,KAAK;gBACX,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,EAAE;aAC9C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,MAAe;QACpD,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,cAAc,CAAC,MAAM;gBACxB,OAAO,IAAI,CAAC,YAAY,CAAC,MAA4B,CAAC,CAAC;YACzD,KAAK,cAAc,CAAC,MAAM;gBACxB,OAAO,IAAI,CAAC,YAAY,CAAC,MAA4B,CAAC,CAAC;YACzD,KAAK,cAAc,CAAC,SAAS;gBAC3B,OAAO,IAAI,CAAC,YAAY,CAAC,MAA+B,CAAC,CAAC;YAC5D,yDAAyD;YACzD,KAAK,eAAe;gBAClB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACtC,uEAAuE;YACvE,0CAA0C;YAC1C,KAAK,gBAAgB;gBACnB,OAAO,IAAI,CAAC,aAAa,CAAC,MAAgC,CAAC,CAAC;YAC9D;gBACE,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,uBAAuB;IACvB,8EAA8E;IAEtE,KAAK,CAAC,QAAQ;QACpB,MAAM,MAAM,GAAyB;YACnC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;YACpC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAA0C;YAC/D,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc;YAC1C,aAAa,EAAE,eAAe;YAC9B,YAAY,EAAE;gBACZ,UAAU,EAAE,CAAC,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,CAAC;gBAClE,SAAS,EAAE,IAAI;gBACf,UAAU,EAAE,YAAY;gBACxB,iBAAiB,EAAE,CAAC;aACrB;SACF,CAAC;QACF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,WAAW,CAAyB,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC5F,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC;YACvD,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA8B,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;YAC9E,kCAAkC;YAClC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,cAAc;QACpB,IAAI,IAAI,CAAC,cAAc;YAAE,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5D,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,MAAM,MAAM,GAA0B;gBACpC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;gBAC5B,YAAY,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;gBACzC,UAAU,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;gBACrD,YAAY,EAAE,eAAe,EAAE;aAChC,CAAC;YACF,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;gBACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC;IAEO,WAAW,CAAC,MAAwB,EAAE,OAAe;QAC3D,MAAM,MAAM,GAAsB,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QACpF,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC;aAC3C,KAAK,CAAC,GAAG,EAAE;YACV,iBAAiB;QACnB,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,0CAA0C;YAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACP,CAAC;IAED,8EAA8E;IAC9E,6BAA6B;IAC7B,8EAA8E;IAEtE,KAAK,CAAC,YAAY,CAAC,MAA0B;QACnD,MAAM,KAAK,GAAG,OAAO,UAAU,EAAE,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAElD,yEAAyE;QACzE,uEAAuE;QACvE,IAAI,CAAC,GAAG;aACL,IAAI,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;aAC5E,IAAI,CAAC,CAAC,MAAe,EAAE,EAAE;YACxB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;YACpB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7F,CAAC,CAAC,CAAC;QAEL,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,MAA0B;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9C,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;YACpE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,MAA6B;QACtD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC9E,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC;YACvB,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc;YACnC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;SACjC,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,UAAU,EAAE;YACvC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,OAAO,EAAE,QAAQ;SAClB,CAAC,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC9E,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,MAA8B;QACxD,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;QACtF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,8EAA8E;IAC9E,mCAAmC;IACnC,8EAA8E;IAEtE,iBAAiB,CAAC,GAAyC;QACjE,IAAI,GAAG,CAAC,MAAM,KAAK,gBAAgB;YAAE,OAAO;QAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,MAA4C,CAAC;QAChE,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,CAAC;QACpC,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,OAAO,GAAsB;YACjC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,KAAK;YACL,SAAS;YACT,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACxC,SAAS,EAAE,GAAG,CAAC,MAAM;SACtB,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAEO,SAAS,CACf,KAAa,EACb,SAAiB,EACjB,KAAwB,EACxB,SAAiB,EACjB,KAAkD;QAElD,MAAM,OAAO,GAAsB;YACjC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,KAAK;YACL,SAAS;YACT,KAAK;YACL,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SACnC,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;CACF;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,wEAAwE;AACxE,SAAS,OAAO,CAAC,GAA0B;IACzC,IAAI,CAAC,GAAG;QAAE,OAAO,CAAC,CAAC;IACnB,MAAM,CAAC,GAAG,GAAoC,CAAC;IAC/C,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5B,qEAAqE;QACrE,yEAAyE;QACzE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,SAAS,eAAe;IACtB,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC;AAC7C,CAAC;AAID,8DAA8D;AAC7D,UAAkB,CAAC,eAAe,KAAK,OAAO,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface BridgeConfig {
|
|
2
|
+
/** Stable shell identifier issued by the gateway at provision time. */
|
|
3
|
+
shellId: string;
|
|
4
|
+
/** Gateway WSS URL, e.g. "wss://gateway.example.com/ws". */
|
|
5
|
+
gatewayUrl: string;
|
|
6
|
+
/** One-time device token for `shells.register`. */
|
|
7
|
+
deviceToken: string;
|
|
8
|
+
/** Harness identifier — selects which ACP adapter to instantiate. */
|
|
9
|
+
harness: 'hermes' | 'claude-code' | 'codex' | string;
|
|
10
|
+
/** Version string of the harness binary baked into the image. */
|
|
11
|
+
harnessVersion: string;
|
|
12
|
+
/** Command + args spawned to run the harness via ACP. */
|
|
13
|
+
harnessCommand: string;
|
|
14
|
+
harnessArgs: string[];
|
|
15
|
+
/** Working directory for the harness process. State lives here. */
|
|
16
|
+
harnessWorkDir: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional B2 (or any rclone-compatible) target for backups.
|
|
19
|
+
* Format: "b2://bucket/path/<shellId>/" — bridge appends `<backupId>.tar.gz`.
|
|
20
|
+
*/
|
|
21
|
+
backupTarget?: string;
|
|
22
|
+
/** Reconnect floor (ms). Bridge backs off exponentially up to a cap. */
|
|
23
|
+
reconnectMinMs: number;
|
|
24
|
+
reconnectMaxMs: number;
|
|
25
|
+
/** Heartbeat cadence (ms) — overridden by server response to shells.register. */
|
|
26
|
+
heartbeatMs: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class ConfigError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
export declare function loadConfig(env?: NodeJS.ProcessEnv): BridgeConfig;
|
|
32
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,OAAO,EAAE,QAAQ,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;IACrD,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAoBD,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CA2B7E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Bridge configuration — sourced from environment.
|
|
2
|
+
// Set by the exe.dev VM provisioning step (systemd unit env file).
|
|
3
|
+
export class ConfigError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'ConfigError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function required(name) {
|
|
10
|
+
const value = process.env[name];
|
|
11
|
+
if (!value || value.trim() === '') {
|
|
12
|
+
throw new ConfigError(`Missing required env var: ${name}`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function optionalInt(name, fallback) {
|
|
17
|
+
const raw = process.env[name];
|
|
18
|
+
if (!raw)
|
|
19
|
+
return fallback;
|
|
20
|
+
const n = Number.parseInt(raw, 10);
|
|
21
|
+
if (Number.isNaN(n)) {
|
|
22
|
+
throw new ConfigError(`Env var ${name} must be an integer, got: ${raw}`);
|
|
23
|
+
}
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
export function loadConfig(env = process.env) {
|
|
27
|
+
// Allow injection in tests by swapping process.env reference.
|
|
28
|
+
const cmdRaw = env.SHELLS_HARNESS_CMD;
|
|
29
|
+
if (!cmdRaw) {
|
|
30
|
+
throw new ConfigError('Missing required env var: SHELLS_HARNESS_CMD');
|
|
31
|
+
}
|
|
32
|
+
// Naive splitter — quoted args not supported. Image builder controls this value
|
|
33
|
+
// so naive is fine.
|
|
34
|
+
const [harnessCommand, ...harnessArgs] = cmdRaw.split(/\s+/);
|
|
35
|
+
if (!harnessCommand) {
|
|
36
|
+
throw new ConfigError('SHELLS_HARNESS_CMD is empty');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
shellId: required('SHELLS_SHELL_ID'),
|
|
40
|
+
gatewayUrl: required('SHELLS_GATEWAY_URL'),
|
|
41
|
+
deviceToken: required('SHELLS_DEVICE_TOKEN'),
|
|
42
|
+
harness: required('SHELLS_HARNESS'),
|
|
43
|
+
harnessVersion: required('SHELLS_HARNESS_VERSION'),
|
|
44
|
+
harnessCommand,
|
|
45
|
+
harnessArgs,
|
|
46
|
+
harnessWorkDir: env.SHELLS_HARNESS_WORKDIR ?? '/home/agent/state',
|
|
47
|
+
backupTarget: env.SHELLS_BACKUP_TARGET,
|
|
48
|
+
reconnectMinMs: optionalInt('SHELLS_RECONNECT_MIN_MS', 1000),
|
|
49
|
+
reconnectMaxMs: optionalInt('SHELLS_RECONNECT_MAX_MS', 60_000),
|
|
50
|
+
heartbeatMs: optionalInt('SHELLS_HEARTBEAT_MS', 15_000),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,mEAAmE;AA8BnE,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,WAAW,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,QAAgB;IACjD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACnC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,WAAW,CAAC,WAAW,IAAI,6BAA6B,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,8DAA8D;IAC9D,MAAM,MAAM,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,WAAW,CAAC,8CAA8C,CAAC,CAAC;IACxE,CAAC;IACD,gFAAgF;IAChF,oBAAoB;IACpB,MAAM,CAAC,cAAc,EAAE,GAAG,WAAW,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7D,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,WAAW,CAAC,6BAA6B,CAAC,CAAC;IACvD,CAAC;IAED,OAAO;QACL,OAAO,EAAE,QAAQ,CAAC,iBAAiB,CAAC;QACpC,UAAU,EAAE,QAAQ,CAAC,oBAAoB,CAAC;QAC1C,WAAW,EAAE,QAAQ,CAAC,qBAAqB,CAAC;QAC5C,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC;QACnC,cAAc,EAAE,QAAQ,CAAC,wBAAwB,CAAC;QAClD,cAAc;QACd,WAAW;QACX,cAAc,EAAE,GAAG,CAAC,sBAAsB,IAAI,mBAAmB;QACjE,YAAY,EAAE,GAAG,CAAC,oBAAoB;QACtC,cAAc,EAAE,WAAW,CAAC,yBAAyB,EAAE,IAAI,CAAC;QAC5D,cAAc,EAAE,WAAW,CAAC,yBAAyB,EAAE,MAAM,CAAC;QAC9D,WAAW,EAAE,WAAW,CAAC,qBAAqB,EAAE,MAAM,CAAC;KACxD,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAsDA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @minion-stack/shells-bridge entrypoint.
|
|
3
|
+
//
|
|
4
|
+
// Baked into every shell VM image. Started by systemd:
|
|
5
|
+
//
|
|
6
|
+
// [Service]
|
|
7
|
+
// ExecStart=/usr/bin/shells-bridge
|
|
8
|
+
// EnvironmentFile=/etc/shells-bridge.env
|
|
9
|
+
// Restart=always
|
|
10
|
+
// RestartSec=5s
|
|
11
|
+
//
|
|
12
|
+
// Environment (see config.ts for full list):
|
|
13
|
+
// SHELLS_SHELL_ID — stable shell id
|
|
14
|
+
// SHELLS_GATEWAY_URL — wss://gateway.../ws
|
|
15
|
+
// SHELLS_DEVICE_TOKEN — one-time token for shells.register
|
|
16
|
+
// SHELLS_HARNESS — "hermes" | "claude-code" | "codex" | …
|
|
17
|
+
// SHELLS_HARNESS_VERSION — version string
|
|
18
|
+
// SHELLS_HARNESS_CMD — command line spawned to run the harness via ACP
|
|
19
|
+
// SHELLS_HARNESS_WORKDIR — state dir (default /home/agent/state)
|
|
20
|
+
// SHELLS_BACKUP_TARGET — optional, e.g. b2://bucket/<shellId>/
|
|
21
|
+
import { Bridge } from './bridge.js';
|
|
22
|
+
import { loadConfig, ConfigError } from './config.js';
|
|
23
|
+
async function main() {
|
|
24
|
+
let config;
|
|
25
|
+
try {
|
|
26
|
+
config = loadConfig();
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err instanceof ConfigError) {
|
|
30
|
+
process.stderr.write(`[bridge] ${err.message}\n`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
const bridge = new Bridge(config);
|
|
36
|
+
const shutdown = async (signal) => {
|
|
37
|
+
process.stderr.write(`[bridge] received ${signal}, shutting down\n`);
|
|
38
|
+
await bridge.shutdown();
|
|
39
|
+
process.exit(0);
|
|
40
|
+
};
|
|
41
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
42
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
43
|
+
bridge.start();
|
|
44
|
+
}
|
|
45
|
+
void main().catch((err) => {
|
|
46
|
+
process.stderr.write(`[bridge] fatal: ${err.message}\n`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
49
|
+
export { Bridge } from './bridge.js';
|
|
50
|
+
export { loadConfig, ConfigError } from './config.js';
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,0CAA0C;AAC1C,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,cAAc;AACd,qCAAqC;AACrC,2CAA2C;AAC3C,mBAAmB;AACnB,kBAAkB;AAClB,EAAE;AACF,6CAA6C;AAC7C,8CAA8C;AAC9C,kDAAkD;AAClD,iEAAiE;AACjE,qEAAqE;AACrE,6CAA6C;AAC7C,8EAA8E;AAC9E,oEAAoE;AACpE,oEAAoE;AAEpE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,KAAK,UAAU,IAAI;IACjB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,UAAU,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;IAElC,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAsB,EAAiB,EAAE;QAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,MAAM,mBAAmB,CAAC,CAAC;QACrE,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACtD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEpD,MAAM,CAAC,KAAK,EAAE,CAAC;AACjB,CAAC;AAED,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;IACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAqB,MAAM,aAAa,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@minion-stack/shells-bridge",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "In-VM bridge process for Minion Shells — speaks @minion-stack/shared frames upstream to the gateway and ACP/JSON-RPC downstream to the local harness (HERMES, Claude Code, Codex, etc.).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/NikolasP98/minion-meta.git",
|
|
9
|
+
"directory": "packages/shells-bridge"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"bin": {
|
|
15
|
+
"shells-bridge": "dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=22.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"ws": "^8.21.0",
|
|
35
|
+
"@minion-stack/shared": "0.7.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.10.0",
|
|
39
|
+
"@types/ws": "^8.18.1",
|
|
40
|
+
"oxlint": "^1.66.0",
|
|
41
|
+
"typescript": "^5.7.0",
|
|
42
|
+
"vitest": "^2.1.9",
|
|
43
|
+
"@minion-stack/tsconfig": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"lint": "oxlint src"
|
|
51
|
+
}
|
|
52
|
+
}
|