@drisp/cli 0.4.4 → 0.4.7

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.
@@ -0,0 +1,692 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ startDaemon
4
+ } from "./chunk-SKWAGQXF.js";
5
+ import "./chunk-D4W7RB25.js";
6
+ import {
7
+ openDaemonLog
8
+ } from "./chunk-2OJ3GGIP.js";
9
+ import {
10
+ acquirePidLock,
11
+ attachmentMirrorPath,
12
+ createInstanceSocketClient,
13
+ readAttachmentMirror
14
+ } from "./chunk-ZVOGOZNT.js";
15
+ import {
16
+ readDashboardClientConfig,
17
+ refreshDashboardAccessToken,
18
+ resolveGatewayPaths,
19
+ resolveListenSpec
20
+ } from "./chunk-WRHKXH5M.js";
21
+
22
+ // src/app/entry/supervisor.tsx
23
+ import { spawn } from "child_process";
24
+ import fs2 from "fs";
25
+ import os from "os";
26
+ import path2 from "path";
27
+ import { fileURLToPath } from "url";
28
+
29
+ // src/gateway/adapters/runner/adapter.ts
30
+ var CAPABILITIES = {
31
+ chat: true,
32
+ threads: false,
33
+ relayPermission: false,
34
+ relayQuestion: false
35
+ };
36
+ function createRunnerAdapter(opts) {
37
+ const { runnerId, transport } = opts;
38
+ let unsubscribe = null;
39
+ let ctx = null;
40
+ function inboundFromAssignment(frame) {
41
+ return {
42
+ location: {
43
+ channelId: `runner:${runnerId}`,
44
+ accountId: `runner:${runnerId}`
45
+ },
46
+ sender: { id: `runner:${runnerId}` },
47
+ text: JSON.stringify({
48
+ kind: "job_assignment",
49
+ runId: frame.runId,
50
+ ...frame.runSpec !== void 0 ? { runSpec: frame.runSpec } : {}
51
+ }),
52
+ receivedAt: Date.now(),
53
+ idempotencyKey: frame.runId,
54
+ providerMessageId: frame.runId
55
+ };
56
+ }
57
+ function inboundFromCancel(frame) {
58
+ return {
59
+ location: {
60
+ channelId: `runner:${runnerId}`,
61
+ accountId: `runner:${runnerId}`
62
+ },
63
+ sender: { id: `runner:${runnerId}` },
64
+ text: JSON.stringify({ kind: "cancel", runId: frame.runId }),
65
+ receivedAt: Date.now(),
66
+ idempotencyKey: `cancel:${frame.runId}`,
67
+ providerMessageId: `cancel:${frame.runId}`
68
+ };
69
+ }
70
+ function parseOutbound(text) {
71
+ let env;
72
+ try {
73
+ env = JSON.parse(text);
74
+ } catch (err) {
75
+ throw new Error(
76
+ `runner adapter: malformed outbound envelope: ${err instanceof Error ? err.message : String(err)}`
77
+ );
78
+ }
79
+ if (typeof env !== "object" || env === null) {
80
+ throw new Error("runner adapter: outbound envelope must be an object");
81
+ }
82
+ const obj = env;
83
+ const kind = obj["kind"];
84
+ const runId = obj["runId"];
85
+ if (typeof runId !== "string" || runId.length === 0) {
86
+ throw new Error("runner adapter: outbound envelope missing runId");
87
+ }
88
+ if (kind !== "run_event") {
89
+ throw new Error(`runner adapter: unknown envelope kind: ${String(kind)}`);
90
+ }
91
+ const seq = obj["seq"];
92
+ const eventKind = obj["eventKind"];
93
+ const ts = obj["ts"];
94
+ if (typeof seq !== "number") {
95
+ throw new Error("runner adapter: run_event envelope missing seq");
96
+ }
97
+ if (typeof eventKind !== "string") {
98
+ throw new Error("runner adapter: run_event envelope missing eventKind");
99
+ }
100
+ return {
101
+ type: "run_event",
102
+ runId,
103
+ seq,
104
+ ts: typeof ts === "number" ? ts : Date.now(),
105
+ kind: eventKind,
106
+ ...obj["payload"] !== void 0 ? { payload: obj["payload"] } : {}
107
+ };
108
+ }
109
+ return {
110
+ id: `runner:${runnerId}`,
111
+ capabilities: CAPABILITIES,
112
+ async start(c) {
113
+ ctx = c;
114
+ unsubscribe = transport.subscribe(runnerId, (frame) => {
115
+ const emit = ctx?.emitInbound;
116
+ if (!emit) return;
117
+ if (frame.type === "job_assignment") {
118
+ emit(inboundFromAssignment(frame));
119
+ } else {
120
+ emit(inboundFromCancel(frame));
121
+ }
122
+ });
123
+ ctx.emitHealth({ at: Date.now(), transportOk: transport.isReady() });
124
+ },
125
+ async stop(_reason) {
126
+ if (unsubscribe) {
127
+ unsubscribe();
128
+ unsubscribe = null;
129
+ }
130
+ ctx = null;
131
+ },
132
+ async send(msg) {
133
+ if (!transport.isReady()) {
134
+ throw new Error("runner adapter: transport not ready");
135
+ }
136
+ const frame = parseOutbound(msg.text);
137
+ transport.send(frame);
138
+ return {
139
+ providerMessageId: msg.idempotencyKey,
140
+ deliveredAt: Date.now()
141
+ };
142
+ },
143
+ async probe() {
144
+ const ok = transport.isReady();
145
+ return {
146
+ ok,
147
+ detail: ok ? "transport ready" : "transport not ready",
148
+ checkedAt: Date.now()
149
+ };
150
+ }
151
+ };
152
+ }
153
+
154
+ // src/gateway/adapters/runner/instanceSocketTransport.ts
155
+ function createInstanceSocketRunnerTransport(opts) {
156
+ const log = opts.log ?? (() => {
157
+ });
158
+ const subscribers = /* @__PURE__ */ new Map();
159
+ let connected = true;
160
+ function deliver(runnerId, frame) {
161
+ const set = subscribers.get(runnerId);
162
+ if (!set) return;
163
+ for (const handler of [...set]) {
164
+ try {
165
+ handler(frame);
166
+ } catch (err) {
167
+ log(
168
+ "warn",
169
+ `runner transport handler threw: ${err instanceof Error ? err.message : String(err)}`
170
+ );
171
+ }
172
+ }
173
+ }
174
+ opts.source.onFrame((frame) => {
175
+ if (frame.type === "job_assignment") {
176
+ const f = frame;
177
+ if (!f.runnerId) return;
178
+ deliver(f.runnerId, {
179
+ type: "job_assignment",
180
+ runId: f.runId,
181
+ runSpec: f.runSpec
182
+ });
183
+ return;
184
+ }
185
+ if (frame.type === "cancel") {
186
+ const f = frame;
187
+ if (!f.runnerId) return;
188
+ deliver(f.runnerId, { type: "cancel", runId: f.runId });
189
+ }
190
+ });
191
+ opts.source.onClose(() => {
192
+ connected = false;
193
+ });
194
+ return {
195
+ subscribe(runnerId, handler) {
196
+ let set = subscribers.get(runnerId);
197
+ if (!set) {
198
+ set = /* @__PURE__ */ new Set();
199
+ subscribers.set(runnerId, set);
200
+ }
201
+ set.add(handler);
202
+ return () => {
203
+ const cur = subscribers.get(runnerId);
204
+ if (!cur) return;
205
+ cur.delete(handler);
206
+ if (cur.size === 0) subscribers.delete(runnerId);
207
+ };
208
+ },
209
+ send(frame) {
210
+ opts.source.sendRunEvent({
211
+ runId: frame.runId,
212
+ seq: frame.seq,
213
+ ts: frame.ts,
214
+ kind: frame.kind,
215
+ ...frame.payload !== void 0 ? { payload: frame.payload } : {}
216
+ });
217
+ },
218
+ isReady() {
219
+ return connected;
220
+ }
221
+ };
222
+ }
223
+
224
+ // src/app/dashboard/runnerTransport.ts
225
+ function runnerTransportFromInstanceSocket(opts) {
226
+ const source = {
227
+ onFrame(handler) {
228
+ opts.client.onFrame((frame) => handler(frame));
229
+ },
230
+ onClose(handler) {
231
+ opts.client.onClose(handler);
232
+ },
233
+ sendRunEvent(event) {
234
+ opts.client.sendRunEvent(event);
235
+ }
236
+ };
237
+ return createInstanceSocketRunnerTransport({
238
+ source,
239
+ ...opts.log ? { log: opts.log } : {}
240
+ });
241
+ }
242
+
243
+ // src/app/supervisor/attachmentRunner.ts
244
+ function createAttachmentRunner(opts) {
245
+ const exitHandlers = /* @__PURE__ */ new Set();
246
+ let child = null;
247
+ let exited = null;
248
+ function spawn2() {
249
+ const args = [
250
+ "--attachment-id",
251
+ opts.attachmentId,
252
+ "--runner",
253
+ opts.runnerId,
254
+ ...opts.extraArgs ?? []
255
+ ];
256
+ const next = opts.spawnChild(args);
257
+ exited = new Promise((resolve) => {
258
+ next.once(
259
+ "exit",
260
+ (code, signal) => {
261
+ const event = { code: code ?? 0, signal };
262
+ if (child === next) {
263
+ child = null;
264
+ exited = null;
265
+ }
266
+ for (const handler of exitHandlers) handler(event);
267
+ resolve(event);
268
+ }
269
+ );
270
+ });
271
+ child = next;
272
+ }
273
+ return {
274
+ attachmentId: opts.attachmentId,
275
+ runnerId: opts.runnerId,
276
+ async start() {
277
+ if (child) return;
278
+ spawn2();
279
+ },
280
+ async stop(_reason) {
281
+ const current = child;
282
+ const wait = exited;
283
+ if (!current) return;
284
+ current.kill("SIGTERM");
285
+ await wait;
286
+ },
287
+ onChildExit(handler) {
288
+ exitHandlers.add(handler);
289
+ }
290
+ };
291
+ }
292
+
293
+ // src/app/supervisor/attachmentSet.ts
294
+ function createAttachmentSet(opts) {
295
+ const runners = /* @__PURE__ */ new Map();
296
+ async function stopRunner(runner, reason) {
297
+ try {
298
+ await runner.stop(reason);
299
+ } catch {
300
+ }
301
+ }
302
+ return {
303
+ async reconcile(desired) {
304
+ const desiredById = new Map(desired.map((d) => [d.attachmentId, d]));
305
+ const stops = [];
306
+ for (const [id, runner] of runners) {
307
+ const want = desiredById.get(id);
308
+ if (!want || want.runnerId !== runner.runnerId) {
309
+ runners.delete(id);
310
+ stops.push(stopRunner(runner, "detach"));
311
+ }
312
+ }
313
+ await Promise.all(stops);
314
+ for (const want of desired) {
315
+ if (runners.has(want.attachmentId)) continue;
316
+ const runner = opts.createRunner({
317
+ attachmentId: want.attachmentId,
318
+ runnerId: want.runnerId
319
+ });
320
+ runners.set(want.attachmentId, runner);
321
+ await runner.start();
322
+ }
323
+ },
324
+ list() {
325
+ return [...runners.values()].map((r) => ({
326
+ attachmentId: r.attachmentId,
327
+ runnerId: r.runnerId
328
+ }));
329
+ },
330
+ async shutdown() {
331
+ const stops = [];
332
+ for (const runner of runners.values()) {
333
+ stops.push(stopRunner(runner, "shutdown"));
334
+ }
335
+ runners.clear();
336
+ await Promise.all(stops);
337
+ }
338
+ };
339
+ }
340
+
341
+ // src/app/supervisor/mirrorAttachmentSource.ts
342
+ import fs from "fs";
343
+ import path from "path";
344
+ function createMirrorAttachmentSource(opts = {}) {
345
+ const env = opts.env ?? process.env;
346
+ const log = opts.log ?? (() => {
347
+ });
348
+ const file = attachmentMirrorPath(env);
349
+ const dir = path.dirname(file);
350
+ const basename = path.basename(file);
351
+ const handlers = /* @__PURE__ */ new Set();
352
+ let watcher = null;
353
+ let closed = false;
354
+ function project() {
355
+ const mirror = readAttachmentMirror(env);
356
+ if (!mirror) return [];
357
+ return mirror.attachments.map((a) => ({
358
+ attachmentId: a.runnerId,
359
+ runnerId: a.runnerId
360
+ }));
361
+ }
362
+ function emit() {
363
+ if (closed) return;
364
+ let next;
365
+ try {
366
+ next = project();
367
+ } catch (err) {
368
+ log(
369
+ "warn",
370
+ `supervisor: failed to read attachment mirror: ${err instanceof Error ? err.message : String(err)}`
371
+ );
372
+ return;
373
+ }
374
+ for (const handler of handlers) handler(next);
375
+ }
376
+ function ensureWatcher() {
377
+ if (watcher || closed) return;
378
+ try {
379
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
380
+ } catch {
381
+ }
382
+ try {
383
+ watcher = fs.watch(dir, (_evt, filename) => {
384
+ if (filename && filename !== basename) return;
385
+ emit();
386
+ });
387
+ watcher.on("error", (err) => {
388
+ log(
389
+ "warn",
390
+ `supervisor: mirror watcher error: ${err instanceof Error ? err.message : String(err)}`
391
+ );
392
+ });
393
+ } catch (err) {
394
+ log(
395
+ "warn",
396
+ `supervisor: failed to watch mirror dir ${dir}: ${err instanceof Error ? err.message : String(err)}`
397
+ );
398
+ }
399
+ }
400
+ return {
401
+ initial() {
402
+ return project();
403
+ },
404
+ subscribe(handler) {
405
+ handlers.add(handler);
406
+ ensureWatcher();
407
+ return () => {
408
+ handlers.delete(handler);
409
+ };
410
+ },
411
+ close() {
412
+ closed = true;
413
+ handlers.clear();
414
+ if (watcher) {
415
+ try {
416
+ watcher.close();
417
+ } catch {
418
+ }
419
+ watcher = null;
420
+ }
421
+ }
422
+ };
423
+ }
424
+
425
+ // src/app/supervisor/runSupervisor.ts
426
+ async function runSupervisor(opts) {
427
+ const log = opts.log ?? (() => {
428
+ });
429
+ let stopped = false;
430
+ async function safeReconcile(desired) {
431
+ if (stopped) return;
432
+ try {
433
+ await opts.set.reconcile(desired);
434
+ } catch (err) {
435
+ log(
436
+ "warn",
437
+ `supervisor: reconcile failed: ${err instanceof Error ? err.message : String(err)}`
438
+ );
439
+ }
440
+ }
441
+ const unsubscribe = opts.source.subscribe((next) => {
442
+ void safeReconcile(next);
443
+ });
444
+ await safeReconcile(opts.source.initial());
445
+ return {
446
+ async shutdown() {
447
+ stopped = true;
448
+ unsubscribe();
449
+ await opts.set.shutdown();
450
+ }
451
+ };
452
+ }
453
+
454
+ // src/app/entry/supervisor.tsx
455
+ function supervisorPaths(env = process.env) {
456
+ const xdg = env["XDG_STATE_HOME"];
457
+ const home = env["HOME"] ?? os.homedir();
458
+ const base = xdg && xdg.length > 0 ? xdg : path2.join(home, ".local", "state");
459
+ const dir = path2.join(base, "drisp");
460
+ return {
461
+ dir,
462
+ pidPath: path2.join(dir, "supervisor.pid"),
463
+ logPath: path2.join(dir, "supervisor.log")
464
+ };
465
+ }
466
+ function ensureSupervisorDir(paths) {
467
+ fs2.mkdirSync(paths.dir, { recursive: true, mode: 448 });
468
+ if (process.platform !== "win32") {
469
+ try {
470
+ fs2.chmodSync(paths.dir, 448);
471
+ } catch {
472
+ }
473
+ }
474
+ }
475
+ function resolveCliEntry() {
476
+ const here = fileURLToPath(import.meta.url);
477
+ return path2.join(path2.dirname(here), "cli.js");
478
+ }
479
+ async function runSupervisorEntry() {
480
+ const sup = supervisorPaths();
481
+ ensureSupervisorDir(sup);
482
+ const writer = openDaemonLog(sup.logPath);
483
+ const log = (level, message) => writer.write(level, message);
484
+ let pidLock;
485
+ try {
486
+ pidLock = acquirePidLock(sup.pidPath);
487
+ } catch (err) {
488
+ const message = err instanceof Error ? err.message : String(err);
489
+ log("error", `supervisor startup: ${message}`);
490
+ writer.close();
491
+ process.stderr.write(`drisp supervisor: ${message}
492
+ `);
493
+ return 1;
494
+ }
495
+ const cliEntry = resolveCliEntry();
496
+ const gatewayPaths = resolveGatewayPaths();
497
+ const listenSpec = resolveListenSpec({ paths: gatewayPaths });
498
+ let gateway;
499
+ try {
500
+ gateway = await startDaemon({
501
+ foreground: true,
502
+ paths: gatewayPaths,
503
+ listenSpec,
504
+ skipSignalHandlers: true
505
+ });
506
+ } catch (err) {
507
+ const message = err instanceof Error ? err.message : String(err);
508
+ log("error", `gateway startup failed: ${message}`);
509
+ pidLock.release();
510
+ writer.close();
511
+ process.stderr.write(
512
+ `drisp supervisor: gateway startup failed: ${message}
513
+ `
514
+ );
515
+ return 1;
516
+ }
517
+ log(
518
+ "info",
519
+ `supervisor: embedded gateway listening on ${describeListener(gateway)}`
520
+ );
521
+ const dashboardConfig = readDashboardClientConfig();
522
+ let instanceSocket = null;
523
+ let runnerTransport = null;
524
+ if (dashboardConfig) {
525
+ try {
526
+ const token = await refreshDashboardAccessToken({});
527
+ instanceSocket = createInstanceSocketClient({
528
+ dashboardUrl: dashboardConfig.dashboardUrl,
529
+ instanceId: token.instanceId,
530
+ accessToken: token.accessToken,
531
+ log
532
+ });
533
+ await instanceSocket.connect();
534
+ runnerTransport = runnerTransportFromInstanceSocket({
535
+ client: instanceSocket,
536
+ log
537
+ });
538
+ log(
539
+ "info",
540
+ `supervisor: instance socket connected as ${token.instanceId}`
541
+ );
542
+ } catch (err) {
543
+ log(
544
+ "warn",
545
+ `supervisor: instance socket unavailable (runs will not flow): ${err instanceof Error ? err.message : String(err)}`
546
+ );
547
+ instanceSocket = null;
548
+ runnerTransport = null;
549
+ }
550
+ } else {
551
+ log("info", "supervisor: no dashboard config; skipping runner transport");
552
+ }
553
+ const source = createMirrorAttachmentSource({ log });
554
+ const set = createAttachmentSet({
555
+ createRunner: (input) => {
556
+ const child = createAttachmentRunner({
557
+ attachmentId: input.attachmentId,
558
+ runnerId: input.runnerId,
559
+ spawnChild: (args) => {
560
+ const proc = spawn(process.execPath, [cliEntry, ...args], {
561
+ stdio: "inherit"
562
+ });
563
+ return proc;
564
+ }
565
+ });
566
+ if (!runnerTransport) return child;
567
+ return wrapWithRunnerAdapter({
568
+ child,
569
+ attachmentId: input.attachmentId,
570
+ runnerId: input.runnerId,
571
+ transport: runnerTransport,
572
+ gateway,
573
+ log
574
+ });
575
+ }
576
+ });
577
+ const handle = await runSupervisor({ source, set, log });
578
+ log("info", "supervisor: ready");
579
+ const stopSignal = createDeferred();
580
+ let stopReason = "sigterm";
581
+ const onSignal = (signal) => {
582
+ log("info", `received ${signal}`);
583
+ stopReason = signal;
584
+ stopSignal.resolve(signal);
585
+ };
586
+ process.on("SIGINT", onSignal);
587
+ process.on("SIGTERM", onSignal);
588
+ const reason = await stopSignal.promise;
589
+ process.off("SIGINT", onSignal);
590
+ process.off("SIGTERM", onSignal);
591
+ log("info", `supervisor stopping: ${reason}`);
592
+ try {
593
+ await handle.shutdown();
594
+ } catch (err) {
595
+ log(
596
+ "warn",
597
+ `supervisor shutdown failed: ${err instanceof Error ? err.message : String(err)}`
598
+ );
599
+ }
600
+ source.close();
601
+ if (instanceSocket) {
602
+ try {
603
+ instanceSocket.close("supervisor stopping");
604
+ } catch (err) {
605
+ log(
606
+ "warn",
607
+ `instance socket close failed: ${err instanceof Error ? err.message : String(err)}`
608
+ );
609
+ }
610
+ }
611
+ try {
612
+ await gateway.stop();
613
+ } catch (err) {
614
+ log(
615
+ "warn",
616
+ `gateway stop failed: ${err instanceof Error ? err.message : String(err)}`
617
+ );
618
+ }
619
+ pidLock.release();
620
+ log("info", `supervisor stopped: ${stopReason}`);
621
+ writer.close();
622
+ return 0;
623
+ }
624
+ function wrapWithRunnerAdapter(opts) {
625
+ const adapter = createRunnerAdapter({
626
+ runnerId: opts.runnerId,
627
+ transport: opts.transport
628
+ });
629
+ let registered = false;
630
+ return {
631
+ attachmentId: opts.child.attachmentId,
632
+ runnerId: opts.child.runnerId,
633
+ async start() {
634
+ if (!registered) {
635
+ try {
636
+ await opts.gateway.channelManager.register(adapter, {
637
+ attachmentId: opts.attachmentId
638
+ });
639
+ registered = true;
640
+ } catch (err) {
641
+ opts.log(
642
+ "warn",
643
+ `supervisor: failed to register runner adapter for ${opts.attachmentId}: ${err instanceof Error ? err.message : String(err)}`
644
+ );
645
+ }
646
+ }
647
+ await opts.child.start();
648
+ },
649
+ async stop(reason) {
650
+ await opts.child.stop(reason);
651
+ if (registered) {
652
+ try {
653
+ await opts.gateway.channelManager.unregister(adapter.id, "shutdown");
654
+ } catch (err) {
655
+ opts.log(
656
+ "warn",
657
+ `supervisor: failed to unregister runner adapter for ${opts.attachmentId}: ${err instanceof Error ? err.message : String(err)}`
658
+ );
659
+ }
660
+ registered = false;
661
+ }
662
+ },
663
+ onChildExit: opts.child.onChildExit
664
+ };
665
+ }
666
+ function describeListener(gateway) {
667
+ const l = gateway.listener;
668
+ if (l.kind === "uds") return l.socketPath ?? "<uds>";
669
+ return l.url ?? `${l.host ?? "?"}:${l.port ?? "?"}`;
670
+ }
671
+ function createDeferred() {
672
+ let resolve;
673
+ const promise = new Promise((r) => {
674
+ resolve = r;
675
+ });
676
+ let settled = false;
677
+ return {
678
+ promise,
679
+ resolve(value) {
680
+ if (settled) return;
681
+ settled = true;
682
+ resolve(value);
683
+ }
684
+ };
685
+ }
686
+ void runSupervisorEntry().then((code) => {
687
+ process.exit(code);
688
+ });
689
+ export {
690
+ runSupervisorEntry
691
+ };
692
+ //# sourceMappingURL=supervisor.js.map
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "hooks",
18
18
  "dashboard"
19
19
  ],
20
- "version": "0.4.4",
20
+ "version": "0.4.7",
21
21
  "license": "MIT",
22
22
  "bin": {
23
23
  "athena": "dist/cli.js",