@almyty/runner 1.2.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.
Files changed (59) hide show
  1. package/README.md +72 -0
  2. package/dist/binaries.d.ts +30 -0
  3. package/dist/binaries.js +99 -0
  4. package/dist/binaries.js.map +1 -0
  5. package/dist/cli.d.ts +12 -0
  6. package/dist/cli.js +128 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/coding-agents/detect.d.ts +12 -0
  9. package/dist/coding-agents/detect.js +35 -0
  10. package/dist/coding-agents/detect.js.map +1 -0
  11. package/dist/coding-agents/index.d.ts +15 -0
  12. package/dist/coding-agents/index.js +15 -0
  13. package/dist/coding-agents/index.js.map +1 -0
  14. package/dist/coding-agents/registry.d.ts +9 -0
  15. package/dist/coding-agents/registry.js +260 -0
  16. package/dist/coding-agents/registry.js.map +1 -0
  17. package/dist/coding-agents/spawn-spec.d.ts +47 -0
  18. package/dist/coding-agents/spawn-spec.js +58 -0
  19. package/dist/coding-agents/spawn-spec.js.map +1 -0
  20. package/dist/coding-agents/status.d.ts +38 -0
  21. package/dist/coding-agents/status.js +111 -0
  22. package/dist/coding-agents/status.js.map +1 -0
  23. package/dist/coding-agents/types.d.ts +104 -0
  24. package/dist/coding-agents/types.js +18 -0
  25. package/dist/coding-agents/types.js.map +1 -0
  26. package/dist/coding-sessions.d.ts +80 -0
  27. package/dist/coding-sessions.js +121 -0
  28. package/dist/coding-sessions.js.map +1 -0
  29. package/dist/config.d.ts +49 -0
  30. package/dist/config.js +180 -0
  31. package/dist/config.js.map +1 -0
  32. package/dist/daemon.d.ts +71 -0
  33. package/dist/daemon.js +353 -0
  34. package/dist/daemon.js.map +1 -0
  35. package/dist/handlers.d.ts +39 -0
  36. package/dist/handlers.js +299 -0
  37. package/dist/handlers.js.map +1 -0
  38. package/dist/index.d.ts +17 -0
  39. package/dist/index.js +18 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/policy.d.ts +16 -0
  42. package/dist/policy.js +134 -0
  43. package/dist/policy.js.map +1 -0
  44. package/dist/process-manager.d.ts +107 -0
  45. package/dist/process-manager.js +334 -0
  46. package/dist/process-manager.js.map +1 -0
  47. package/dist/protocol.d.ts +53 -0
  48. package/dist/protocol.js +39 -0
  49. package/dist/protocol.js.map +1 -0
  50. package/dist/runtime-info.d.ts +23 -0
  51. package/dist/runtime-info.js +35 -0
  52. package/dist/runtime-info.js.map +1 -0
  53. package/dist/streamable-client.d.ts +69 -0
  54. package/dist/streamable-client.js +204 -0
  55. package/dist/streamable-client.js.map +1 -0
  56. package/dist/types.d.ts +112 -0
  57. package/dist/types.js +21 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +54 -0
