agent-dag 1.33.76 → 1.33.78

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>agents-deck</title>
7
7
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
8
- <script type="module" crossorigin src="/assets/index-BKIhNAkv.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-Ffz1bNVI.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-CF2yG9oG.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-dag",
3
- "version": "1.33.76",
3
+ "version": "1.33.78",
4
4
  "description": "Live deck of Claude Code and Codex agents — watch parallel subagents fork, call tools, and return on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -307,7 +307,7 @@ export function hasCodexInstalled() {
307
307
  // pid keeps its old mode through writeFile, hence the explicit chmod.
308
308
  export async function writeDiscovery({ port, workspace, token }) {
309
309
  await ensureDir(AGENT_DAG_DIR);
310
- const file = join(AGENT_DAG_DIR, `${process.pid}.json`);
310
+ const file = discoveryPath();
311
311
  const data = {
312
312
  pid: process.pid,
313
313
  port,
@@ -326,6 +326,78 @@ export async function removeDiscovery(file) {
326
326
  try { await unlink(file); } catch {}
327
327
  }
328
328
 
329
+ /** Where this process registers itself. One file per deck, named by pid. */
330
+ export function discoveryPath() {
331
+ return join(AGENT_DAG_DIR, `${process.pid}.json`);
332
+ }
333
+
334
+ /**
335
+ * Make sure this deck's discovery file is on disk and says what it should.
336
+ *
337
+ * Registration was a single write at boot, so anything that took the file away
338
+ * afterwards — a sweep on another machine's clock, a half-finished restart, a
339
+ * user tidying the directory — left a deck that was listening, serving and
340
+ * completely invisible: hook.js enumerates this directory and nothing else, so
341
+ * the deck received zero events while looking perfectly healthy. Re-asserting
342
+ * is cheap (one small read), so the deck checks rather than assumes.
343
+ *
344
+ * A file this process wrote is left alone, mode included. Anything else — no
345
+ * file, unreadable, another pid, a stale port or token — is replaced.
346
+ */
347
+ export async function ensureDiscovery({ port, workspace, token }) {
348
+ const file = discoveryPath();
349
+ try {
350
+ const d = JSON.parse(stripBom(await readFile(file, "utf8")));
351
+ if (d
352
+ && d.pid === process.pid
353
+ && d.port === port
354
+ && (d.workspace ?? "") === (workspace ?? "")
355
+ && (d.token ?? "") === (token ?? "")) {
356
+ return { file, rewritten: false };
357
+ }
358
+ } catch { /* missing, unreadable or corrupt — rewritten below */ }
359
+ await writeDiscovery({ port, workspace, token });
360
+ return { file, rewritten: true };
361
+ }
362
+
363
+ /**
364
+ * Keep this deck registered for as long as it runs, and tell the caller when
365
+ * that stops being true.
366
+ *
367
+ * `onState` hears the first outcome, every change of health, and every
368
+ * re-registration after the first — never a steady state. A deck that cannot
369
+ * write the file has to say so: silently listening while no hook can find it
370
+ * is the failure this exists to end, not a state worth hiding.
371
+ *
372
+ * The interval is unref'd, so it never keeps a finished process alive, and
373
+ * `stop()` must be called before the file is removed on shutdown — otherwise
374
+ * the next tick would put it straight back.
375
+ */
376
+ export function keepDiscovery({ port, workspace, token, intervalMs = 5000, onState = null } = {}) {
377
+ // null until the first outcome, which therefore always differs and is always
378
+ // reported — the caller learns where it stands before anything else happens.
379
+ let healthy = null;
380
+
381
+ const check = async () => {
382
+ let state;
383
+ try {
384
+ const { rewritten } = await ensureDiscovery({ port, workspace, token });
385
+ state = { ok: true, rewritten, file: discoveryPath(), error: null };
386
+ } catch (err) {
387
+ state = { ok: false, rewritten: false, file: discoveryPath(), error: err };
388
+ }
389
+ const worthSaying = healthy !== state.ok || state.rewritten;
390
+ healthy = state.ok;
391
+ if (worthSaying && onState) { try { onState(state); } catch { /* not our problem */ } }
392
+ return state;
393
+ };
394
+
395
+ const timer = setInterval(() => { check(); }, intervalMs);
396
+ timer.unref?.();
397
+
398
+ return { file: discoveryPath(), check, stop: () => clearInterval(timer) };
399
+ }
400
+
329
401
  export const HOOK_EVENTS = CLAUDE_EVENTS;
330
402
  export { AGENT_DAG_DIR, CLAUDE_DIR, CODEX_DIR, CLAUDE_EVENTS, CODEX_EVENTS };
331
403
  // Exported for the other modules that rewrite settings.json — the sound toggle