agent-dag 1.33.77 → 1.33.79
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/bin/deck.js
CHANGED
|
@@ -73,11 +73,16 @@ const openBrowser = flags.noOpen !== true;
|
|
|
73
73
|
// config dir rather than assuming ~/.claude — see src/server/claude-dir.mjs.
|
|
74
74
|
const { claudeConfigDir } =
|
|
75
75
|
await import(pathToFileURL(join(PKG_ROOT, "src/server/claude-dir.mjs")).href);
|
|
76
|
+
// Resolved here rather than left as typed: the discovery file publishes this
|
|
77
|
+
// path so the hook can tell which decks share one log and elect a single
|
|
78
|
+
// writer for it, and two spellings of one file would read as two files.
|
|
79
|
+
// startServer resolves it the same way, from this same process, so the two
|
|
80
|
+
// always name the same file.
|
|
76
81
|
const persist = flags.noPersist
|
|
77
82
|
? null
|
|
78
|
-
: (flags.history ?? join(claudeConfigDir(), "agent-dag", "events.jsonl"));
|
|
83
|
+
: resolve(flags.history ?? join(claudeConfigDir(), "agent-dag", "events.jsonl"));
|
|
79
84
|
|
|
80
|
-
const { installHooks,
|
|
85
|
+
const { installHooks, keepDiscovery, removeDiscovery, hasCodexInstalled } =
|
|
81
86
|
await import(pathToFileURL(join(PKG_ROOT, "src/server/installer.mjs")).href);
|
|
82
87
|
const { startServer, hookToken } =
|
|
83
88
|
await import(pathToFileURL(join(PKG_ROOT, "src/server/index.mjs")).href);
|
|
@@ -328,9 +333,35 @@ if (RESPAWN) {
|
|
|
328
333
|
else process.stdout.write("\n");
|
|
329
334
|
}
|
|
330
335
|
|
|
336
|
+
// The discovery file is the whole of how a hook finds this deck: hook.js
|
|
337
|
+
// enumerates that directory and nothing else. Writing it once at boot meant
|
|
338
|
+
// anything that later took it away left a deck that listened, served, and
|
|
339
|
+
// received not one event — with nothing on screen to say so. So it is checked
|
|
340
|
+
// on a timer, put back when it goes missing, and its absence is stated out loud
|
|
341
|
+
// rather than left to look like an idle afternoon.
|
|
342
|
+
//
|
|
331
343
|
// The token goes in with the port: it is what lets a hook tell this deck from
|
|
332
344
|
// whatever else may later be listening on the same number. See hookToken().
|
|
333
|
-
|
|
345
|
+
//
|
|
346
|
+
// The log path goes in with them, so a hook can see which decks share one events
|
|
347
|
+
// log and elect a single writer for it. See electWriters in hook/hook.js.
|
|
348
|
+
let registered = null;
|
|
349
|
+
const discovery = keepDiscovery({
|
|
350
|
+
port: realPort,
|
|
351
|
+
workspace,
|
|
352
|
+
token: hookToken(),
|
|
353
|
+
persist,
|
|
354
|
+
onState: (state) => {
|
|
355
|
+
const first = registered === null;
|
|
356
|
+
registered = state.ok;
|
|
357
|
+
if (!state.ok) reportUnregistered(state);
|
|
358
|
+
else if (!first) reportReregistered(state);
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
const discoveryFile = discovery.file;
|
|
362
|
+
// Now, not in five seconds: nothing should reach the pulse line below without
|
|
363
|
+
// the deck knowing whether the hooks can see it.
|
|
364
|
+
await discovery.check();
|
|
334
365
|
|
|
335
366
|
// Never on a respawn: the tab that asked for the restart is still open and
|
|
336
367
|
// reconnecting on its own. A second one would be the deck talking over itself.
|
|
@@ -344,9 +375,19 @@ if (openBrowser && !RESPAWN) {
|
|
|
344
375
|
// ── Pulse indicator ───────────────────────────────────────────────────────────
|
|
345
376
|
if (tty) {
|
|
346
377
|
const pulseFrames = [`${C.green}●${C.reset}`, `${C.dim}●${C.reset}`];
|
|
378
|
+
const LISTENING = "listening — Ctrl+C to stop";
|
|
379
|
+
// A deck no hook can find is not listening in any sense the user cares
|
|
380
|
+
// about, and the pulse is the one line that stays on screen for hours — so
|
|
381
|
+
// it is where this has to be said.
|
|
382
|
+
const UNREGISTERED = "listening, but not registered — hooks cannot find this deck";
|
|
383
|
+
// Padded on the plain text, before any colour: the line is redrawn over
|
|
384
|
+
// itself with \r, so the shorter message has to cover the longer one.
|
|
385
|
+
const width = UNREGISTERED.length + 3;
|
|
347
386
|
let pi = 0;
|
|
348
387
|
setInterval(() => {
|
|
349
|
-
|
|
388
|
+
const text = (registered ? LISTENING : UNREGISTERED).padEnd(width);
|
|
389
|
+
const colour = registered ? C.dim : C.yellow;
|
|
390
|
+
process.stdout.write(`\r ${pulseFrames[pi++ % 2]} ${colour}${text}${C.reset}`);
|
|
350
391
|
}, 800).unref();
|
|
351
392
|
}
|
|
352
393
|
|
|
@@ -358,6 +399,10 @@ const shutdown = async (code = 0) => {
|
|
|
358
399
|
if (tty && code !== RESTART_CODE && code !== UPGRADE_CODE) {
|
|
359
400
|
process.stdout.write(`\n\n ${C.yellow}◉ shutting down…${C.reset}\n`);
|
|
360
401
|
}
|
|
402
|
+
// Stopped first, always: a tick landing after the unlink would re-register a
|
|
403
|
+
// deck that is on its way out, and leave the file behind for the hooks to
|
|
404
|
+
// find once nothing is listening.
|
|
405
|
+
discovery.stop();
|
|
361
406
|
await removeDiscovery(discoveryFile);
|
|
362
407
|
server.close(() => process.exit(code));
|
|
363
408
|
// SSE connections never end by themselves, so close() alone would sit out the
|
|
@@ -369,10 +414,25 @@ const shutdown = async (code = 0) => {
|
|
|
369
414
|
};
|
|
370
415
|
process.on("SIGINT", () => shutdown(0));
|
|
371
416
|
process.on("SIGTERM", () => shutdown(0));
|
|
372
|
-
process.on("beforeExit", () => removeDiscovery(discoveryFile));
|
|
417
|
+
process.on("beforeExit", () => { discovery.stop(); removeDiscovery(discoveryFile); });
|
|
373
418
|
|
|
374
419
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
375
420
|
|
|
421
|
+
// The deck is up and the hooks cannot see it. Said in full — the path, the
|
|
422
|
+
// reason — because the alternative is what this replaced: an ordinary-looking
|
|
423
|
+
// deck that simply never shows a session.
|
|
424
|
+
function reportUnregistered({ file, error }) {
|
|
425
|
+
const why = error?.message ? ` — ${error.message}` : "";
|
|
426
|
+
process.stdout.write(
|
|
427
|
+
`\n ${C.yellow}⚠${C.reset} ${C.bold}not registered${C.reset}${C.dim}${why}${C.reset}\n` +
|
|
428
|
+
` ${C.dim}hooks find this deck through ${file}, so until that file exists no events arrive.${C.reset}\n`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function reportReregistered({ file }) {
|
|
433
|
+
process.stdout.write(`\n ${C.green}✓${C.reset} ${C.dim}registered again → ${file}${C.reset}\n`);
|
|
434
|
+
}
|
|
435
|
+
|
|
376
436
|
function parseArgs(args) {
|
|
377
437
|
const out = {};
|
|
378
438
|
for (let i = 0; i < args.length; i++) {
|