package/dist/daemon.js ADDED
@@ -0,0 +1,353 @@
1
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, chmodSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import { resolveCredentialsOrExit } from '@almyty/client';
5
+ import { loadConfig } from './config.js';
6
+ import { detectRuntimeInfo, RUNNER_VERSION } from './runtime-info.js';
7
+ import { createDefaultAdapterFactory, ProcessManager } from './process-manager.js';
8
+ import { StreamableClient, envelope } from './streamable-client.js';
9
+ import { dispatchHandler } from './handlers.js';
10
+ import { CodingSessionManager } from './coding-sessions.js';
11
+ const STATE_DIR = join(homedir(), '.almyty', 'runner');
12
+ const PID_FILE = join(STATE_DIR, 'daemon.pid');
13
+ const STATUS_FILE = join(STATE_DIR, 'status.json');
14
+ const HEARTBEAT_INTERVAL_MS = 30_000;
15
+ /** Backoff between session re-establish attempts after a 404 session-lost. */
16
+ const SESSION_LOST_BACKOFF_MS = [500, 1_000, 2_000, 5_000, 10_000];
17
+ /** Consecutive session-lost failures tolerated before exiting fatal. */
18
+ const MAX_SESSION_LOST_STREAK = 8;
19
+ /**
20
+ * Runner daemon. Responsibilities, in order:
21
+ *
22
+ * 1. Resolve config (defaults <- files <- env <- flags).
23
+ * 2. Resolve credentials (env <- ~/.almyty/credentials.json).
24
+ * 3. Detect runtime info + probe binaries.
25
+ * 4. POST /runners/register with the snapshot.
26
+ * 5. Open the Streamable HTTP stream and start dispatching incoming
27
+ * envelopes to handlers.
28
+ * 6. Heartbeat every 30s.
29
+ * 7. On SIGTERM / SIGINT: send a final shutdown envelope, wait
30
+ * briefly for in-flight responses, then exit.
31
+ *
32
+ * The daemon writes its PID and a status snapshot to ~/.almyty/runner/
33
+ * so `almyty runner status` and `almyty runner stop` can find it.
34
+ */
35
+ export class RunnerDaemon {
36
+ resolved = null;
37
+ client = null;
38
+ processes = null;
39
+ coding = null;
40
+ runnerId = null;
41
+ heartbeatTimer = null;
42
+ status = null;
43
+ /** Guards against overlapping re-establish attempts. */
44
+ reestablishing = false;
45
+ /** Consecutive failed re-establish attempts; bounds the retry loop. */
46
+ sessionLostStreak = 0;
47
+ /**
48
+ * Bootstraps the daemon. Returns when the runner has registered and
49
+ * the stream is open. Errors here exit the process; caller is the
50
+ * CLI's `start` command, which doesn't try to recover from a failed
51
+ * register/connect — just prints and dies.
52
+ */
53
+ async start(flags) {
54
+ const resolved = loadConfig({ flags });
55
+ this.resolved = resolved;
56
+ const credentials = resolveCredentialsOrExit();
57
+ const backendUrl = resolved.backendUrl || credentials.url;
58
+ process.stdout.write(`almyty-runner v${RUNNER_VERSION} starting\n`);
59
+ process.stdout.write(`name=${resolved.name} url=${backendUrl}\n`);
60
+ // Detect runtime info before anything else; if a probe hangs (e.g.
61
+ // a misbehaving node-pty install) we want to fail fast here rather
62
+ // than after registration has succeeded.
63
+ const runtime = await detectRuntimeInfo({ binaries: resolved.binaryProbeList });
64
+ process.stdout.write(`detected ${Object.values(runtime.binaries).filter(v => v).length}/${resolved.binaryProbeList.length} binaries on PATH\n`);
65
+ // Register over the existing REST endpoint (POST /runners/register).
66
+ // Streamable HTTP isn't used for registration itself — registration
67
+ // is a one-shot HTTP exchange that returns the runner row plus the
68
+ // effective config, which we use to confirm what limits the backend
69
+ // applied. After registration we switch to Streamable HTTP for the
70
+ // long-lived connection.
71
+ const regResp = await fetch(`${backendUrl}/runners/register`, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ 'Authorization': `Bearer ${credentials.token}`,
76
+ },
77
+ body: JSON.stringify({
78
+ name: resolved.name,
79
+ labels: resolved.labels,
80
+ runtimeInfo: runtime,
81
+ config: resolved.config,
82
+ }),
83
+ });
84
+ if (!regResp.ok) {
85
+ const text = await regResp.text();
86
+ throw new Error(`register failed: ${regResp.status} ${text}`);
87
+ }
88
+ const regBody = await regResp.json();
89
+ this.runnerId = regBody.data.runner.id;
90
+ process.stdout.write(`registered as ${this.runnerId}\n`);
91
+ // Construct the process manager with the effective config (backend
92
+ // may have constrained max_concurrent below what we requested).
93
+ const effective = regBody.data.effectiveConfig;
94
+ this.processes = new ProcessManager(createDefaultAdapterFactory(), effective.maxConcurrent);
95
+ // Coding-session registry for the chat bridge. Output events are sent
96
+ // through the same streamable client the heartbeats ride; the client is
97
+ // captured lazily so construction order doesn't matter.
98
+ this.coding = new CodingSessionManager(this.processes, (payload) => {
99
+ void this.client?.send(envelope('event', payload)).catch((err) => {
100
+ process.stderr.write(`coding event send failed: ${err?.message ?? err}\n`);
101
+ });
102
+ });
103
+ // Open the Streamable HTTP stream.
104
+ this.client = new StreamableClient({ baseUrl: backendUrl, token: credentials.token });
105
+ this.wireClient();
106
+ // First POST mints the session id; send a hello envelope so the
107
+ // backend's StreamableHttpTransport assigns one.
108
+ await this.client.send(envelope('event', { kind: 'runner.hello', runnerId: this.runnerId }));
109
+ await this.client.openStream();
110
+ // Heartbeat loop. This timer is deliberately REF'd: it is the daemon's
111
+ // keep-alive. The GET command stream can briefly drop (e.g. a reconnect
112
+ // after a wrong-replica 404), and during that gap the fetch handle is
113
+ // gone; if the heartbeat timer were unref'd, the event loop would drain
114
+ // and the process would exit(0) BEFORE the first heartbeat ever fired —
115
+ // so the runner could never report online. Keeping it ref'd holds the
116
+ // process up across stream gaps; SIGINT/SIGTERM clears it for a clean exit.
117
+ this.heartbeatTimer = setInterval(() => this.heartbeat().catch(err => {
118
+ process.stderr.write(`heartbeat failed: ${err.message}\n`);
119
+ }), HEARTBEAT_INTERVAL_MS);
120
+ // Send one immediately so the runner reports online within seconds rather
121
+ // than waiting a full interval for the first beat.
122
+ void this.heartbeat().catch(err => {
123
+ process.stderr.write(`initial heartbeat failed: ${err.message}\n`);
124
+ });
125
+ this.installSignalHandlers();
126
+ this.writeState({
127
+ pid: process.pid,
128
+ startedAt: new Date().toISOString(),
129
+ runnerName: resolved.name,
130
+ backendUrl,
131
+ runnerId: this.runnerId,
132
+ sessionId: this.client.getSessionId(),
133
+ inUseProcesses: 0,
134
+ connectionState: 'online',
135
+ });
136
+ process.stdout.write(`runner online; press ctrl-c to exit\n`);
137
+ }
138
+ /**
139
+ * Exit signal flow. On the first SIGINT/SIGTERM, transition to
140
+ * draining and inform the backend; on the second, hard exit.
141
+ */
142
+ installSignalHandlers() {
143
+ let drainingSent = false;
144
+ const handle = (sig) => {
145
+ if (drainingSent) {
146
+ process.stderr.write(`\nsecond ${sig}; force exit\n`);
147
+ process.exit(1);
148
+ }
149
+ drainingSent = true;
150
+ process.stderr.write(`\n${sig} received; draining...\n`);
151
+ this.gracefulShutdown().finally(() => process.exit(0));
152
+ };
153
+ process.on('SIGINT', () => handle('SIGINT'));
154
+ process.on('SIGTERM', () => handle('SIGTERM'));
155
+ }
156
+ async gracefulShutdown() {
157
+ try {
158
+ if (this.heartbeatTimer)
159
+ clearInterval(this.heartbeatTimer);
160
+ if (this.client) {
161
+ await this.client.send(envelope('event', { kind: 'runner.draining' })).catch(() => { });
162
+ this.client.stop();
163
+ }
164
+ this.deleteState();
165
+ }
166
+ catch (err) {
167
+ process.stderr.write(`shutdown error: ${err.message}\n`);
168
+ }
169
+ }
170
+ wireClient() {
171
+ if (!this.client)
172
+ return;
173
+ this.client.on('envelope', (env) => {
174
+ if (env.type === 'request') {
175
+ this.handleRequest(env).catch(err => {
176
+ process.stderr.write(`request handling error: ${err.message}\n`);
177
+ });
178
+ }
179
+ // Heartbeat / event envelopes from the server are observational;
180
+ // the runner doesn't react to them in v1.0.
181
+ });
182
+ this.client.on('reconnect', info => {
183
+ process.stderr.write(`reconnecting (attempt ${info.attempt}, delay ${info.delayMs}ms): ${info.reason}\n`);
184
+ this.updateState({ connectionState: 'reconnecting' });
185
+ });
186
+ this.client.on('open', () => {
187
+ this.updateState({ connectionState: 'online', sessionId: this.client?.getSessionId() ?? null });
188
+ });
189
+ this.client.on('session-lost', () => {
190
+ // The backend forgot our session (404 UNKNOWN_SESSION). The common
191
+ // cause is a multi-replica backend with pod-local streamable sessions:
192
+ // our mint-POST landed on one pod and the GET stream load-balanced to
193
+ // another that never saw the session. Exiting here was too fragile —
194
+ // a single wrong-replica hit killed the runner permanently. Instead,
195
+ // re-establish a fresh session (re-mint + reopen the stream). The
196
+ // client already cleared its session id, so the next send mints a new
197
+ // one; with backoff this lands on a consistent pod and stays online.
198
+ void this.reestablishSession();
199
+ });
200
+ }
201
+ async handleRequest(req) {
202
+ // Trust model: inbound requests are trusted at the CHANNEL level. The
203
+ // runner authenticates itself to the backend at registration, the
204
+ // command stream rides the resulting authenticated session, and the
205
+ // backend URL is required to be https for any non-loopback host (see
206
+ // config.ts), which closes the MITM / stream-injection vectors. We do
207
+ // NOT additionally verify a per-request signature here — that would
208
+ // defend against a *compromised backend* and needs a coordinated
209
+ // backend signing scheme (tracked separately). Each command is still
210
+ // gated by the execution policy (isolation/deny/cwd/env) before it runs.
211
+ if (!this.processes || !this.client || !this.resolved)
212
+ return;
213
+ const ctx = {
214
+ processes: this.processes,
215
+ runnerName: this.resolved.name,
216
+ labels: this.resolved.labels,
217
+ maxConcurrent: this.resolved.config.maxConcurrent,
218
+ config: this.resolved.config,
219
+ coding: this.coding ?? undefined,
220
+ };
221
+ const response = await dispatchHandler(ctx, req.payload);
222
+ await this.client.send(envelope('response', response, req.id));
223
+ }
224
+ async heartbeat() {
225
+ if (!this.client || !this.processes)
226
+ return;
227
+ const inUse = this.processes.inUse();
228
+ await this.client.send(envelope('heartbeat', { ts: Date.now(), inUse }));
229
+ this.updateState({ inUseProcesses: inUse });
230
+ }
231
+ /**
232
+ * Re-establish the command stream after a session-lost (404). Re-mints a
233
+ * session (hello envelope) and reopens the GET stream, with backoff. Only
234
+ * gives up — exiting fatal — after MAX_SESSION_LOST_STREAK consecutive
235
+ * failures, so a transient wrong-replica hit recovers transparently while a
236
+ * genuinely-down backend still terminates rather than spinning forever.
237
+ */
238
+ async reestablishSession() {
239
+ if (this.reestablishing || !this.client)
240
+ return;
241
+ this.reestablishing = true;
242
+ this.updateState({ connectionState: 'reconnecting' });
243
+ try {
244
+ this.sessionLostStreak++;
245
+ if (this.sessionLostStreak > MAX_SESSION_LOST_STREAK) {
246
+ process.stderr.write(`backend session lost ${this.sessionLostStreak}x in a row; giving up\n`);
247
+ this.updateState({ connectionState: 'fatal' });
248
+ process.exit(2);
249
+ }
250
+ const delay = SESSION_LOST_BACKOFF_MS[Math.min(this.sessionLostStreak - 1, SESSION_LOST_BACKOFF_MS.length - 1)];
251
+ process.stderr.write(`backend session lost; re-establishing in ${delay}ms (attempt ${this.sessionLostStreak})\n`);
252
+ await new Promise(r => setTimeout(r, delay));
253
+ // Re-mint a session (client already cleared the stale id) and reopen.
254
+ await this.client.send(envelope('event', { kind: 'runner.hello', runnerId: this.runnerId }));
255
+ await this.client.openStream();
256
+ // openStream's 'open' event resets connectionState to 'online'; a clean
257
+ // reopen means the streak is broken.
258
+ this.sessionLostStreak = 0;
259
+ this.updateState({ connectionState: 'online', sessionId: this.client.getSessionId() });
260
+ process.stdout.write('backend session re-established\n');
261
+ }
262
+ catch (err) {
263
+ process.stderr.write(`re-establish failed: ${err?.message ?? err}\n`);
264
+ // Retry: openStream/send failures schedule the client's own reconnect,
265
+ // and another session-lost will re-enter here; release the guard so it can.
266
+ }
267
+ finally {
268
+ this.reestablishing = false;
269
+ }
270
+ }
271
+ writeState(s) {
272
+ // status.json holds the sessionId (bearer-equivalent for the command
273
+ // channel) — keep the dir and file owner-only.
274
+ mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
275
+ try {
276
+ chmodSync(STATE_DIR, 0o700);
277
+ }
278
+ catch { /* best-effort */ }
279
+ writeFileSync(PID_FILE, String(s.pid));
280
+ writeFileSync(STATUS_FILE, JSON.stringify(s, null, 2), { mode: 0o600 });
281
+ try {
282
+ chmodSync(STATUS_FILE, 0o600);
283
+ }
284
+ catch { /* best-effort */ }
285
+ this.status = s;
286
+ }
287
+ updateState(patch) {
288
+ if (!this.status)
289
+ return;
290
+ this.status = { ...this.status, ...patch };
291
+ try {
292
+ writeFileSync(STATUS_FILE, JSON.stringify(this.status, null, 2));
293
+ }
294
+ catch { /* */ }
295
+ }
296
+ deleteState() {
297
+ try {
298
+ unlinkSync(PID_FILE);
299
+ }
300
+ catch { /* */ }
301
+ try {
302
+ unlinkSync(STATUS_FILE);
303
+ }
304
+ catch { /* */ }
305
+ }
306
+ }
307
+ // ── status / stop helpers (used by CLI subcommands) ─────────────────
308
+ export function readStatus() {
309
+ try {
310
+ if (!existsSync(STATUS_FILE))
311
+ return null;
312
+ const raw = readFileSync(STATUS_FILE, 'utf-8');
313
+ const status = JSON.parse(raw);
314
+ if (!isAlive(status.pid)) {
315
+ // Stale state file: PID's gone.
316
+ try {
317
+ unlinkSync(STATUS_FILE);
318
+ }
319
+ catch { /* */ }
320
+ try {
321
+ unlinkSync(PID_FILE);
322
+ }
323
+ catch { /* */ }
324
+ return null;
325
+ }
326
+ return status;
327
+ }
328
+ catch {
329
+ return null;
330
+ }
331
+ }
332
+ export function stopDaemon() {
333
+ const status = readStatus();
334
+ if (!status)
335
+ return false;
336
+ try {
337
+ process.kill(status.pid, 'SIGTERM');
338
+ return true;
339
+ }
340
+ catch {
341
+ return false;
342
+ }
343
+ }
344
+ function isAlive(pid) {
345
+ try {
346
+ process.kill(pid, 0);
347
+ return true;
348
+ }
349
+ catch {
350
+ return false;
351
+ }
352
+ }
353
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { ProcessManager } from './process-manager.js';
2
+ import { detectRuntimeInfo } from './runtime-info.js';
3
+ import { RequestPayload, ResponsePayload } from './protocol.js';
4
+ import { type ProbeExec } from './binaries.js';
5
+ import { CodingSessionManager } from './coding-sessions.js';
6
+ import { RunnerConfig } from './types.js';
7
+ /**
8
+ * Runner-side method dispatch. The protocol surface is the public
9
+ * v1.0 contract documented in the runner spec; method names are
10
+ * dotted strings ("process.spawn", "shell.exec", "runner.info").
11
+ *
12
+ * Every handler:
13
+ * - Pulls workspaceId out of the request payload (required for
14
+ * process.* and shell.*; optional for runner.info).
15
+ * - Validates params just enough to surface a typed error
16
+ * (RunnerError with a code) rather than crashing.
17
+ * - Returns a ResponsePayload with ok=true|false; the daemon wraps
18
+ * it in a `response` envelope and posts it back over the
19
+ * Streamable HTTP transport.
20
+ *
21
+ * Error mapping: unhandled exceptions become INTERNAL responses; any
22
+ * RunnerError surfaces with its own code intact so the agent can
23
+ * reason about retryability.
24
+ */
25
+ export interface HandlerContext {
26
+ processes: ProcessManager;
27
+ runnerName: string;
28
+ labels: Record<string, string>;
29
+ maxConcurrent: number;
30
+ /** Runner execution policy (isolation, denyPatterns, cwd roots, …). */
31
+ config: RunnerConfig;
32
+ /** Test injection point for runner.info; returns the cached probe map. */
33
+ cachedRuntimeInfo?: Awaited<ReturnType<typeof detectRuntimeInfo>>;
34
+ /** Coding-session registry (chat-to-runner bridge); wired by the daemon. */
35
+ coding?: CodingSessionManager;
36
+ /** Test injection for coding.list's binary probing. */
37
+ probeExec?: ProbeExec;
38
+ }
39
+ export declare function dispatchHandler(ctx: HandlerContext, req: RequestPayload): Promise<ResponsePayload>;