agent-dag 1.35.19 → 1.35.21

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
@@ -9,8 +9,8 @@ import { fileURLToPath, pathToFileURL } from "node:url";
9
9
  import { existsSync, readFileSync } from "node:fs";
10
10
  import { dieOfSignal } from "../src/server/supervisor.mjs";
11
11
  import {
12
- CURSOR_HIDE, CURSOR_SHOW, colorProfile, fit, glyphs, labelColumn, link, motionOK, palette,
13
- pulseText, spinnerFrames, statusLine, supportsHyperlinks, termColumns, unicodeOK,
12
+ CURSOR_HIDE, CURSOR_SHOW, colorProfile, fit, glyphs, labelColumn, link, motionOK, oneLine,
13
+ palette, pulseText, spinnerFrames, statusLine, supportsHyperlinks, termColumns, unicodeOK,
14
14
  unregisteredDetail, wordmark,
15
15
  } from "../src/server/term.mjs";
16
16
  import { PRODUCT } from "../src/server/brand.mjs";
@@ -101,7 +101,7 @@ const { installHooks, keepDiscovery, removeDiscovery, hasCodexInstalled } =
101
101
  // the watcher tails, and the watcher lives in that module. Recomputing the path
102
102
  // here is how the banner came to print ~/.codex/sessions on machines whose
103
103
  // sessions are somewhere else entirely — see the row further down.
104
- const { startServer, hookToken, releaseRestart, CODEX_SESSIONS_DIR, canonicalWorkspace } =
104
+ const { startServer, hookToken, releaseRestart, markDeckReady, CODEX_SESSIONS_DIR, canonicalWorkspace } =
105
105
  await import(pathToFileURL(join(PKG_ROOT, "src/server/index.mjs")).href);
106
106
 
107
107
  // Resolved here rather than left as typed, for the reason the events log above
@@ -445,32 +445,116 @@ let restarting = false;
445
445
  const UPGRADE_ANSWER_MS = 150_000;
446
446
  let upgradeTimer = null;
447
447
 
448
+ // Whether the rest of this file has finished running.
449
+ //
450
+ // The server below starts accepting connections from inside startServer, before
451
+ // that call has returned — so /api/restart is reachable for the whole of the
452
+ // boot that follows it: the port report to the supervisor, the discovery file
453
+ // and its first fsynced write, and on a cold start the browser spawn. A restart
454
+ // landing in that window used to reach `shutdown` before the binding holding it
455
+ // was initialised and die of a ReferenceError, having already set the latch
456
+ // above, with nothing left to clear it — after which every restart from every
457
+ // tab was answered "ok" and did nothing, for the life of the process (#448).
458
+ //
459
+ // So an ask that arrives too early is held rather than run: the window is
460
+ // bounded and short, the user asked for something this deck can genuinely give
461
+ // a second later, and refusing outright would put back the same silence in a
462
+ // politer form. BOOT_RESTART_MS is the outer bound, for the reason
463
+ // UPGRADE_ANSWER_MS above is one: a boot that has not finished in ten seconds is
464
+ // itself the fault, and the restart is the answer to it rather than a casualty
465
+ // of it.
466
+ let booted = false;
467
+ let heldRestart = null;
468
+ let bootTimer = null;
469
+ const BOOT_RESTART_MS = 10_000;
470
+
448
471
  const requestRestart = (mode) => {
449
472
  if (restarting) return;
450
473
  restarting = true;
451
- // "npx" means the newer code is not on this disk at all, so it has to be
452
- // fetched and this process keeps serving while that happens. Exiting first
453
- // is what made every failed upgrade an outage: the SSE stream dropped, hook
454
- // events fired into the gap were lost outright (hook/hook.js is
455
- // fire-and-forget with a 1s timeout and no retry), and the canvas came back
456
- // with whatever was in flight stuck until the stale sweeper reaped it — all
457
- // of it paid before anyone knew whether npm could even resolve the version.
458
- // Nothing is torn down here now; the supervisor answers when it knows.
459
- if (mode === "npx") {
460
- upgradeTimer = setTimeout(() => abandonUpgrade("no answer from the supervisor"), UPGRADE_ANSWER_MS);
461
- upgradeTimer.unref?.();
462
- // Armed before the ask, not after: a send that throws is a supervisor that
463
- // can no longer answer, and the deck has to come back out of the latch on
464
- // its own rather than wait out an answer that cannot arrive.
465
- try { process.send({ type: "upgrade" }); }
466
- catch (err) { abandonUpgrade(err?.message ?? "the supervisor is no longer listening"); }
474
+ if (!booted) {
475
+ heldRestart = { mode };
476
+ bootTimer = setTimeout(() => { bootTimer = null; runHeldRestart(); }, BOOT_RESTART_MS);
477
+ bootTimer.unref?.();
478
+ // Said out loud for the same reason abandonUpgrade below is: the tab has
479
+ // already been told its restart was accepted, and a second of nothing
480
+ // happening on this terminal is otherwise indistinguishable from the bug
481
+ // this replaces.
482
+ write(`\n ${P.warn}${G.restart}${P.reset} ${P.muted}restart queued ${G.dash} still starting up${P.reset}\n`);
467
483
  return;
468
484
  }
469
- const to = restartTarget();
470
- write(`\n ${P.warn}${G.restart}${P.reset} ${P.muted}restarting${to ? ` ${G.arrow} v${to}` : ""}${G.ellipsis}${P.reset}\n`);
471
- shutdown(RESTART_CODE);
485
+ beginRestart(mode);
472
486
  };
473
487
 
488
+ // The restart itself, once there is a booted deck to end. Split out of
489
+ // requestRestart so the held ask above can re-enter it without tripping the
490
+ // latch it is already holding.
491
+ //
492
+ // Everything here runs inside one try: the whole point of #448 is that a throw
493
+ // on this path is not merely a failed restart but a permanent one, because the
494
+ // latch it leaves behind outlives it. There is no line in here worth dying for.
495
+ function beginRestart(mode) {
496
+ try {
497
+ // "npx" means the newer code is not on this disk at all, so it has to be
498
+ // fetched — and this process keeps serving while that happens. Exiting first
499
+ // is what made every failed upgrade an outage: the SSE stream dropped, hook
500
+ // events fired into the gap were lost outright (hook/hook.js is
501
+ // fire-and-forget with a 1s timeout and no retry), and the canvas came back
502
+ // with whatever was in flight stuck until the stale sweeper reaped it — all
503
+ // of it paid before anyone knew whether npm could even resolve the version.
504
+ // Nothing is torn down here now; the supervisor answers when it knows.
505
+ if (mode === "npx") {
506
+ upgradeTimer = setTimeout(() => abandonUpgrade("no answer from the supervisor"), UPGRADE_ANSWER_MS);
507
+ upgradeTimer.unref?.();
508
+ // Armed before the ask, not after: a send that throws is a supervisor that
509
+ // can no longer answer, and the deck has to come back out of the latch on
510
+ // its own rather than wait out an answer that cannot arrive.
511
+ try { process.send({ type: "upgrade" }); }
512
+ catch (err) { abandonUpgrade(err?.message ?? "the supervisor is no longer listening"); }
513
+ return;
514
+ }
515
+ const to = restartTarget();
516
+ write(`\n ${P.warn}${G.restart}${P.reset} ${P.muted}restarting${to ? ` ${G.arrow} v${to}` : ""}${G.ellipsis}${P.reset}\n`);
517
+ shutdown(RESTART_CODE);
518
+ } catch (err) {
519
+ abandonRestart(err);
520
+ }
521
+ }
522
+
523
+ // The ask that was waiting for the boot to finish, now that it has. Safe to
524
+ // call when nothing is waiting, which is every ordinary boot.
525
+ function runHeldRestart() {
526
+ if (!heldRestart) return;
527
+ const { mode } = heldRestart;
528
+ heldRestart = null;
529
+ clearTimeout(bootTimer);
530
+ bootTimer = null;
531
+ beginRestart(mode);
532
+ }
533
+
534
+ // A restart that could not be started, said out loud and then let go of.
535
+ //
536
+ // Both halves of the latch have to come down — this file's and the server's —
537
+ // because a latch nothing clears is precisely how one failed request turned
538
+ // into a deck that refused every restart afterwards while answering "ok" to
539
+ // each one (#448). The reason is folded onto one line by oneLine: the terminal
540
+ // under this is repainted every 800ms by the pulse, and a stack written into
541
+ // that is a stack nobody can read (#432).
542
+ //
543
+ // A declaration rather than a const, like `shutdown` below and for the same
544
+ // reason: this is the handler for a binding that was not there yet, and it must
545
+ // not be capable of becoming the next one.
546
+ function abandonRestart(err) {
547
+ clearTimeout(bootTimer);
548
+ bootTimer = null;
549
+ heldRestart = null;
550
+ restarting = false;
551
+ releaseRestart();
552
+ write(
553
+ `\n ${P.err}${G.fail}${P.reset} ${P.muted}restart failed ${G.dash} still on ${P.reset}v${PKG_VERSION}\n` +
554
+ ` ${P.muted}${oneLine(err?.stack ?? err, Math.max(20, cols() - 6), G.ellipsis)}${P.reset}\n`,
555
+ );
556
+ }
557
+
474
558
  // The upgrade did not happen and this deck is still the deck. Said out loud
475
559
  // because the terminal has just printed that a fetch was starting, and left
476
560
  // unsaid it reads as a restart that hung.
@@ -508,13 +592,24 @@ function restartTarget() {
508
592
  catch { return null; }
509
593
  }
510
594
 
595
+ // The three things `shutdown` has to tear down, named before the boot that
596
+ // fills them in rather than by it. From the line below onwards this process is
597
+ // answering HTTP, and /api/restart can therefore reach `shutdown` at any moment
598
+ // after it — including moments at which none of these exist yet. `let … = null`
599
+ // is what makes that a question shutdown can ask instead of a ReferenceError it
600
+ // dies of; the boot queue in requestRestart is what makes it a question it
601
+ // almost never has to ask. See #448.
602
+ let server = null;
603
+ let discovery = null;
604
+ let discoveryFile = null;
605
+
511
606
  const starting = startServer({
512
607
  port, persist, workspace, codex: wantCodex, claude: wantClaude,
513
608
  // Withheld when nothing is supervising us: without a parent, exiting is just
514
609
  // exiting, and /api/restart answers 501 so the UI hides the control.
515
610
  onRestart: SUPERVISED ? requestRestart : null,
516
611
  });
517
- const server = await (RESPAWN ? starting : step(`starting server${G.ellipsis}`, starting)).catch(err => {
612
+ server = await (RESPAWN ? starting : step(`starting server${G.ellipsis}`, starting)).catch(err => {
518
613
  // stderr, not a row: a deck that could not bind is not a status line, and
519
614
  // whatever launched it reads this stream.
520
615
  console.error(`${PRODUCT}: server failed: ${err.message}`);
@@ -564,7 +659,7 @@ if (RESPAWN) {
564
659
  // rollout files this deck tails itself, which a --no-codex deck must never be
565
660
  // elected to record. See writesCodexLog in src/server/log-writer.mjs.
566
661
  let registered = null;
567
- const discovery = keepDiscovery({
662
+ discovery = keepDiscovery({
568
663
  port: realPort,
569
664
  workspace,
570
665
  token: hookToken(),
@@ -577,7 +672,7 @@ const discovery = keepDiscovery({
577
672
  else if (!first) reportReregistered(state);
578
673
  },
579
674
  });
580
- const discoveryFile = discovery.file;
675
+ discoveryFile = discovery.file;
581
676
  // Now, not in five seconds: nothing should reach the pulse line below without
582
677
  // the deck knowing whether the hooks can see it.
583
678
  await discovery.check();
@@ -612,33 +707,78 @@ if (MOTION) {
612
707
  }, 800).unref();
613
708
  }
614
709
 
615
- const shutdown = async (code = 0) => {
710
+ // Boot is over. Everything `shutdown` tears down exists, so a restart can be
711
+ // run rather than held — and the server is told, so /api/restart stops
712
+ // describing a deck that is still assembling itself. This line is exactly where
713
+ // the window opened at the top of this file closes; see requestRestart.
714
+ booted = true;
715
+ markDeckReady();
716
+ runHeldRestart();
717
+
718
+ /**
719
+ * A declaration, not the `const` arrow this was for eight months.
720
+ *
721
+ * The difference is the whole of #448: a const is in its temporal dead zone
722
+ * until the line declaring it runs, and every line above — the port report, the
723
+ * discovery file, the browser spawn — executes with the server already
724
+ * accepting connections. A restart arriving in that window called this and got
725
+ * `ReferenceError: Cannot access 'shutdown' before initialization`, and the
726
+ * latch it had already set is what made that permanent. A declaration is
727
+ * hoisted, so from the first instruction of this module there is a function
728
+ * here to call.
729
+ *
730
+ * Hoisting alone would only have moved the fault one line down, onto `server`,
731
+ * `discovery` and `discoveryFile` — which is why those are `let … = null` above
732
+ * and asked about rather than assumed here. Between them, this is callable at
733
+ * any instant of this process's life and cannot end in a throw for the caller
734
+ * to lose.
735
+ */
736
+ async function shutdown(code = 0) {
616
737
  // Also set as exitCode, not only passed to exit(): if the event loop empties
617
738
  // on its own before either timer runs, Node would otherwise exit 0 and the
618
739
  // supervisor would take that as "done" instead of "bring me back".
619
740
  process.exitCode = code;
620
- // Before anything that can take time: a Ctrl+C the user has to watch for a
621
- // second and a half is a second and a half without a cursor.
622
- showCursor();
623
- if (tty && code !== RESTART_CODE && code !== UPGRADE_CODE) {
624
- write(`\n\n ${P.warn}${G.stop} shutting down${G.ellipsis}${P.reset}\n`);
741
+ // Nothing inside a shutdown is worth staying alive for, and this one is
742
+ // called from three places that cannot handle a rejection a signal handler,
743
+ // an IPC message handler, and a restart. An unhandled one there ends the
744
+ // process on Node's terms rather than ours, which is to say with the wrong
745
+ // exit code and therefore, half the time, without the supervisor bringing the
746
+ // deck back.
747
+ try {
748
+ // Before anything that can take time: a Ctrl+C the user has to watch for a
749
+ // second and a half is a second and a half without a cursor.
750
+ showCursor();
751
+ if (tty && code !== RESTART_CODE && code !== UPGRADE_CODE) {
752
+ write(`\n\n ${P.warn}${G.stop} shutting down${G.ellipsis}${P.reset}\n`);
753
+ }
754
+ // Stopped first, always: a tick landing after the unlink would re-register a
755
+ // deck that is on its way out, and leave the file behind for the hooks to
756
+ // find once nothing is listening.
757
+ //
758
+ // Guarded on its own, because a discovery file this process cannot remove is
759
+ // a nuisance the next boot's stale sweep clears up — worth carrying on to
760
+ // the orderly close below rather than skipping to the abrupt one.
761
+ try {
762
+ discovery?.stop();
763
+ if (discoveryFile) await removeDiscovery(discoveryFile);
764
+ } catch { /* the sweep at the next boot gets it */ }
765
+ // No server yet means nothing to drain and nothing to hand the port over to,
766
+ // so the exit is the whole of the shutdown.
767
+ if (!server) return process.exit(code);
768
+ server.close(() => process.exit(code));
769
+ // SSE connections never end by themselves, so close() alone would sit out the
770
+ // full 1500ms fallback on every restart. Hanging them up is safe — the stream
771
+ // sets retry: 1500 and replays from Last-Event-ID, so each tab reconnects and
772
+ // catches up without being told anything.
773
+ try { server.closeAllConnections?.(); } catch { /* Node < 18.2 */ }
774
+ setTimeout(() => process.exit(code), 1500).unref();
775
+ } catch {
776
+ process.exit(code);
625
777
  }
626
- // Stopped first, always: a tick landing after the unlink would re-register a
627
- // deck that is on its way out, and leave the file behind for the hooks to
628
- // find once nothing is listening.
629
- discovery.stop();
630
- await removeDiscovery(discoveryFile);
631
- server.close(() => process.exit(code));
632
- // SSE connections never end by themselves, so close() alone would sit out the
633
- // full 1500ms fallback on every restart. Hanging them up is safe — the stream
634
- // sets retry: 1500 and replays from Last-Event-ID, so each tab reconnects and
635
- // catches up without being told anything.
636
- try { server.closeAllConnections?.(); } catch { /* Node < 18.2 */ }
637
- setTimeout(() => process.exit(code), 1500).unref();
638
- };
778
+ }
639
779
  process.on("SIGINT", () => shutdown(0));
640
780
  process.on("SIGTERM", () => shutdown(0));
641
- process.on("beforeExit", () => { discovery.stop(); removeDiscovery(discoveryFile); });
781
+ process.on("beforeExit", () => { discovery?.stop(); if (discoveryFile) removeDiscovery(discoveryFile); });
642
782
 
643
783
  // ── helpers ───────────────────────────────────────────────────────────────────
644
784
 
@@ -69,7 +69,7 @@ Claude Code turns only. The sound is a Stop hook Claude Code runs itself when a
69
69
 
70
70
  ${n.clash} sound hook${n.clash>1?"s":""} of your own in settings.json also run${n.clash>1?"":"s"} here.`:"",l=n.parked>0?`
71
71
 
72
- ${n.parked} of your own sound hook${n.parked>1?"s were":" was"} set aside so this switch actually controls the sound. Nothing was deleted — shift-click to put ${n.parked>1?"them":"it"} back.`:"";return r+i+o+l}function LT(e){const n=Math.max(0,Math.floor(e));return`${n} event${n===1?"":"s"}`}function b5(e){return e.connected?e.paused?{tone:"paused",label:"paused",title:e.held>0?`Connected — ${LT(e.held)} held until you resume (Space)`:"Connected — updates held until you resume (Space)"}:{tone:"live",label:"live",title:"Receiving events"}:{tone:"dead",label:"offline",title:e.paused?"SSE disconnected — and the canvas is paused, so resuming will not bring events back until it reconnects":"SSE disconnected"}}function S5(e){return e.paused?e.held<=0?{label:"Resume",title:"Nothing has arrived since you paused. Resume to follow the canvas again (Space)"}:{label:`Resume · ${e.held} held`,title:`${LT(e.held)} arrived while paused and will be applied in order when you resume (Space)`}:{label:"Pause",title:"Pause live updates — events keep arriving and are applied when you resume (Space)"}}function $T(e,n){return e!==""&&n>0}function k5(e,n,r){return e===""?{active:!1,matched:n,total:r,count:null,empty:!1,message:null,dim:!1}:{active:!0,matched:n,total:r,count:`${n} of ${r}`,empty:n===0,message:n===0?`No agents match “${e}”`:null,dim:$T(e,n)}}const E5="Search agents, tools, model…";function qT(e,n){var i,o,l,u;if(!n)return!0;const r=n.toLowerCase();if(e.label.toLowerCase().includes(r)||(i=e.cwd)!=null&&i.toLowerCase().includes(r)||(o=e.cwdBasename)!=null&&o.toLowerCase().includes(r)||e.sessionId.toLowerCase().includes(r)||(l=e.firstPrompt)!=null&&l.toLowerCase().includes(r)||e.model&&(e.model.toLowerCase().includes(r)||js(e.model).toLowerCase().includes(r))||(u=e.provider)!=null&&u.toLowerCase().startsWith(r))return!0;for(const c of e.tools)if(c.name.toLowerCase().includes(r))return!0;return!1}function Pv(e){const n=Math.max(0,Math.floor(e/1e3));return n<60?"just now":n<3600?`${Math.floor(n/60)}m ago`:n<86400?`${Math.floor(n/3600)}h ago`:`${Math.floor(n/86400)}d ago`}function N5(e,n){return e.getFullYear()===n.getFullYear()&&e.getMonth()===n.getMonth()&&e.getDate()===n.getDate()}function C5(e,n,r){const i=new Date(e),o=Pv(n-e);return{label:N5(i,new Date(n))?o:`${o} · ${i.toLocaleDateString(r,{month:"short",day:"numeric"})}`,title:i.toLocaleString(r,{dateStyle:"medium",timeStyle:"medium"})}}function ua(e){return typeof window>"u"?"":getComputedStyle(document.documentElement).getPropertyValue(e).trim()||""}const T5={agent:QP,sessionGroup:dL},DT="react-flow__node";function OE(e){var n,r;return!!e&&((r=(n=e.classList)==null?void 0:n.contains)==null?void 0:r.call(n,DT))===!0}function R5(e){try{const n=document.querySelector(`.${DT}[data-id="${CSS.escape(e)}"]`);n==null||n.focus({preventScroll:!0})}catch{}}const j5=["a[href]","button:not(:disabled)","input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)","summary","[tabindex]"].join(","),A5=420,au=18,I5=200,M5=5*6e4,O5=6,P5=2*6e4,ky="agent-dag.layout",Ey="agent-dag.viewport",FT="agent-dag.summariesDismissed",zT="agent-dag.sessionListOpen",BT="agent-dag.detailOpen",UT="agent-dag.usagePanelOpen",PE="agent-dag.accountsPanelOpen",LE="agent-dag.versionNoticeDismissed",$E="agent-dag.oldNameNoticeDismissed",L5=15*6e4,qE={git_checkout:"this deck runs from a git checkout — pull instead:",npx:"npx runs from a cache that cannot be upgraded in place — run:",not_writable:"the install directory is not writable by this user — run:",opted_out:"installs are off (AGENTS_DECK_NO_INSTALL=1) — run:"},DE="agent-dag.autoRestart",FE="agent-dag.bundleReloadedFor";function $5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(zT)==="1"}catch{return!1}}function q5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(zT,e?"1":"0")}catch{}}function D5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(BT)==="1"}catch{return!1}}function F5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(BT,e?"1":"0")}catch{}}function z5(){const e=MC(UT);return e===null?!0:e==="1"}function B5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(UT,e?"1":"0")}catch{}}function U5(){if(typeof window>"u")return new Set;try{const e=window.localStorage.getItem(FT);if(!e)return new Set;const n=JSON.parse(e);return new Set(Array.isArray(n)?n.filter(r=>typeof r=="string"):[])}catch{return new Set}}function zE(e){if(!(typeof window>"u"))try{const n=Array.from(e),r=n.length>200?n.slice(-200):n;window.localStorage.setItem(FT,JSON.stringify(r))}catch{}}function H5(){const e={positions:[],pins:[]};if(typeof window>"u")return e;try{const n=window.localStorage.getItem(ky);if(!n)return e;const r=JSON.parse(n);if(!("v"in r)){const o=Object.entries(r).filter(([,l])=>l&&typeof l.x=="number"&&typeof l.y=="number");return{positions:o,pins:o.map(([l])=>l)}}return{positions:Object.entries(r.positions??{}).filter(([,o])=>o&&typeof o.x=="number"&&typeof o.y=="number"),pins:Array.isArray(r.pins)?r.pins:[]}}catch{return e}}function dv(e,n){if(!(typeof window>"u"))try{const r={};for(const[i,o]of e)r[i]=o;for(const[i,o]of n)r[i]=o;window.localStorage.setItem(ky,JSON.stringify({v:2,positions:r,pins:Array.from(n.keys())}))}catch{}}function W5(){if(typeof window>"u")return null;try{const e=window.localStorage.getItem(Ey);if(!e)return null;const n=JSON.parse(e);return typeof(n==null?void 0:n.x)!="number"||typeof(n==null?void 0:n.y)!="number"||typeof(n==null?void 0:n.zoom)!="number"?null:n}catch{return null}}function V5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(Ey,JSON.stringify(e))}catch{}}function BE(){if(!(typeof window>"u")){try{window.localStorage.removeItem(ky)}catch{}try{window.localStorage.removeItem(Ey)}catch{}}}function G5(e,n){const r=e.agents.get(n);if(!r)return;const i=[];for(const d of e.agents.values())d.sessionId===n&&i.push(d);const o={schemaVersion:1,exportedAt:new Date().toISOString(),sessionId:n,label:r.label,cwd:r.cwd,startedAt:r.startedAt,endedAt:r.endedAt,model:r.model,agents:i.map(d=>({id:d.id,kind:d.kind,label:d.label,parentId:d.parentId,state:d.state,startedAt:d.startedAt,endedAt:d.endedAt,model:d.model,cwd:d.cwd,usage:d.usage,prompts:d.prompts,tools:d.tools.map(m=>({id:m.id,name:m.name,inputPreview:m.inputPreview,startedAt:m.startedAt,endedAt:m.endedAt,ok:m.ok,errorPreview:m.errorPreview,usage:m.usage}))}))},l=JSON.stringify(o,null,2),u=new Blob([l],{type:"application/json"}),c=URL.createObjectURL(u),h=document.createElement("a"),p=(r.label||"session").replace(/[^a-z0-9._-]/gi,"_");h.href=c,h.download=`${en}-${p}-${n.slice(0,8)}.json`,document.body.appendChild(h),h.click(),document.body.removeChild(h),setTimeout(()=>URL.revokeObjectURL(c),1e3)}const Lv={file:"📁",shell:"⚡",web:"🌐",agent:"🤖",task:"📋",plan:"🧭",mcp:"🔌",other:"✨"},$v={file:"file",shell:"shell",web:"web",agent:"agent",task:"task",plan:"plan",mcp:"mcp",other:"other"},HT=kC;function Y5(e,n){if(!n)return null;const r=new Set([n]);let i=n;for(;i;){const l=e.agents.get(i);if(!(l!=null&&l.parentId)||r.has(l.parentId))break;r.add(l.parentId),i=l.parentId}let o=!0;for(;o;){o=!1;for(const l of e.agents.values())l.parentId&&r.has(l.parentId)&&!r.has(l.id)&&(r.add(l.id),o=!0)}return r}function K5(e,n,r,i,o,l,u,c,h,p,d,m,y,w,N,_,b,S,k){const E=[],T=[],I=new Set;if(l)for(const B of e.agents.values())S.has(B.id)&&qT(B,l)&&I.add(B.id);const O=$T(l,I.size);for(const B of e.agents.values()){if(!S.has(B.id))continue;const C=O&&!I.has(B.id),q=B.exitAt!=null,L=b!=null&&!b.has(B.id),re=[C?"rf-dim":"",q?"rf-exiting":"",L?"rf-spotlit-out":""].filter(Boolean).join(" ")||void 0,J=u.get(B.id);if(E.push({id:B.id,type:"agent",position:{x:0,y:0},data:{...B,now:n,onOpenContext:k},className:re,...J?{width:J.width,height:J.height}:null}),B.parentId&&S.has(B.parentId)){const W=ny(B.sessionId),Z=l&&!I.has(B.id)&&!I.has(B.parentId),M=q,G=_.size>0&&(_.has(B.id)||_.has(B.parentId)),K=b!=null&&!b.has(B.id)&&!b.has(B.parentId),$=B.state==="active"?2:1.5,Q=G?$+1.5:$,ne=Z||M?.2:K?.12:1,ae=["sess-edge",B.state==="active"?"sess-live":"sess-idle",M?"rf-edge-exiting":"",G?"rf-edge-selected":""].filter(Boolean).join(" ");T.push({id:`e:${B.parentId}->${B.id}`,source:B.parentId,target:B.id,animated:(B.state==="active"||G)&&!Z&&!M,type:"smoothstep",style:{"--session-hue":W,strokeWidth:Q,opacity:ne,transition:"var(--edge-transition)"},className:ae})}}const A=new Map;for(const B of e.agents.values())B.tools.length>0&&A.set(B.id,Math.min(4,B.tools.length));const U=`${w}#lanes:${J4(A)}`,z=E.filter(B=>c$(B.id,o,m,y));if(z.length>0||U!==N.current){if(z.length>0){const B=t5(E,T,{direction:"LR",pinned:o,measured:u,availableWidth:r,availableHeight:i,lanes:A});for(const C of B)IC(C.id,m,y)&&f$(C.id,C.position,m,y);r5(E,m,o,u,new Set(z.map(C=>C.id)),A)}n5(E,m,o,u,A),N.current=U}const P=s5(E,m,o,u,c,!p||d,A);P.length>0&&h(P),ru(m,e.agents),ru(y,e.agents),ru(o,e.agents),ru(u,u$(e.agents.values()));const D=[];let V=!1;for(const B of E){let C=o.get(B.id)??m.get(B.id);C||(C=d$(B.id,m,y),V=!0),D.push({...B,position:C})}return V&&(N.current=""),{nodes:D,edges:T}}function X5(){return g.jsx(ty,{children:g.jsx(Q5,{})})}function Q5(){var Va,Ga,Ya,Ka;const e=ja(),n=j.useRef(Tv()),[,r]=j.useState(0),i=j.useCallback(()=>r(H=>H+1),[]),[o,l]=j.useState(()=>new Set),[u,c]=j.useState(null),[h,p]=j.useState(null),d=j.useCallback((H,Y)=>{l(oe=>{if(!Y)return new Set([H]);const de=new Set(oe);return de.has(H)?de.delete(H):de.add(H),de}),c(oe=>Y&&oe===H?oe:H)},[]),m=j.useCallback(()=>{l(new Set),c(null)},[]),[y,w]=j.useState(null),[N,_]=j.useState(null),b=j.useCallback(H=>_(H),[]),[S,k]=j.useState(!1),E=j.useRef(U5()),[T,I]=j.useState($5);j.useEffect(()=>{q5(T)},[T]);const[O,A]=j.useState(D5);j.useEffect(()=>{F5(O)},[O]);const[U,z]=j.useState(z5);j.useEffect(()=>{B5(U)},[U]);const[P,D]=j.useState(()=>{try{const H=window.localStorage.getItem(PE);return H===null?!0:H==="1"}catch{return!0}});j.useEffect(()=>{try{window.localStorage.setItem(PE,P?"1":"0")}catch{}},[P]);const[V,B]=j.useState(null),[C,q]=j.useState(!1),[L,re]=j.useState(0),[J,W]=j.useState(0);j.useEffect(()=>{fetch("/api/sound-hook").then(H=>H.ok?H.json():null).then(H=>{H!=null&&H.ok&&(B(H.enabled===!0),re((H.foreign??[]).filter(Y=>Y.worksHere).length),W(typeof H.parked=="number"?H.parked:0))}).catch(()=>{})},[]);const Z=j.useCallback(async()=>{q(!0);try{const Y=await(await fetch("/api/sound-hook",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:!V})})).json().catch(()=>null);Y!=null&&Y.ok&&B(Y.enabled===!0),fetch("/api/sound-hook").then(oe=>oe.ok?oe.json():null).then(oe=>{oe!=null&&oe.ok&&(re((oe.foreign??[]).filter(de=>de.worksHere).length),W(typeof oe.parked=="number"?oe.parked:0))}).catch(()=>{})}catch{}finally{q(!1)}},[V]),[M,G]=j.useState(!1),[K,$]=j.useState(null),[Q,ne]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem(LE)??""}catch{return""}}),[ae,fe]=j.useState(!1),te=j.useRef(0),[ce,Ce]=j.useState(!1),ve=j.useCallback((H=!1)=>{H&&(te.current=Date.now(),Ce(!0)),fetch(H?"/api/version?refresh=1":"/api/version").then(Y=>Y.ok?Y.json():null).then(Y=>{Y&&$(Y)}).catch(()=>{}).finally(()=>{H&&Ce(!1)})},[]),je=j.useCallback(()=>{ve(Date.now()-te.current>=L5)},[ve]);j.useEffect(()=>{ve();const H=window.setInterval(je,5*6e4),Y=()=>{document.visibilityState==="visible"&&je()};return document.addEventListener("visibilitychange",Y),()=>{window.clearInterval(H),document.removeEventListener("visibilitychange",Y)}},[ve,je]),j.useEffect(()=>{M&&ve()},[M,ve]);const ye=(K==null?void 0:K.notice)??null,[Ee,Ye]=j.useState(null),[we,_e]=j.useState(Ov);j.useEffect(()=>{let H=!1;return fetch("/api/health").then(Y=>Y.ok?Y.json():null).then(Y=>{H||(_e(m5(Y)),!(!Y||typeof Y.workspace!="string")&&Ye(Y.workspace))}).catch(()=>{}),()=>{H=!0}},[M]);const Be=j.useRef(we);Be.current=we;const it=ye?`${ye.kind}:${ye.to}`:"",ft=ye!=null&&Q!==it,Ze=j.useCallback(()=>{if(!ye)return;const H=Q===it?"":it;ne(H);try{window.localStorage.setItem(LE,H)}catch{}},[ye,it,Q]),[De,Je]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem($E)??""}catch{return""}}),Xe=K!=null&&K.invokedAs&&K.invokedAs!==en?K.invokedAs:null,Mt=Xe!=null&&De!==Xe,lt=j.useCallback(()=>{if(Xe){Je(Xe);try{window.localStorage.setItem($E,Xe)}catch{}}},[Xe]),Qe=((Va=K==null?void 0:K.upgrade)==null?void 0:Va.state)??"idle",Ot=Z3(K==null?void 0:K.upgrade),zt=j.useCallback(async()=>{try{await fetch("/api/upgrade",{method:"POST"})}catch{}ve()},[ve]);j.useEffect(()=>{if(Qe!=="running")return;const H=window.setInterval(ve,3e3);return()=>window.clearInterval(H)},[Qe,ve]);const et=j.useCallback(async()=>{var oe;const H=K==null?void 0:K.command;if(!H)return;let Y=!1;try{Y=await Promise.race([((oe=navigator.clipboard)==null?void 0:oe.writeText(H).then(()=>!0))??Promise.resolve(!1),new Promise(de=>window.setTimeout(()=>de(!1),500))])}catch{Y=!1}if(!Y)try{const de=document.createElement("textarea");de.value=H,de.setAttribute("readonly",""),de.style.cssText="position:fixed;top:0;left:0;opacity:0",document.body.appendChild(de),de.select(),Y=document.execCommand("copy"),de.remove()}catch{Y=!1}Y&&(fe(!0),window.setTimeout(()=>fe(!1),1600))},[K==null?void 0:K.command]),bt=j.useCallback(()=>{I(H=>(H||D(!1),!H))},[]),ht=j.useCallback(()=>{D(H=>(H||I(!1),!H))},[]),[vt,hn]=j.useState(!1),[Gt,Pt]=j.useState(0),bn=j.useRef(null),[gr,Sn]=j.useState(!1),Bt=j.useRef(null),[Fn,pn]=j.useState(0),vr=j.useRef(g$()),[Hr,xi]=j.useState(!1),Wr=j.useCallback(()=>{const H=vr.current,Y=H.setPaused(!H.paused);for(const oe of Y)n.current=Fx(n.current,oe);xi(H.paused)},[]),[ut,_i]=j.useState(Date.now()),[mn,Is]=j.useState(()=>{if(typeof window>"u")return!0;try{return window.localStorage.getItem(DE)!=="0"}catch{return!0}}),yr=j.useCallback(()=>{Is(H=>{const Y=!H;try{window.localStorage.setItem(DE,Y?"1":"0")}catch{}return Y})},[]),[zn,kn]=j.useState(!1),[Vr,Gr]=j.useState("restart"),[Yr,bi]=j.useState(null),sr=j.useRef(!1),Ms=j.useRef(null),En=j.useRef(0),wr=j.useCallback(async H=>{if(sr.current)return;const Y=(H==null?void 0:H.upgrade)===!0;sr.current=!0,Ms.current=Ot;const oe=++En.current;Gr(Y?"npx":"restart"),kn(!0);try{window.sessionStorage.setItem("agent-dag.restartPending",(ye==null?void 0:ye.to)??"")}catch{}try{await fetch("/api/restart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({upgrade:Y})})}catch{}window.setTimeout(()=>{if(En.current===oe&&sr.current){sr.current=!1,kn(!1);try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}}},Y?18e4:3e4)},[ye==null?void 0:ye.to,Ot]),Os=j.useRef(null);j.useEffect(()=>{let H=!1;for(const oe of n.current.agents.values())if(oe.state==="active"){H=!0;break}const Y=e$({enabled:mn,kind:ye==null?void 0:ye.kind,canRestart:(K==null?void 0:K.canRestart)===!0,busy:H,idleSince:Os.current,now:ut});Os.current=Y.idleSince,Y.restart&&wr()},[mn,ye==null?void 0:ye.kind,K==null?void 0:K.canRestart,ut,wr]),j.useEffect(()=>{const H=K==null?void 0:K.running;let Y=null,oe=null;try{Y=window.sessionStorage.getItem("agent-dag.restartPending"),oe=window.sessionStorage.getItem(FE)}catch{return}const de=Q3({bundle:"1.35.19",running:H,pending:Y,lastTried:oe});if(de==="reload"){try{window.sessionStorage.setItem(FE,H??"")}catch{return}window.location.reload();return}if(de!=="confirm")return;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1),bi(H??null);const xe=window.setTimeout(()=>bi(null),6e3);return()=>window.clearTimeout(xe)},[K==null?void 0:K.running]),j.useEffect(()=>{if(zn&&J3({asked:Ms.current,reported:Ot})){try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1)}},[zn,Ot]);const Ps=j.useRef(H5()).current;j.useRef(!1);const Tt=j.useRef(new Map(Ps.positions.filter(([H])=>Ps.pins.includes(H)))),xr=j.useRef(null),_r=j.useState(()=>W5())[0],[Yt,Kr]=j.useState(""),[Si,nt]=j.useState(()=>new Set),[br,Xr]=j.useState(_$),[Ls,Qr]=j.useState(!1),Zr=j.useRef(null);j.useEffect(()=>{document.documentElement.dataset.theme=br;try{window.localStorage.setItem(OC,br)}catch{}},[br]),j.useEffect(()=>{if(!_r)return;const H=window.setTimeout(()=>{try{e.setViewport(_r,{duration:0})}catch{}},60);return()=>window.clearTimeout(H)},[e,_r]);const $s=j.useRef(!0);j.useEffect(()=>{const H=new EventSource("/events"),Y=m$(i,{now:()=>Date.now(),setTimeout:(oe,de)=>window.setTimeout(oe,de),clearTimeout:oe=>window.clearTimeout(oe)});return H.addEventListener("open",()=>{G(!0),Qr(!0)}),H.addEventListener("error",()=>G(!1)),H.addEventListener("replay-end",()=>{$s.current=!1,Y.flush()}),H.addEventListener("hook",oe=>{try{const de=JSON.parse(oe.data);if(!vr.current.accept(de))return;n.current=Fx(n.current,de),de.replay===!0||$s.current||Date.now()-de.receivedAt>3e4?Y.replay():Y.live()}catch{}}),()=>{H.close(),Y.cancel()}},[i]),j.useEffect(()=>{const H=setInterval(()=>{const Y=Date.now();_i(Y);let oe=mP(n.current,Y,Dx);gP(n.current,Y,Dx)&&(oe=!0),hP(n.current,Y,I5,M5)&&(oe=!0),pP(n.current,Y,O5,P5)&&(oe=!0),oe&&i()},250);return()=>clearInterval(H)},[i]);const ir=j.useRef(null),St=j.useRef(0),qs=420,pt=j.useCallback((H=500)=>{try{const Se=document.querySelector(".canvas-wrap"),Pe=Array.from(document.querySelectorAll(".react-flow__node")).filter(wt=>wt.offsetWidth>0);if(!Se||Pe.length===0)return;const Fe=Se.getBoundingClientRect(),Ge=e.getViewport();if(!Number.isFinite(Ge.zoom)||Ge.zoom<=0)return;const We=wt=>(wt-Fe.left-Ge.x)/Ge.zoom,ze=wt=>(wt-Fe.top-Ge.y)/Ge.zoom,Tn=Pe.map(wt=>wt.getBoundingClientRect()),Ys=Math.min(...Tn.map(wt=>We(wt.left))),Ao=Math.max(...Tn.map(wt=>We(wt.right))),Ks=Math.min(...Tn.map(wt=>ze(wt.top))),Io=Math.max(...Tn.map(wt=>ze(wt.bottom))),Xa=Ao-Ys,us=Io-Ks;if(!(Xa>0&&us>0))return;const Qt=Math.max(.2,Math.min(1,(Fe.width-160)/(Xa+qs)*.86,(Fe.height-160)/us*.86));e.setViewport({x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},{duration:H}),St.current=Date.now(),window.setTimeout(()=>{try{const wt={x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},Mo=e.getViewport();(Math.abs(Mo.zoom-Qt)>.01||Math.abs(Mo.x-wt.x)>2)&&e.setViewport(wt,{duration:0})}catch{}},H+60)}catch{}},[e]),ko=j.useRef(""),Eo=j.useRef(null),qa=j.useRef(0),Sr=j.useCallback(()=>{qa.current=Date.now()},[]),Jr="agent-dag.autoFitDisabled",gn=j.useRef((()=>{if(typeof window>"u")return!1;try{return window.localStorage.getItem(Jr)==="1"}catch{return!1}})()),[Ds,Da]=j.useState(gn.current),Fa=j.useCallback(()=>{if(!gn.current){gn.current=!0,Da(!0);try{window.localStorage.setItem(Jr,"1")}catch{}}},[]),No=j.useCallback(()=>{gn.current=!1,Da(!1);try{window.localStorage.removeItem(Jr)}catch{}pt(400)},[e,pt]);j.useEffect(()=>{const H=setInterval(()=>{if(gn.current||Date.now()-qa.current<800)return;const Y=n.current,oe=Date.now(),de=[];for(const We of Y.agents.values())Mv(We,oe)&&de.push({id:We.id});if(de.length===0)return;const xe=e.getViewport(),Se=window.innerWidth-360,Pe=window.innerHeight-52;let Fe=!1,Ge=!1;for(const{id:We}of de){const ze=or.current.get(We),Tn=Tt.current.get(We)??Kt.current.get(We);if(!ze||!Tn)continue;Ge=!0;const Ys=Tn.x*xe.zoom+xe.x,Ao=Tn.y*xe.zoom+xe.y,Ks=(Tn.x+ze.width)*xe.zoom+xe.x,Io=(Tn.y+ze.height)*xe.zoom+xe.y;if(Ks>0&&Ys<Se&&Io>0&&Ao<Pe){Fe=!0;break}}Ge&&!Fe&&pt(600)},1500);return()=>clearInterval(H)},[e]);const ki=j.useRef(!1),or=j.useRef(new Map),es=j.useRef(0),ec=j.useCallback(H=>{const Y=or.current;let oe=!1;for(const de of H.nodeInternals.values()){const xe=de.width,Se=de.height;if(xe==null||Se==null)continue;const Pe=Y.get(de.id);Pe?(Math.abs(Pe.height-Se)>4||Math.abs(Pe.width-xe)>4)&&(Y.set(de.id,{width:xe,height:Se}),oe=!0):(Y.set(de.id,{width:xe,height:Se}),oe=!0)}return oe&&(es.current+=1),es.current},[]),Ei=Ke(ec),[He,za]=j.useState(0);j.useEffect(()=>{let H=0;const Y=()=>{const de=or.current;let xe=!1;for(const Se of document.querySelectorAll(".react-flow__node[data-id]")){const Pe=Se.getAttribute("data-id");if(!Pe)continue;const Fe=Se.offsetWidth,Ge=Se.offsetHeight;if(!Fe||!Ge)continue;const We=de.get(Pe);(!We||Math.abs(We.width-Fe)>4||Math.abs(We.height-Ge)>4)&&(de.set(Pe,{width:Fe,height:Ge}),xe=!0)}xe&&za(Se=>Se+1)};if(ki.current)return;let oe=0;return document.visibilityState==="hidden"?oe=window.setTimeout(Y,32):H=requestAnimationFrame(Y),()=>{cancelAnimationFrame(H),window.clearTimeout(oe)}});const Kt=j.useRef(new Map(Ps.positions)),Co=j.useRef(new Set),Ni=j.useRef(""),kr=j.useMemo(()=>{const H=[];for(const Y of n.current.agents.values())Mv(Y,ut)&&H.push(Y.id+(Y.parentId?`>${Y.parentId}`:""));return H.sort(),`${H.join("|")}#sv${Ei}.${He}`},[n.current,n.current.lastSeq,ut,Ei,He]),ts=j.useRef(null);j.useEffect(()=>(ts.current!=null&&window.clearTimeout(ts.current),ts.current=window.setTimeout(()=>{dv(Kt.current,Tt.current)},1500),()=>{ts.current!=null&&window.clearTimeout(ts.current)}),[kr]),j.useEffect(()=>{if(ko.current===kr)return;const H=ko.current;if(ko.current=kr,!H||gn.current)return;Date.now()-St.current>1200&&pt(400),ir.current&&window.clearTimeout(ir.current),ir.current=window.setTimeout(()=>{gn.current||pt(500)},280)},[kr,e,pt]);const Fs=j.useMemo(()=>{if(o.size===0)return null;const H=new Set;for(const Y of o){const oe=Y5(n.current,Y);if(oe)for(const de of oe)H.add(de)}return H.size>0?H:null},[n.current,n.current.lastSeq,o]),Nn=j.useMemo(()=>o5(n.current,ut),[n.current,n.current.lastSeq,ut]),Bn=j.useRef(null),[vn,Un]=j.useState({w:0,h:0});j.useEffect(()=>{const H=Bn.current;if(!H||typeof ResizeObserver>"u")return;const Y=new ResizeObserver(oe=>{var xe;const de=(xe=oe[0])==null?void 0:xe.contentRect;de&&Un(Se=>Math.abs(Se.w-de.width)>40||Math.abs(Se.h-de.height)>40?{w:de.width,h:de.height}:Se)});return Y.observe(H),Un({w:H.clientWidth,h:H.clientHeight}),()=>Y.disconnect()},[]);const zs=j.useCallback(H=>{var oe,de;const Y=H.target;!(Y!=null&&Y.closest)||Y.closest(j5)!==H.currentTarget||(H.preventDefault(),(de=(oe=document.activeElement)==null?void 0:oe.blur)==null||de.call(oe))},[]),Bs=j.useRef(new Map),[Hn,tc]=j.useState(!1);j.useEffect(()=>{const H=window.setTimeout(()=>tc(!0),2500);return()=>window.clearTimeout(H)},[]);const[Ba,Er]=j.useState(!1),ar=j.useRef(null),[Ci,Us]=j.useState(!1),Ua=j.useCallback(()=>{ar.current&&(window.clearTimeout(ar.current),ar.current=null),Er(!1)},[]),nc=j.useCallback(H=>{H.length!==0&&queueMicrotask(()=>{Er(!0),ar.current&&window.clearTimeout(ar.current),ar.current=window.setTimeout(()=>Er(!1),A5+80)})},[]);j.useEffect(()=>()=>{ar.current&&window.clearTimeout(ar.current)},[]);const ns=vn.w>0?vn.w*.92:0,rs=vn.h>0?vn.h:0,{nodes:Xt,edges:Ti}=j.useMemo(()=>K5(n.current,ut,ns,rs,Tt.current,Yt,or.current,Bs.current,nc,Hn,Ci,Kt.current,Co.current,kr,Ni,o,Fs,Nn,b),[n.current,n.current.lastSeq,ut,ns,rs,Hn,Ci,Yt,kr,o,Fs,Nn,b,Gt]),Ha=j.useMemo(()=>{const H=new Map;for(const oe of Xt){const de=oe.data;if(!(de!=null&&de.sessionId)||de.exitAt!=null)continue;const xe=oe.width,Se=oe.height;if(xe==null||Se==null)continue;const Pe=oe.position.x,Fe=oe.position.y,Ge=Pe+xe,We=Fe+Se,ze=H.get(de.sessionId);ze?(ze.minX=Math.min(ze.minX,Pe),ze.minY=Math.min(ze.minY,Fe),ze.maxX=Math.max(ze.maxX,Ge),ze.maxY=Math.max(ze.maxY,We)):H.set(de.sessionId,{minX:Pe,minY:Fe,maxX:Ge,maxY:We})}const Y=[];for(const[oe,de]of H){const xe=de.maxX-de.minX+au*2,Se=de.maxY-de.minY+au*2;Y.push({id:`group:${oe}`,type:PT,position:{x:de.minX-au,y:de.minY-au},data:{sessionId:oe,w:xe,h:Se},width:xe,height:Se,style:{width:xe,height:Se},zIndex:-1,draggable:!0,selectable:!1,focusable:!1,deletable:!1,connectable:!1})}return Y},[Xt,ut]),rc=j.useMemo(()=>{const H=[...Ha,...Xt],Y=Bt.current;return!Y||Y.size===0?H:H.map(oe=>{const de=Y.get(oe.id);return de?{...oe,position:de}:oe})},[Ha,Xt,Fn]),Nr=j.useMemo(()=>{if(!Yt)return null;const H=new Set;for(const Y of n.current.agents.values())Nn.has(Y.id)&&qT(Y,Yt)&&H.add(Y.id);return H},[n.current,n.current.lastSeq,Yt,Nn]),Cn=j.useMemo(()=>k5(Yt,(Nr==null?void 0:Nr.size)??0,Nn.size),[Yt,Nr,Nn]),ss=j.useMemo(()=>{const H=new Set;for(const Y of n.current.agents.values())for(const oe of Y.tools)H.add(HT(oe.name));return Object.keys(Lv).filter(Y=>H.has(Y))},[n.current,n.current.lastSeq]);j.useEffect(()=>{if(ss.length<=1){Sn(!1);return}let H=0;const Y=()=>{const oe=bn.current;if(oe&&!document.hidden){const de=oe.getBoundingClientRect(),xe=Array.from(document.querySelectorAll(".react-flow__node, .tool-burst")).map(Pe=>Pe.getBoundingClientRect()),Se=l5(de,xe,8);Sn(Pe=>Pe===Se?Pe:Se)}H=window.setTimeout(Y,300)};return Y(),()=>window.clearTimeout(H)},[ss.length]);const is=j.useMemo(()=>{const H=new Map;for(const oe of n.current.agents.values())for(const de of oe.tools){if(!de.name.startsWith("mcp__"))continue;const xe=de.name.slice(5),Se=xe.indexOf("__"),Pe=Se>0?xe.slice(0,Se):xe;H.set(Pe,(H.get(Pe)??0)+1)}const Y=[];for(const[oe,de]of H){let xe=5381;for(let Se=0;Se<oe.length;Se++)xe=(xe<<5)+xe^oe.charCodeAt(Se);Y.push({server:oe,count:de,hue:Math.abs(xe)%360})}return Y.sort((oe,de)=>de.count-oe.count),Y},[n.current,n.current.lastSeq]),lr=j.useCallback(H=>{nt(Y=>{const oe=new Set(Y);return oe.has(H)?oe.delete(H):oe.add(H),oe})},[]),Rt=u?n.current.agents.get(u):null,os=h?Array.from(n.current.agents.values()).flatMap(H=>H.tools).find(H=>H.id===h)??null:null,To=j.useCallback(async()=>{try{await fetch("/api/clear",{method:"POST"})}catch{}n.current=Tv(),Tt.current.clear(),or.current.clear(),Kt.current.clear(),Ni.current="",BE(),m(),i()},[i,m]),Hs=j.useRef(S);Hs.current=S;const Ws=j.useRef(!1);Ws.current=os!=null||vt||N!=null||y!=null;const Vs=j.useCallback(H=>{const Y=n$(H,{confirmOpen:Hs.current,modalOpen:Ws.current});Y==="confirm"?k(!0):Y==="clear"&&(k(!1),To())},[To]),Lt=j.useCallback(()=>{Tt.current.clear(),Kt.current.clear(),Ni.current="",BE(),i(),window.setTimeout(()=>pt(500),80)},[i,e,pt]),Cr=j.useCallback(()=>pt(500),[pt]),as=j.useRef(Xt);as.current=Xt;const ls=j.useRef(u);ls.current=u;const Ro=j.useCallback(H=>{const Y=as.current,oe=s$(Y.map(Se=>({id:Se.id,x:Se.position.x,y:Se.position.y})),ls.current,H),de=oe?Y.find(Se=>Se.id===oe):void 0;if(!de)return;const xe=OE(document.activeElement);d(de.id,!1),window.setTimeout(()=>{try{e.fitView({padding:.35,duration:350,nodes:[de]})}catch{}St.current=Date.now(),xe&&R5(de.id)},30)},[d,e]),Ri=j.useCallback(H=>{d(H,!1),window.setTimeout(()=>{try{const Y=as.current.find(oe=>oe.id===H);Y&&e.fitView({padding:.3,duration:500,nodes:[Y]}),St.current=Date.now()}catch{}},60)},[d,e]);j.useEffect(()=>{const H=Y=>{var Fe,Ge,We;const oe=Y.target,de={tagName:oe==null?void 0:oe.tagName,isContentEditable:oe==null?void 0:oe.isContentEditable,role:(Fe=oe==null?void 0:oe.getAttribute)==null?void 0:Fe.call(oe,"role"),type:oe==null?void 0:oe.type};if(Y.key==="Escape"){const ze=PP({overlayOpen:Eu.depth()>0,typing:ry(de)});ze==="dismiss"?Eu.dismissTop():ze==="blur"?oe==null||oe.blur():(l$(de)&&(oe==null||oe.blur()),m());return}if(_C(Y))return;const xe=OE(oe)?(Ge=oe==null?void 0:oe.getAttribute)==null?void 0:Ge.call(oe,"data-id"):null,Se=xe&&as.current.some(ze=>ze.id===xe)?xe:null,Pe=o$(Y,Se);if(Pe.kind==="activate"){Y.preventDefault(),d(Pe.nodeId,Pe.additive);return}if(Pe.kind!=="node"&&!(Pe.nodeId==null&&UP(de))){if(Y.key==="/"){Y.preventDefault(),(We=Zr.current)==null||We.focus();return}Y.key===" "&&(Y.preventDefault(),Wr()),(Y.key==="c"||Y.key==="C")&&Vs("shortcut"),(Y.key==="r"||Y.key==="R")&&Lt(),(Y.key==="f"||Y.key==="F")&&Cr(),(Y.key==="l"||Y.key==="L")&&bt(),(Y.key==="h"||Y.key==="H")&&hn(ze=>!ze),(Y.key==="u"||Y.key==="U")&&z(ze=>!ze),(Y.key==="a"||Y.key==="A")&&Be.current.claude&&ht(),(Y.key==="j"||Y.key==="J")&&Ro(1),(Y.key==="k"||Y.key==="K")&&Ro(-1),(Y.key==="t"||Y.key==="T")&&Xr(ze=>ze==="dark"?"light":"dark")}};return window.addEventListener("keydown",H),()=>window.removeEventListener("keydown",H)},[Vs,Lt,Cr,m,d,Ro,Wr]);const Tr=n.current.agents.size,Gs=new Set(Array.from(n.current.agents.values()).map(H=>H.sessionId)).size,tn=j.useMemo(()=>JL(n.current.agents.values()),[n.current,n.current.lastSeq]),jo=j.useMemo(()=>e3(n.current.agents.values()),[n.current,n.current.lastSeq]),Wa=j.useRef(null);j.useEffect(()=>{const H=b$({waiting:tn.length,running:jo}),Y=Wa.current;if(Wa.current=H,(Y==null?void 0:Y.title)!==H.title&&(document.title=H.title),(Y==null?void 0:Y.icon)!==H.icon){const oe=document.querySelector('link[rel="icon"]');oe&&(oe.href=S$[H.icon])}},[tn.length,jo]);const[sc,ic]=j.useState(""),ji=E$(tn);j.useEffect(()=>{ic(H=>N$(H,ji))},[ji]);const nn=j.useMemo(()=>{let H=0,Y=0,oe=0,de=0,xe=0,Se=0,Pe=0,Fe=0,Ge=0;for(const We of n.current.agents.values()){H+=We.usage.inputTokens,Y+=We.usage.outputTokens,oe+=We.usage.cacheReadTokens,de+=We.usage.cacheCreateTokens;const ze=dn(We.usage,We.model);xe+=ze.total,Se+=ze.input,Pe+=ze.output,Fe+=ze.cacheRead,Ge+=ze.cacheWrite}return{inT:H,outT:Y,cacheR:oe,cacheC:de,sum:H+Y,cost:{total:xe,input:Se,output:Pe,cacheRead:Fe,cacheWrite:Ge}}},[n.current,n.current.lastSeq]);return g.jsxs("div",{className:"app",children:[g.jsx("a",{className:"skip-link",href:"#canvas",children:"Skip to the canvas"}),g.jsxs("header",{className:"topbar",children:[g.jsxs("div",{className:"brand",children:[g.jsx("span",{className:"logo"}),g.jsx("h1",{children:en}),ye?g.jsxs("button",{type:"button",className:"v stale",onClick:Ze,"aria-label":h5({...ye,open:ft}),title:ye.kind==="restart"?`Running v${ye.from}; v${ye.to} is installed on disk. Restart to pick it up.`:`Running v${ye.from}; v${ye.to} is on npm.`,children:["v",ye.from,g.jsx("span",{className:"v-dot","aria-hidden":!0})]}):(()=>{const H={running:(K==null?void 0:K.running)??"1.35.19",latest:K==null?void 0:K.latest,latestPending:K==null?void 0:K.latestPending,checkedAgo:K!=null&&K.checkedAt?Pv(ut-K.checkedAt):null,checkDisabled:K==null?void 0:K.checkDisabled,checking:ce};return g.jsxs("button",{type:"button",className:ce?"v checking":"v",onClick:()=>ve(!0),"aria-busy":ce||void 0,"aria-label":f5(H),title:d5(H),children:["v",H.running]})})()]}),Rt&&(()=>{const H=dn(Rt.usage,Rt.model),Y=Math.max(0,((Rt.endedAt??ut)-Rt.startedAt)/1e3),oe=Rt.state==="active"?ku(H.total,Y):null,de=o.size-1;return g.jsxs("button",{type:"button",className:"selected-ribbon",title:`Fit view to ${Rt.label}`,onClick:()=>{try{const xe=Xt.find(Se=>Se.id===Rt.id);xe&&e.fitView({padding:.35,duration:500,nodes:[xe]}),St.current=Date.now()}catch{}},children:[g.jsx("span",{className:`state-pill state-${Rt.state}`,children:Rt.state==="active"?"live":Rt.state}),g.jsx("span",{className:"selected-label",children:Rt.label}),H.total>0&&g.jsxs("span",{className:"selected-cost",children:[qe(H.total),oe?g.jsxs("span",{className:"selected-rate",children:[" · ",oe]}):null]}),de>0&&g.jsxs("span",{className:"selected-extra",children:["+",de]}),g.jsx("span",{"aria-hidden":!0,className:"selected-close",onClick:xe=>{xe.stopPropagation(),m()},children:"×"})]})})(),g.jsxs("div",{className:"actions",children:[g.jsxs("div",{className:"search",children:[g.jsx("span",{className:"search-icon","aria-hidden":!0,children:"⌕"}),g.jsx("input",{ref:Zr,type:"text",placeholder:E5,value:Yt,onChange:H=>Kr(H.target.value),spellCheck:!1,"aria-label":"Filter the graph"}),Yt?g.jsx("button",{className:"search-clear","aria-label":"Clear search",onClick:()=>Kr(""),children:"×"}):g.jsx("kbd",{className:"search-kbd","aria-hidden":!0,children:"/"}),Cn.count&&g.jsx("span",{className:`search-count${Cn.empty?" none":""}`,"aria-live":"polite",title:`${Cn.count} agents on the canvas match “${Yt}”`,children:Cn.count})]}),g.jsxs("span",{className:"status",children:[(()=>{const H=b5({connected:M,paused:Hr,held:vr.current.size});return g.jsx("span",{className:`pill ${H.tone}`,title:H.title,children:H.label})})(),g.jsxs("span",{className:"stat",title:y5(we),children:[g.jsx("span",{className:"count",children:Gs}),g.jsx("span",{className:"lbl",children:"sessions"})]}),g.jsxs("span",{className:"stat",title:"Total agents (root + subagents)",children:[g.jsx("span",{className:"count",children:Tr}),g.jsx("span",{className:"lbl",children:"agents"})]}),g.jsxs("span",{className:"stat",title:w5(we),children:[g.jsx("span",{className:"count",children:n.current.totalEvents}),g.jsx("span",{className:"lbl",children:"events"})]}),nn.sum>0&&g.jsxs("span",{className:"stat",title:`in:${nn.inT.toLocaleString()} out:${nn.outT.toLocaleString()} cache-r:${nn.cacheR.toLocaleString()} cache-c:${nn.cacheC.toLocaleString()}`,children:[g.jsx("span",{className:"count",children:Dt(nn.sum)}),g.jsx("span",{className:"lbl",children:"tokens"})]}),is.length>0&&g.jsxs("span",{className:"stat mcp-legend",title:`MCP servers seen this session:
72
+ ${n.parked} of your own sound hook${n.parked>1?"s were":" was"} set aside so this switch actually controls the sound. Nothing was deleted — shift-click to put ${n.parked>1?"them":"it"} back.`:"";return r+i+o+l}function LT(e){const n=Math.max(0,Math.floor(e));return`${n} event${n===1?"":"s"}`}function b5(e){return e.connected?e.paused?{tone:"paused",label:"paused",title:e.held>0?`Connected — ${LT(e.held)} held until you resume (Space)`:"Connected — updates held until you resume (Space)"}:{tone:"live",label:"live",title:"Receiving events"}:{tone:"dead",label:"offline",title:e.paused?"SSE disconnected — and the canvas is paused, so resuming will not bring events back until it reconnects":"SSE disconnected"}}function S5(e){return e.paused?e.held<=0?{label:"Resume",title:"Nothing has arrived since you paused. Resume to follow the canvas again (Space)"}:{label:`Resume · ${e.held} held`,title:`${LT(e.held)} arrived while paused and will be applied in order when you resume (Space)`}:{label:"Pause",title:"Pause live updates — events keep arriving and are applied when you resume (Space)"}}function $T(e,n){return e!==""&&n>0}function k5(e,n,r){return e===""?{active:!1,matched:n,total:r,count:null,empty:!1,message:null,dim:!1}:{active:!0,matched:n,total:r,count:`${n} of ${r}`,empty:n===0,message:n===0?`No agents match “${e}”`:null,dim:$T(e,n)}}const E5="Search agents, tools, model…";function qT(e,n){var i,o,l,u;if(!n)return!0;const r=n.toLowerCase();if(e.label.toLowerCase().includes(r)||(i=e.cwd)!=null&&i.toLowerCase().includes(r)||(o=e.cwdBasename)!=null&&o.toLowerCase().includes(r)||e.sessionId.toLowerCase().includes(r)||(l=e.firstPrompt)!=null&&l.toLowerCase().includes(r)||e.model&&(e.model.toLowerCase().includes(r)||js(e.model).toLowerCase().includes(r))||(u=e.provider)!=null&&u.toLowerCase().startsWith(r))return!0;for(const c of e.tools)if(c.name.toLowerCase().includes(r))return!0;return!1}function Pv(e){const n=Math.max(0,Math.floor(e/1e3));return n<60?"just now":n<3600?`${Math.floor(n/60)}m ago`:n<86400?`${Math.floor(n/3600)}h ago`:`${Math.floor(n/86400)}d ago`}function N5(e,n){return e.getFullYear()===n.getFullYear()&&e.getMonth()===n.getMonth()&&e.getDate()===n.getDate()}function C5(e,n,r){const i=new Date(e),o=Pv(n-e);return{label:N5(i,new Date(n))?o:`${o} · ${i.toLocaleDateString(r,{month:"short",day:"numeric"})}`,title:i.toLocaleString(r,{dateStyle:"medium",timeStyle:"medium"})}}function ua(e){return typeof window>"u"?"":getComputedStyle(document.documentElement).getPropertyValue(e).trim()||""}const T5={agent:QP,sessionGroup:dL},DT="react-flow__node";function OE(e){var n,r;return!!e&&((r=(n=e.classList)==null?void 0:n.contains)==null?void 0:r.call(n,DT))===!0}function R5(e){try{const n=document.querySelector(`.${DT}[data-id="${CSS.escape(e)}"]`);n==null||n.focus({preventScroll:!0})}catch{}}const j5=["a[href]","button:not(:disabled)","input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)","summary","[tabindex]"].join(","),A5=420,au=18,I5=200,M5=5*6e4,O5=6,P5=2*6e4,ky="agent-dag.layout",Ey="agent-dag.viewport",FT="agent-dag.summariesDismissed",zT="agent-dag.sessionListOpen",BT="agent-dag.detailOpen",UT="agent-dag.usagePanelOpen",PE="agent-dag.accountsPanelOpen",LE="agent-dag.versionNoticeDismissed",$E="agent-dag.oldNameNoticeDismissed",L5=15*6e4,qE={git_checkout:"this deck runs from a git checkout — pull instead:",npx:"npx runs from a cache that cannot be upgraded in place — run:",not_writable:"the install directory is not writable by this user — run:",opted_out:"installs are off (AGENTS_DECK_NO_INSTALL=1) — run:"},DE="agent-dag.autoRestart",FE="agent-dag.bundleReloadedFor";function $5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(zT)==="1"}catch{return!1}}function q5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(zT,e?"1":"0")}catch{}}function D5(){if(typeof window>"u")return!1;try{return window.localStorage.getItem(BT)==="1"}catch{return!1}}function F5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(BT,e?"1":"0")}catch{}}function z5(){const e=MC(UT);return e===null?!0:e==="1"}function B5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(UT,e?"1":"0")}catch{}}function U5(){if(typeof window>"u")return new Set;try{const e=window.localStorage.getItem(FT);if(!e)return new Set;const n=JSON.parse(e);return new Set(Array.isArray(n)?n.filter(r=>typeof r=="string"):[])}catch{return new Set}}function zE(e){if(!(typeof window>"u"))try{const n=Array.from(e),r=n.length>200?n.slice(-200):n;window.localStorage.setItem(FT,JSON.stringify(r))}catch{}}function H5(){const e={positions:[],pins:[]};if(typeof window>"u")return e;try{const n=window.localStorage.getItem(ky);if(!n)return e;const r=JSON.parse(n);if(!("v"in r)){const o=Object.entries(r).filter(([,l])=>l&&typeof l.x=="number"&&typeof l.y=="number");return{positions:o,pins:o.map(([l])=>l)}}return{positions:Object.entries(r.positions??{}).filter(([,o])=>o&&typeof o.x=="number"&&typeof o.y=="number"),pins:Array.isArray(r.pins)?r.pins:[]}}catch{return e}}function dv(e,n){if(!(typeof window>"u"))try{const r={};for(const[i,o]of e)r[i]=o;for(const[i,o]of n)r[i]=o;window.localStorage.setItem(ky,JSON.stringify({v:2,positions:r,pins:Array.from(n.keys())}))}catch{}}function W5(){if(typeof window>"u")return null;try{const e=window.localStorage.getItem(Ey);if(!e)return null;const n=JSON.parse(e);return typeof(n==null?void 0:n.x)!="number"||typeof(n==null?void 0:n.y)!="number"||typeof(n==null?void 0:n.zoom)!="number"?null:n}catch{return null}}function V5(e){if(!(typeof window>"u"))try{window.localStorage.setItem(Ey,JSON.stringify(e))}catch{}}function BE(){if(!(typeof window>"u")){try{window.localStorage.removeItem(ky)}catch{}try{window.localStorage.removeItem(Ey)}catch{}}}function G5(e,n){const r=e.agents.get(n);if(!r)return;const i=[];for(const d of e.agents.values())d.sessionId===n&&i.push(d);const o={schemaVersion:1,exportedAt:new Date().toISOString(),sessionId:n,label:r.label,cwd:r.cwd,startedAt:r.startedAt,endedAt:r.endedAt,model:r.model,agents:i.map(d=>({id:d.id,kind:d.kind,label:d.label,parentId:d.parentId,state:d.state,startedAt:d.startedAt,endedAt:d.endedAt,model:d.model,cwd:d.cwd,usage:d.usage,prompts:d.prompts,tools:d.tools.map(m=>({id:m.id,name:m.name,inputPreview:m.inputPreview,startedAt:m.startedAt,endedAt:m.endedAt,ok:m.ok,errorPreview:m.errorPreview,usage:m.usage}))}))},l=JSON.stringify(o,null,2),u=new Blob([l],{type:"application/json"}),c=URL.createObjectURL(u),h=document.createElement("a"),p=(r.label||"session").replace(/[^a-z0-9._-]/gi,"_");h.href=c,h.download=`${en}-${p}-${n.slice(0,8)}.json`,document.body.appendChild(h),h.click(),document.body.removeChild(h),setTimeout(()=>URL.revokeObjectURL(c),1e3)}const Lv={file:"📁",shell:"⚡",web:"🌐",agent:"🤖",task:"📋",plan:"🧭",mcp:"🔌",other:"✨"},$v={file:"file",shell:"shell",web:"web",agent:"agent",task:"task",plan:"plan",mcp:"mcp",other:"other"},HT=kC;function Y5(e,n){if(!n)return null;const r=new Set([n]);let i=n;for(;i;){const l=e.agents.get(i);if(!(l!=null&&l.parentId)||r.has(l.parentId))break;r.add(l.parentId),i=l.parentId}let o=!0;for(;o;){o=!1;for(const l of e.agents.values())l.parentId&&r.has(l.parentId)&&!r.has(l.id)&&(r.add(l.id),o=!0)}return r}function K5(e,n,r,i,o,l,u,c,h,p,d,m,y,w,N,_,b,S,k){const E=[],T=[],I=new Set;if(l)for(const B of e.agents.values())S.has(B.id)&&qT(B,l)&&I.add(B.id);const O=$T(l,I.size);for(const B of e.agents.values()){if(!S.has(B.id))continue;const C=O&&!I.has(B.id),q=B.exitAt!=null,L=b!=null&&!b.has(B.id),re=[C?"rf-dim":"",q?"rf-exiting":"",L?"rf-spotlit-out":""].filter(Boolean).join(" ")||void 0,J=u.get(B.id);if(E.push({id:B.id,type:"agent",position:{x:0,y:0},data:{...B,now:n,onOpenContext:k},className:re,...J?{width:J.width,height:J.height}:null}),B.parentId&&S.has(B.parentId)){const W=ny(B.sessionId),Z=l&&!I.has(B.id)&&!I.has(B.parentId),M=q,G=_.size>0&&(_.has(B.id)||_.has(B.parentId)),K=b!=null&&!b.has(B.id)&&!b.has(B.parentId),$=B.state==="active"?2:1.5,Q=G?$+1.5:$,ne=Z||M?.2:K?.12:1,ae=["sess-edge",B.state==="active"?"sess-live":"sess-idle",M?"rf-edge-exiting":"",G?"rf-edge-selected":""].filter(Boolean).join(" ");T.push({id:`e:${B.parentId}->${B.id}`,source:B.parentId,target:B.id,animated:(B.state==="active"||G)&&!Z&&!M,type:"smoothstep",style:{"--session-hue":W,strokeWidth:Q,opacity:ne,transition:"var(--edge-transition)"},className:ae})}}const A=new Map;for(const B of e.agents.values())B.tools.length>0&&A.set(B.id,Math.min(4,B.tools.length));const U=`${w}#lanes:${J4(A)}`,z=E.filter(B=>c$(B.id,o,m,y));if(z.length>0||U!==N.current){if(z.length>0){const B=t5(E,T,{direction:"LR",pinned:o,measured:u,availableWidth:r,availableHeight:i,lanes:A});for(const C of B)IC(C.id,m,y)&&f$(C.id,C.position,m,y);r5(E,m,o,u,new Set(z.map(C=>C.id)),A)}n5(E,m,o,u,A),N.current=U}const P=s5(E,m,o,u,c,!p||d,A);P.length>0&&h(P),ru(m,e.agents),ru(y,e.agents),ru(o,e.agents),ru(u,u$(e.agents.values()));const D=[];let V=!1;for(const B of E){let C=o.get(B.id)??m.get(B.id);C||(C=d$(B.id,m,y),V=!0),D.push({...B,position:C})}return V&&(N.current=""),{nodes:D,edges:T}}function X5(){return g.jsx(ty,{children:g.jsx(Q5,{})})}function Q5(){var Va,Ga,Ya,Ka;const e=ja(),n=j.useRef(Tv()),[,r]=j.useState(0),i=j.useCallback(()=>r(H=>H+1),[]),[o,l]=j.useState(()=>new Set),[u,c]=j.useState(null),[h,p]=j.useState(null),d=j.useCallback((H,Y)=>{l(oe=>{if(!Y)return new Set([H]);const de=new Set(oe);return de.has(H)?de.delete(H):de.add(H),de}),c(oe=>Y&&oe===H?oe:H)},[]),m=j.useCallback(()=>{l(new Set),c(null)},[]),[y,w]=j.useState(null),[N,_]=j.useState(null),b=j.useCallback(H=>_(H),[]),[S,k]=j.useState(!1),E=j.useRef(U5()),[T,I]=j.useState($5);j.useEffect(()=>{q5(T)},[T]);const[O,A]=j.useState(D5);j.useEffect(()=>{F5(O)},[O]);const[U,z]=j.useState(z5);j.useEffect(()=>{B5(U)},[U]);const[P,D]=j.useState(()=>{try{const H=window.localStorage.getItem(PE);return H===null?!0:H==="1"}catch{return!0}});j.useEffect(()=>{try{window.localStorage.setItem(PE,P?"1":"0")}catch{}},[P]);const[V,B]=j.useState(null),[C,q]=j.useState(!1),[L,re]=j.useState(0),[J,W]=j.useState(0);j.useEffect(()=>{fetch("/api/sound-hook").then(H=>H.ok?H.json():null).then(H=>{H!=null&&H.ok&&(B(H.enabled===!0),re((H.foreign??[]).filter(Y=>Y.worksHere).length),W(typeof H.parked=="number"?H.parked:0))}).catch(()=>{})},[]);const Z=j.useCallback(async()=>{q(!0);try{const Y=await(await fetch("/api/sound-hook",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:!V})})).json().catch(()=>null);Y!=null&&Y.ok&&B(Y.enabled===!0),fetch("/api/sound-hook").then(oe=>oe.ok?oe.json():null).then(oe=>{oe!=null&&oe.ok&&(re((oe.foreign??[]).filter(de=>de.worksHere).length),W(typeof oe.parked=="number"?oe.parked:0))}).catch(()=>{})}catch{}finally{q(!1)}},[V]),[M,G]=j.useState(!1),[K,$]=j.useState(null),[Q,ne]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem(LE)??""}catch{return""}}),[ae,fe]=j.useState(!1),te=j.useRef(0),[ce,Ce]=j.useState(!1),ve=j.useCallback((H=!1)=>{H&&(te.current=Date.now(),Ce(!0)),fetch(H?"/api/version?refresh=1":"/api/version").then(Y=>Y.ok?Y.json():null).then(Y=>{Y&&$(Y)}).catch(()=>{}).finally(()=>{H&&Ce(!1)})},[]),je=j.useCallback(()=>{ve(Date.now()-te.current>=L5)},[ve]);j.useEffect(()=>{ve();const H=window.setInterval(je,5*6e4),Y=()=>{document.visibilityState==="visible"&&je()};return document.addEventListener("visibilitychange",Y),()=>{window.clearInterval(H),document.removeEventListener("visibilitychange",Y)}},[ve,je]),j.useEffect(()=>{M&&ve()},[M,ve]);const ye=(K==null?void 0:K.notice)??null,[Ee,Ye]=j.useState(null),[we,_e]=j.useState(Ov);j.useEffect(()=>{let H=!1;return fetch("/api/health").then(Y=>Y.ok?Y.json():null).then(Y=>{H||(_e(m5(Y)),!(!Y||typeof Y.workspace!="string")&&Ye(Y.workspace))}).catch(()=>{}),()=>{H=!0}},[M]);const Be=j.useRef(we);Be.current=we;const it=ye?`${ye.kind}:${ye.to}`:"",ft=ye!=null&&Q!==it,Ze=j.useCallback(()=>{if(!ye)return;const H=Q===it?"":it;ne(H);try{window.localStorage.setItem(LE,H)}catch{}},[ye,it,Q]),[De,Je]=j.useState(()=>{if(typeof window>"u")return"";try{return window.localStorage.getItem($E)??""}catch{return""}}),Xe=K!=null&&K.invokedAs&&K.invokedAs!==en?K.invokedAs:null,Mt=Xe!=null&&De!==Xe,lt=j.useCallback(()=>{if(Xe){Je(Xe);try{window.localStorage.setItem($E,Xe)}catch{}}},[Xe]),Qe=((Va=K==null?void 0:K.upgrade)==null?void 0:Va.state)??"idle",Ot=Z3(K==null?void 0:K.upgrade),zt=j.useCallback(async()=>{try{await fetch("/api/upgrade",{method:"POST"})}catch{}ve()},[ve]);j.useEffect(()=>{if(Qe!=="running")return;const H=window.setInterval(ve,3e3);return()=>window.clearInterval(H)},[Qe,ve]);const et=j.useCallback(async()=>{var oe;const H=K==null?void 0:K.command;if(!H)return;let Y=!1;try{Y=await Promise.race([((oe=navigator.clipboard)==null?void 0:oe.writeText(H).then(()=>!0))??Promise.resolve(!1),new Promise(de=>window.setTimeout(()=>de(!1),500))])}catch{Y=!1}if(!Y)try{const de=document.createElement("textarea");de.value=H,de.setAttribute("readonly",""),de.style.cssText="position:fixed;top:0;left:0;opacity:0",document.body.appendChild(de),de.select(),Y=document.execCommand("copy"),de.remove()}catch{Y=!1}Y&&(fe(!0),window.setTimeout(()=>fe(!1),1600))},[K==null?void 0:K.command]),bt=j.useCallback(()=>{I(H=>(H||D(!1),!H))},[]),ht=j.useCallback(()=>{D(H=>(H||I(!1),!H))},[]),[vt,hn]=j.useState(!1),[Gt,Pt]=j.useState(0),bn=j.useRef(null),[gr,Sn]=j.useState(!1),Bt=j.useRef(null),[Fn,pn]=j.useState(0),vr=j.useRef(g$()),[Hr,xi]=j.useState(!1),Wr=j.useCallback(()=>{const H=vr.current,Y=H.setPaused(!H.paused);for(const oe of Y)n.current=Fx(n.current,oe);xi(H.paused)},[]),[ut,_i]=j.useState(Date.now()),[mn,Is]=j.useState(()=>{if(typeof window>"u")return!0;try{return window.localStorage.getItem(DE)!=="0"}catch{return!0}}),yr=j.useCallback(()=>{Is(H=>{const Y=!H;try{window.localStorage.setItem(DE,Y?"1":"0")}catch{}return Y})},[]),[zn,kn]=j.useState(!1),[Vr,Gr]=j.useState("restart"),[Yr,bi]=j.useState(null),sr=j.useRef(!1),Ms=j.useRef(null),En=j.useRef(0),wr=j.useCallback(async H=>{if(sr.current)return;const Y=(H==null?void 0:H.upgrade)===!0;sr.current=!0,Ms.current=Ot;const oe=++En.current;Gr(Y?"npx":"restart"),kn(!0);try{window.sessionStorage.setItem("agent-dag.restartPending",(ye==null?void 0:ye.to)??"")}catch{}try{await fetch("/api/restart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({upgrade:Y})})}catch{}window.setTimeout(()=>{if(En.current===oe&&sr.current){sr.current=!1,kn(!1);try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}}},Y?18e4:3e4)},[ye==null?void 0:ye.to,Ot]),Os=j.useRef(null);j.useEffect(()=>{let H=!1;for(const oe of n.current.agents.values())if(oe.state==="active"){H=!0;break}const Y=e$({enabled:mn,kind:ye==null?void 0:ye.kind,canRestart:(K==null?void 0:K.canRestart)===!0,busy:H,idleSince:Os.current,now:ut});Os.current=Y.idleSince,Y.restart&&wr()},[mn,ye==null?void 0:ye.kind,K==null?void 0:K.canRestart,ut,wr]),j.useEffect(()=>{const H=K==null?void 0:K.running;let Y=null,oe=null;try{Y=window.sessionStorage.getItem("agent-dag.restartPending"),oe=window.sessionStorage.getItem(FE)}catch{return}const de=Q3({bundle:"1.35.21",running:H,pending:Y,lastTried:oe});if(de==="reload"){try{window.sessionStorage.setItem(FE,H??"")}catch{return}window.location.reload();return}if(de!=="confirm")return;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1),bi(H??null);const xe=window.setTimeout(()=>bi(null),6e3);return()=>window.clearTimeout(xe)},[K==null?void 0:K.running]),j.useEffect(()=>{if(zn&&J3({asked:Ms.current,reported:Ot})){try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}sr.current=!1,kn(!1)}},[zn,Ot]);const Ps=j.useRef(H5()).current;j.useRef(!1);const Tt=j.useRef(new Map(Ps.positions.filter(([H])=>Ps.pins.includes(H)))),xr=j.useRef(null),_r=j.useState(()=>W5())[0],[Yt,Kr]=j.useState(""),[Si,nt]=j.useState(()=>new Set),[br,Xr]=j.useState(_$),[Ls,Qr]=j.useState(!1),Zr=j.useRef(null);j.useEffect(()=>{document.documentElement.dataset.theme=br;try{window.localStorage.setItem(OC,br)}catch{}},[br]),j.useEffect(()=>{if(!_r)return;const H=window.setTimeout(()=>{try{e.setViewport(_r,{duration:0})}catch{}},60);return()=>window.clearTimeout(H)},[e,_r]);const $s=j.useRef(!0);j.useEffect(()=>{const H=new EventSource("/events"),Y=m$(i,{now:()=>Date.now(),setTimeout:(oe,de)=>window.setTimeout(oe,de),clearTimeout:oe=>window.clearTimeout(oe)});return H.addEventListener("open",()=>{G(!0),Qr(!0)}),H.addEventListener("error",()=>G(!1)),H.addEventListener("replay-end",()=>{$s.current=!1,Y.flush()}),H.addEventListener("hook",oe=>{try{const de=JSON.parse(oe.data);if(!vr.current.accept(de))return;n.current=Fx(n.current,de),de.replay===!0||$s.current||Date.now()-de.receivedAt>3e4?Y.replay():Y.live()}catch{}}),()=>{H.close(),Y.cancel()}},[i]),j.useEffect(()=>{const H=setInterval(()=>{const Y=Date.now();_i(Y);let oe=mP(n.current,Y,Dx);gP(n.current,Y,Dx)&&(oe=!0),hP(n.current,Y,I5,M5)&&(oe=!0),pP(n.current,Y,O5,P5)&&(oe=!0),oe&&i()},250);return()=>clearInterval(H)},[i]);const ir=j.useRef(null),St=j.useRef(0),qs=420,pt=j.useCallback((H=500)=>{try{const Se=document.querySelector(".canvas-wrap"),Pe=Array.from(document.querySelectorAll(".react-flow__node")).filter(wt=>wt.offsetWidth>0);if(!Se||Pe.length===0)return;const Fe=Se.getBoundingClientRect(),Ge=e.getViewport();if(!Number.isFinite(Ge.zoom)||Ge.zoom<=0)return;const We=wt=>(wt-Fe.left-Ge.x)/Ge.zoom,ze=wt=>(wt-Fe.top-Ge.y)/Ge.zoom,Tn=Pe.map(wt=>wt.getBoundingClientRect()),Ys=Math.min(...Tn.map(wt=>We(wt.left))),Ao=Math.max(...Tn.map(wt=>We(wt.right))),Ks=Math.min(...Tn.map(wt=>ze(wt.top))),Io=Math.max(...Tn.map(wt=>ze(wt.bottom))),Xa=Ao-Ys,us=Io-Ks;if(!(Xa>0&&us>0))return;const Qt=Math.max(.2,Math.min(1,(Fe.width-160)/(Xa+qs)*.86,(Fe.height-160)/us*.86));e.setViewport({x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},{duration:H}),St.current=Date.now(),window.setTimeout(()=>{try{const wt={x:80-Ys*Qt,y:Math.max(80,(Fe.height-us*Qt)/2)-Ks*Qt,zoom:Qt},Mo=e.getViewport();(Math.abs(Mo.zoom-Qt)>.01||Math.abs(Mo.x-wt.x)>2)&&e.setViewport(wt,{duration:0})}catch{}},H+60)}catch{}},[e]),ko=j.useRef(""),Eo=j.useRef(null),qa=j.useRef(0),Sr=j.useCallback(()=>{qa.current=Date.now()},[]),Jr="agent-dag.autoFitDisabled",gn=j.useRef((()=>{if(typeof window>"u")return!1;try{return window.localStorage.getItem(Jr)==="1"}catch{return!1}})()),[Ds,Da]=j.useState(gn.current),Fa=j.useCallback(()=>{if(!gn.current){gn.current=!0,Da(!0);try{window.localStorage.setItem(Jr,"1")}catch{}}},[]),No=j.useCallback(()=>{gn.current=!1,Da(!1);try{window.localStorage.removeItem(Jr)}catch{}pt(400)},[e,pt]);j.useEffect(()=>{const H=setInterval(()=>{if(gn.current||Date.now()-qa.current<800)return;const Y=n.current,oe=Date.now(),de=[];for(const We of Y.agents.values())Mv(We,oe)&&de.push({id:We.id});if(de.length===0)return;const xe=e.getViewport(),Se=window.innerWidth-360,Pe=window.innerHeight-52;let Fe=!1,Ge=!1;for(const{id:We}of de){const ze=or.current.get(We),Tn=Tt.current.get(We)??Kt.current.get(We);if(!ze||!Tn)continue;Ge=!0;const Ys=Tn.x*xe.zoom+xe.x,Ao=Tn.y*xe.zoom+xe.y,Ks=(Tn.x+ze.width)*xe.zoom+xe.x,Io=(Tn.y+ze.height)*xe.zoom+xe.y;if(Ks>0&&Ys<Se&&Io>0&&Ao<Pe){Fe=!0;break}}Ge&&!Fe&&pt(600)},1500);return()=>clearInterval(H)},[e]);const ki=j.useRef(!1),or=j.useRef(new Map),es=j.useRef(0),ec=j.useCallback(H=>{const Y=or.current;let oe=!1;for(const de of H.nodeInternals.values()){const xe=de.width,Se=de.height;if(xe==null||Se==null)continue;const Pe=Y.get(de.id);Pe?(Math.abs(Pe.height-Se)>4||Math.abs(Pe.width-xe)>4)&&(Y.set(de.id,{width:xe,height:Se}),oe=!0):(Y.set(de.id,{width:xe,height:Se}),oe=!0)}return oe&&(es.current+=1),es.current},[]),Ei=Ke(ec),[He,za]=j.useState(0);j.useEffect(()=>{let H=0;const Y=()=>{const de=or.current;let xe=!1;for(const Se of document.querySelectorAll(".react-flow__node[data-id]")){const Pe=Se.getAttribute("data-id");if(!Pe)continue;const Fe=Se.offsetWidth,Ge=Se.offsetHeight;if(!Fe||!Ge)continue;const We=de.get(Pe);(!We||Math.abs(We.width-Fe)>4||Math.abs(We.height-Ge)>4)&&(de.set(Pe,{width:Fe,height:Ge}),xe=!0)}xe&&za(Se=>Se+1)};if(ki.current)return;let oe=0;return document.visibilityState==="hidden"?oe=window.setTimeout(Y,32):H=requestAnimationFrame(Y),()=>{cancelAnimationFrame(H),window.clearTimeout(oe)}});const Kt=j.useRef(new Map(Ps.positions)),Co=j.useRef(new Set),Ni=j.useRef(""),kr=j.useMemo(()=>{const H=[];for(const Y of n.current.agents.values())Mv(Y,ut)&&H.push(Y.id+(Y.parentId?`>${Y.parentId}`:""));return H.sort(),`${H.join("|")}#sv${Ei}.${He}`},[n.current,n.current.lastSeq,ut,Ei,He]),ts=j.useRef(null);j.useEffect(()=>(ts.current!=null&&window.clearTimeout(ts.current),ts.current=window.setTimeout(()=>{dv(Kt.current,Tt.current)},1500),()=>{ts.current!=null&&window.clearTimeout(ts.current)}),[kr]),j.useEffect(()=>{if(ko.current===kr)return;const H=ko.current;if(ko.current=kr,!H||gn.current)return;Date.now()-St.current>1200&&pt(400),ir.current&&window.clearTimeout(ir.current),ir.current=window.setTimeout(()=>{gn.current||pt(500)},280)},[kr,e,pt]);const Fs=j.useMemo(()=>{if(o.size===0)return null;const H=new Set;for(const Y of o){const oe=Y5(n.current,Y);if(oe)for(const de of oe)H.add(de)}return H.size>0?H:null},[n.current,n.current.lastSeq,o]),Nn=j.useMemo(()=>o5(n.current,ut),[n.current,n.current.lastSeq,ut]),Bn=j.useRef(null),[vn,Un]=j.useState({w:0,h:0});j.useEffect(()=>{const H=Bn.current;if(!H||typeof ResizeObserver>"u")return;const Y=new ResizeObserver(oe=>{var xe;const de=(xe=oe[0])==null?void 0:xe.contentRect;de&&Un(Se=>Math.abs(Se.w-de.width)>40||Math.abs(Se.h-de.height)>40?{w:de.width,h:de.height}:Se)});return Y.observe(H),Un({w:H.clientWidth,h:H.clientHeight}),()=>Y.disconnect()},[]);const zs=j.useCallback(H=>{var oe,de;const Y=H.target;!(Y!=null&&Y.closest)||Y.closest(j5)!==H.currentTarget||(H.preventDefault(),(de=(oe=document.activeElement)==null?void 0:oe.blur)==null||de.call(oe))},[]),Bs=j.useRef(new Map),[Hn,tc]=j.useState(!1);j.useEffect(()=>{const H=window.setTimeout(()=>tc(!0),2500);return()=>window.clearTimeout(H)},[]);const[Ba,Er]=j.useState(!1),ar=j.useRef(null),[Ci,Us]=j.useState(!1),Ua=j.useCallback(()=>{ar.current&&(window.clearTimeout(ar.current),ar.current=null),Er(!1)},[]),nc=j.useCallback(H=>{H.length!==0&&queueMicrotask(()=>{Er(!0),ar.current&&window.clearTimeout(ar.current),ar.current=window.setTimeout(()=>Er(!1),A5+80)})},[]);j.useEffect(()=>()=>{ar.current&&window.clearTimeout(ar.current)},[]);const ns=vn.w>0?vn.w*.92:0,rs=vn.h>0?vn.h:0,{nodes:Xt,edges:Ti}=j.useMemo(()=>K5(n.current,ut,ns,rs,Tt.current,Yt,or.current,Bs.current,nc,Hn,Ci,Kt.current,Co.current,kr,Ni,o,Fs,Nn,b),[n.current,n.current.lastSeq,ut,ns,rs,Hn,Ci,Yt,kr,o,Fs,Nn,b,Gt]),Ha=j.useMemo(()=>{const H=new Map;for(const oe of Xt){const de=oe.data;if(!(de!=null&&de.sessionId)||de.exitAt!=null)continue;const xe=oe.width,Se=oe.height;if(xe==null||Se==null)continue;const Pe=oe.position.x,Fe=oe.position.y,Ge=Pe+xe,We=Fe+Se,ze=H.get(de.sessionId);ze?(ze.minX=Math.min(ze.minX,Pe),ze.minY=Math.min(ze.minY,Fe),ze.maxX=Math.max(ze.maxX,Ge),ze.maxY=Math.max(ze.maxY,We)):H.set(de.sessionId,{minX:Pe,minY:Fe,maxX:Ge,maxY:We})}const Y=[];for(const[oe,de]of H){const xe=de.maxX-de.minX+au*2,Se=de.maxY-de.minY+au*2;Y.push({id:`group:${oe}`,type:PT,position:{x:de.minX-au,y:de.minY-au},data:{sessionId:oe,w:xe,h:Se},width:xe,height:Se,style:{width:xe,height:Se},zIndex:-1,draggable:!0,selectable:!1,focusable:!1,deletable:!1,connectable:!1})}return Y},[Xt,ut]),rc=j.useMemo(()=>{const H=[...Ha,...Xt],Y=Bt.current;return!Y||Y.size===0?H:H.map(oe=>{const de=Y.get(oe.id);return de?{...oe,position:de}:oe})},[Ha,Xt,Fn]),Nr=j.useMemo(()=>{if(!Yt)return null;const H=new Set;for(const Y of n.current.agents.values())Nn.has(Y.id)&&qT(Y,Yt)&&H.add(Y.id);return H},[n.current,n.current.lastSeq,Yt,Nn]),Cn=j.useMemo(()=>k5(Yt,(Nr==null?void 0:Nr.size)??0,Nn.size),[Yt,Nr,Nn]),ss=j.useMemo(()=>{const H=new Set;for(const Y of n.current.agents.values())for(const oe of Y.tools)H.add(HT(oe.name));return Object.keys(Lv).filter(Y=>H.has(Y))},[n.current,n.current.lastSeq]);j.useEffect(()=>{if(ss.length<=1){Sn(!1);return}let H=0;const Y=()=>{const oe=bn.current;if(oe&&!document.hidden){const de=oe.getBoundingClientRect(),xe=Array.from(document.querySelectorAll(".react-flow__node, .tool-burst")).map(Pe=>Pe.getBoundingClientRect()),Se=l5(de,xe,8);Sn(Pe=>Pe===Se?Pe:Se)}H=window.setTimeout(Y,300)};return Y(),()=>window.clearTimeout(H)},[ss.length]);const is=j.useMemo(()=>{const H=new Map;for(const oe of n.current.agents.values())for(const de of oe.tools){if(!de.name.startsWith("mcp__"))continue;const xe=de.name.slice(5),Se=xe.indexOf("__"),Pe=Se>0?xe.slice(0,Se):xe;H.set(Pe,(H.get(Pe)??0)+1)}const Y=[];for(const[oe,de]of H){let xe=5381;for(let Se=0;Se<oe.length;Se++)xe=(xe<<5)+xe^oe.charCodeAt(Se);Y.push({server:oe,count:de,hue:Math.abs(xe)%360})}return Y.sort((oe,de)=>de.count-oe.count),Y},[n.current,n.current.lastSeq]),lr=j.useCallback(H=>{nt(Y=>{const oe=new Set(Y);return oe.has(H)?oe.delete(H):oe.add(H),oe})},[]),Rt=u?n.current.agents.get(u):null,os=h?Array.from(n.current.agents.values()).flatMap(H=>H.tools).find(H=>H.id===h)??null:null,To=j.useCallback(async()=>{try{await fetch("/api/clear",{method:"POST"})}catch{}n.current=Tv(),Tt.current.clear(),or.current.clear(),Kt.current.clear(),Ni.current="",BE(),m(),i()},[i,m]),Hs=j.useRef(S);Hs.current=S;const Ws=j.useRef(!1);Ws.current=os!=null||vt||N!=null||y!=null;const Vs=j.useCallback(H=>{const Y=n$(H,{confirmOpen:Hs.current,modalOpen:Ws.current});Y==="confirm"?k(!0):Y==="clear"&&(k(!1),To())},[To]),Lt=j.useCallback(()=>{Tt.current.clear(),Kt.current.clear(),Ni.current="",BE(),i(),window.setTimeout(()=>pt(500),80)},[i,e,pt]),Cr=j.useCallback(()=>pt(500),[pt]),as=j.useRef(Xt);as.current=Xt;const ls=j.useRef(u);ls.current=u;const Ro=j.useCallback(H=>{const Y=as.current,oe=s$(Y.map(Se=>({id:Se.id,x:Se.position.x,y:Se.position.y})),ls.current,H),de=oe?Y.find(Se=>Se.id===oe):void 0;if(!de)return;const xe=OE(document.activeElement);d(de.id,!1),window.setTimeout(()=>{try{e.fitView({padding:.35,duration:350,nodes:[de]})}catch{}St.current=Date.now(),xe&&R5(de.id)},30)},[d,e]),Ri=j.useCallback(H=>{d(H,!1),window.setTimeout(()=>{try{const Y=as.current.find(oe=>oe.id===H);Y&&e.fitView({padding:.3,duration:500,nodes:[Y]}),St.current=Date.now()}catch{}},60)},[d,e]);j.useEffect(()=>{const H=Y=>{var Fe,Ge,We;const oe=Y.target,de={tagName:oe==null?void 0:oe.tagName,isContentEditable:oe==null?void 0:oe.isContentEditable,role:(Fe=oe==null?void 0:oe.getAttribute)==null?void 0:Fe.call(oe,"role"),type:oe==null?void 0:oe.type};if(Y.key==="Escape"){const ze=PP({overlayOpen:Eu.depth()>0,typing:ry(de)});ze==="dismiss"?Eu.dismissTop():ze==="blur"?oe==null||oe.blur():(l$(de)&&(oe==null||oe.blur()),m());return}if(_C(Y))return;const xe=OE(oe)?(Ge=oe==null?void 0:oe.getAttribute)==null?void 0:Ge.call(oe,"data-id"):null,Se=xe&&as.current.some(ze=>ze.id===xe)?xe:null,Pe=o$(Y,Se);if(Pe.kind==="activate"){Y.preventDefault(),d(Pe.nodeId,Pe.additive);return}if(Pe.kind!=="node"&&!(Pe.nodeId==null&&UP(de))){if(Y.key==="/"){Y.preventDefault(),(We=Zr.current)==null||We.focus();return}Y.key===" "&&(Y.preventDefault(),Wr()),(Y.key==="c"||Y.key==="C")&&Vs("shortcut"),(Y.key==="r"||Y.key==="R")&&Lt(),(Y.key==="f"||Y.key==="F")&&Cr(),(Y.key==="l"||Y.key==="L")&&bt(),(Y.key==="h"||Y.key==="H")&&hn(ze=>!ze),(Y.key==="u"||Y.key==="U")&&z(ze=>!ze),(Y.key==="a"||Y.key==="A")&&Be.current.claude&&ht(),(Y.key==="j"||Y.key==="J")&&Ro(1),(Y.key==="k"||Y.key==="K")&&Ro(-1),(Y.key==="t"||Y.key==="T")&&Xr(ze=>ze==="dark"?"light":"dark")}};return window.addEventListener("keydown",H),()=>window.removeEventListener("keydown",H)},[Vs,Lt,Cr,m,d,Ro,Wr]);const Tr=n.current.agents.size,Gs=new Set(Array.from(n.current.agents.values()).map(H=>H.sessionId)).size,tn=j.useMemo(()=>JL(n.current.agents.values()),[n.current,n.current.lastSeq]),jo=j.useMemo(()=>e3(n.current.agents.values()),[n.current,n.current.lastSeq]),Wa=j.useRef(null);j.useEffect(()=>{const H=b$({waiting:tn.length,running:jo}),Y=Wa.current;if(Wa.current=H,(Y==null?void 0:Y.title)!==H.title&&(document.title=H.title),(Y==null?void 0:Y.icon)!==H.icon){const oe=document.querySelector('link[rel="icon"]');oe&&(oe.href=S$[H.icon])}},[tn.length,jo]);const[sc,ic]=j.useState(""),ji=E$(tn);j.useEffect(()=>{ic(H=>N$(H,ji))},[ji]);const nn=j.useMemo(()=>{let H=0,Y=0,oe=0,de=0,xe=0,Se=0,Pe=0,Fe=0,Ge=0;for(const We of n.current.agents.values()){H+=We.usage.inputTokens,Y+=We.usage.outputTokens,oe+=We.usage.cacheReadTokens,de+=We.usage.cacheCreateTokens;const ze=dn(We.usage,We.model);xe+=ze.total,Se+=ze.input,Pe+=ze.output,Fe+=ze.cacheRead,Ge+=ze.cacheWrite}return{inT:H,outT:Y,cacheR:oe,cacheC:de,sum:H+Y,cost:{total:xe,input:Se,output:Pe,cacheRead:Fe,cacheWrite:Ge}}},[n.current,n.current.lastSeq]);return g.jsxs("div",{className:"app",children:[g.jsx("a",{className:"skip-link",href:"#canvas",children:"Skip to the canvas"}),g.jsxs("header",{className:"topbar",children:[g.jsxs("div",{className:"brand",children:[g.jsx("span",{className:"logo"}),g.jsx("h1",{children:en}),ye?g.jsxs("button",{type:"button",className:"v stale",onClick:Ze,"aria-label":h5({...ye,open:ft}),title:ye.kind==="restart"?`Running v${ye.from}; v${ye.to} is installed on disk. Restart to pick it up.`:`Running v${ye.from}; v${ye.to} is on npm.`,children:["v",ye.from,g.jsx("span",{className:"v-dot","aria-hidden":!0})]}):(()=>{const H={running:(K==null?void 0:K.running)??"1.35.21",latest:K==null?void 0:K.latest,latestPending:K==null?void 0:K.latestPending,checkedAgo:K!=null&&K.checkedAt?Pv(ut-K.checkedAt):null,checkDisabled:K==null?void 0:K.checkDisabled,checking:ce};return g.jsxs("button",{type:"button",className:ce?"v checking":"v",onClick:()=>ve(!0),"aria-busy":ce||void 0,"aria-label":f5(H),title:d5(H),children:["v",H.running]})})()]}),Rt&&(()=>{const H=dn(Rt.usage,Rt.model),Y=Math.max(0,((Rt.endedAt??ut)-Rt.startedAt)/1e3),oe=Rt.state==="active"?ku(H.total,Y):null,de=o.size-1;return g.jsxs("button",{type:"button",className:"selected-ribbon",title:`Fit view to ${Rt.label}`,onClick:()=>{try{const xe=Xt.find(Se=>Se.id===Rt.id);xe&&e.fitView({padding:.35,duration:500,nodes:[xe]}),St.current=Date.now()}catch{}},children:[g.jsx("span",{className:`state-pill state-${Rt.state}`,children:Rt.state==="active"?"live":Rt.state}),g.jsx("span",{className:"selected-label",children:Rt.label}),H.total>0&&g.jsxs("span",{className:"selected-cost",children:[qe(H.total),oe?g.jsxs("span",{className:"selected-rate",children:[" · ",oe]}):null]}),de>0&&g.jsxs("span",{className:"selected-extra",children:["+",de]}),g.jsx("span",{"aria-hidden":!0,className:"selected-close",onClick:xe=>{xe.stopPropagation(),m()},children:"×"})]})})(),g.jsxs("div",{className:"actions",children:[g.jsxs("div",{className:"search",children:[g.jsx("span",{className:"search-icon","aria-hidden":!0,children:"⌕"}),g.jsx("input",{ref:Zr,type:"text",placeholder:E5,value:Yt,onChange:H=>Kr(H.target.value),spellCheck:!1,"aria-label":"Filter the graph"}),Yt?g.jsx("button",{className:"search-clear","aria-label":"Clear search",onClick:()=>Kr(""),children:"×"}):g.jsx("kbd",{className:"search-kbd","aria-hidden":!0,children:"/"}),Cn.count&&g.jsx("span",{className:`search-count${Cn.empty?" none":""}`,"aria-live":"polite",title:`${Cn.count} agents on the canvas match “${Yt}”`,children:Cn.count})]}),g.jsxs("span",{className:"status",children:[(()=>{const H=b5({connected:M,paused:Hr,held:vr.current.size});return g.jsx("span",{className:`pill ${H.tone}`,title:H.title,children:H.label})})(),g.jsxs("span",{className:"stat",title:y5(we),children:[g.jsx("span",{className:"count",children:Gs}),g.jsx("span",{className:"lbl",children:"sessions"})]}),g.jsxs("span",{className:"stat",title:"Total agents (root + subagents)",children:[g.jsx("span",{className:"count",children:Tr}),g.jsx("span",{className:"lbl",children:"agents"})]}),g.jsxs("span",{className:"stat",title:w5(we),children:[g.jsx("span",{className:"count",children:n.current.totalEvents}),g.jsx("span",{className:"lbl",children:"events"})]}),nn.sum>0&&g.jsxs("span",{className:"stat",title:`in:${nn.inT.toLocaleString()} out:${nn.outT.toLocaleString()} cache-r:${nn.cacheR.toLocaleString()} cache-c:${nn.cacheC.toLocaleString()}`,children:[g.jsx("span",{className:"count",children:Dt(nn.sum)}),g.jsx("span",{className:"lbl",children:"tokens"})]}),is.length>0&&g.jsxs("span",{className:"stat mcp-legend",title:`MCP servers seen this session:
73
73
  ${is.map(H=>` ${H.server}: ${H.count} call${H.count===1?"":"s"}`).join(`
74
74
  `)}`,children:[is.slice(0,6).map(H=>g.jsx("span",{className:"mcp-dot",style:{"--mcp-hue":H.hue}},H.server)),is.length>6&&g.jsxs("span",{className:"mcp-more",children:["+",is.length-6]}),g.jsx("span",{className:"lbl",children:"mcp"})]}),nn.cost.total>0&&(()=>{let H=0,Y=0;for(const xe of n.current.agents.values()){if(xe.state!=="active")continue;const Se=dn(xe.usage,xe.model);H+=Se.total,Y=Math.max(Y,((xe.endedAt??ut)-xe.startedAt)/1e3)}const oe=Y>0?ku(H,Y):null,de=`input ${qe(nn.cost.input)} + output ${qe(nn.cost.output)} + cache r ${qe(nn.cost.cacheRead)} + cache w ${qe(nn.cost.cacheWrite)}${oe?`
75
75
  active burn: ${oe}`:""}`;return g.jsxs("span",{className:"stat",title:de,children:[g.jsx("span",{className:"count",children:qe(nn.cost.total)}),g.jsxs("span",{className:"lbl",children:["cost",oe?` · ${oe}`:""]})]})})()]}),g.jsx("div",{className:"vis-hidden",role:"status","aria-atomic":"true",children:sc}),tn.length>0&&g.jsxs("button",{type:"button",className:"waiting-stat",onClick:()=>Ri(tn[0].id),title:`Blocked waiting for you — click to go to the one that has been stuck longest:
@@ -40,7 +40,7 @@
40
40
  document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
41
41
  })();
42
42
  </script>
43
- <script type="module" crossorigin src="/assets/index-5cbH0A1P.js"></script>
43
+ <script type="module" crossorigin src="/assets/index-DGYih2FE.js"></script>
44
44
  <link rel="stylesheet" crossorigin href="/assets/index-hLBidJXz.css">
45
45
  </head>
46
46
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-dag",
3
- "version": "1.35.19",
3
+ "version": "1.35.21",
4
4
  "description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  // agent-dag server: HTTP ingest + SSE broadcast + static file serving.
2
2
  // Single-file pure Node HTTP server, zero deps.
3
3
  import { createServer } from "node:http";
4
- import { readFile, stat, mkdir, appendFile, open, truncate, readdir, unlink } from "node:fs/promises";
4
+ import { readFile, stat, mkdir, open, truncate, readdir, unlink } from "node:fs/promises";
5
5
  import { createReadStream, existsSync, readFileSync, realpathSync } from "node:fs";
6
6
  import { homedir } from "node:os";
7
7
  import { extname, join, resolve, dirname as pdirname } from "node:path";
@@ -13,7 +13,7 @@ import { claudeConfigDir } from "./claude-dir.mjs";
13
13
  import { CODEX_HOME, CODEX_SESSIONS_DIR, STOP, walkRolloutDays } from "./codex-dir.mjs";
14
14
  import { PRODUCT } from "./brand.mjs";
15
15
  import { invokedName, renameNotice } from "./invoked-as.mjs";
16
- import { codexCwdInWorkspace, writesCodexLog } from "./log-writer.mjs";
16
+ import { appendLogLine, codexCwdInWorkspace, writesCodexLog } from "./log-writer.mjs";
17
17
 
18
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
19
19
  const PKG_ROOT = resolve(__dirname, "..", "..");
@@ -1804,8 +1804,16 @@ function pushEvent(raw, source, opts = {}) {
1804
1804
  }
1805
1805
 
1806
1806
  if (persisting) {
1807
- // Fire-and-forget append. JSONL = newline-delimited JSON.
1808
- appendFile(persistPath, json + "\n", "utf8").catch(() => {});
1807
+ // Fire-and-forget append. JSONL = newline-delimited JSON, so the whole line
1808
+ // has to reach the file as one write — this used to be `appendFile`, which
1809
+ // splits anything over 512 KiB into separate appends and let another
1810
+ // event land in the middle of a large tool response. See appendLogLine.
1811
+ //
1812
+ // Note this runs AFTER redactDeckToken above, as every path to the log
1813
+ // does: the string being written is the one serialization of the event the
1814
+ // SSE frame also used, and the token was taken out of the payload before
1815
+ // either existed.
1816
+ appendLogLine(persistPath, json + "\n");
1809
1817
  // Cheap throttled check (every 30s) — only rotates if file > 50MB.
1810
1818
  maybeRotatePersistFile();
1811
1819
  }
@@ -1837,9 +1845,35 @@ function pushEvent(raw, source, opts = {}) {
1837
1845
  return evt;
1838
1846
  }
1839
1847
 
1848
+ /**
1849
+ * Read the log back into the ring buffer at boot.
1850
+ *
1851
+ * A line that will not parse is skipped rather than thrown on, and that is
1852
+ * deliberate — it is what lets a log damaged by the pre-#446 writer still
1853
+ * replay. Every line before the damage and every line after it is whole, so
1854
+ * stopping at the first bad one would throw away the rest of the session for a
1855
+ * fault that costs one event. Newline framing is what makes the recovery
1856
+ * possible: a torn write leaves the reader resynchronised at the very next
1857
+ * `\n`, with no length prefix to have been lost along with the bytes.
1858
+ *
1859
+ * What changed is that the skip is no longer silent. Before, a deck whose log
1860
+ * had been shredded replayed "mostly" and said nothing anywhere: the largest
1861
+ * tool responses and whatever ordinary event was spliced into them were gone,
1862
+ * with no counter and no line on the terminal. The count below is the only
1863
+ * signal that a log carries damage from a writer that has since been fixed, and
1864
+ * the byte total is what tells the user whether it was one truncated tail from
1865
+ * a kill -9 or a megabyte of shredded tool output.
1866
+ *
1867
+ * Deliberately one line, and deliberately without the path in it: this prints
1868
+ * onto a terminal the deck is about to paint over (see oneLine in term.mjs for
1869
+ * what a multi-line message does there), and the path was already printed at
1870
+ * boot by the caller.
1871
+ */
1840
1872
  async function replayLog(filePath) {
1841
1873
  if (!existsSync(filePath)) return 0;
1842
1874
  let count = 0;
1875
+ let skipped = 0;
1876
+ let skippedBytes = 0;
1843
1877
  const rl = createInterface({ input: createReadStream(filePath, { encoding: "utf8" }) });
1844
1878
  for await (const line of rl) {
1845
1879
  if (!line) continue;
@@ -1849,7 +1883,14 @@ async function replayLog(filePath) {
1849
1883
  pushEvent(evt.payload, evt.source ?? "replay", { receivedAt: evt.receivedAt, replay: true });
1850
1884
  count++;
1851
1885
  }
1852
- } catch { /* skip corrupt line */ }
1886
+ } catch {
1887
+ skipped++;
1888
+ skippedBytes += Buffer.byteLength(line, "utf8");
1889
+ }
1890
+ }
1891
+ if (skipped > 0) {
1892
+ const kb = (skippedBytes / 1024).toFixed(0);
1893
+ console.warn(`${PRODUCT}: skipped ${skipped} unreadable line(s) (${kb}KB) while replaying the event log`);
1853
1894
  }
1854
1895
  return count;
1855
1896
  }
@@ -2164,9 +2205,31 @@ async function handleRestart(req, res) {
2164
2205
 
2165
2206
  if (_restarting) return send(res, 202, { ok: true, already: true });
2166
2207
  _restarting = true;
2167
- send(res, 200, { ok: true, mode });
2208
+ // Accepted either way the ask is good and the launcher holds on to it — but
2209
+ // the two are not the same event and answering 200 to both would be this
2210
+ // window's second untruth rather than its first. Between this listener
2211
+ // accepting its first connection and bin/deck.js finishing the rest of its
2212
+ // startup there is a real stretch during which a restart cannot be run yet,
2213
+ // and a caller that reads the body should be able to tell that it is waiting
2214
+ // on a boot rather than on a supervisor. See markDeckReady, and requestRestart
2215
+ // in bin/deck.js for the half that does the waiting.
2216
+ if (_deckReady) send(res, 200, { ok: true, mode });
2217
+ else send(res, 202, { ok: true, mode, booting: true, detail: "the deck is still starting up; the restart runs as soon as it has finished booting" });
2168
2218
  // Let the response flush before the listener goes away.
2169
- setTimeout(() => { try { _onRestart(mode); } catch { _restarting = false; } }, 120).unref();
2219
+ setTimeout(() => {
2220
+ try { _onRestart(mode); }
2221
+ catch (err) {
2222
+ // This catch is where #448 lived: it released the server's half of the
2223
+ // latch and said nothing, while the launcher's half stayed set with
2224
+ // nothing left to clear it, and every later restart answered "ok" and did
2225
+ // nothing for the rest of the process's life. The launcher now owns its
2226
+ // own failures; this stays as the outer net, and it says so — one line,
2227
+ // message only, because the terminal underneath is repainted every 800ms
2228
+ // by the pulse and a stack dumped into it is a stack nobody can read.
2229
+ _restarting = false;
2230
+ console.error(`${PRODUCT}: restart request failed: ${err?.message ?? err}`);
2231
+ }
2232
+ }, 120).unref();
2170
2233
  }
2171
2234
 
2172
2235
  /** The other end of the latch above, for the upgrade that never happened.
@@ -2180,6 +2243,22 @@ export function releaseRestart() {
2180
2243
  _restarting = false;
2181
2244
  }
2182
2245
 
2246
+ /** The launcher saying its boot is over: everything a shutdown would have to
2247
+ * tear down now exists.
2248
+ *
2249
+ * This server starts accepting from inside startServer, before that call has
2250
+ * even returned to bin/deck.js — so /api/restart is answerable for the whole
2251
+ * of the startup that follows it, which on a cold boot includes the discovery
2252
+ * file's first fsynced write and spawning the browser. The listener cannot see
2253
+ * any of that from here; it has to be told. Called once, from bin/deck.js.
2254
+ *
2255
+ * Only /api/restart reads it, and only to answer honestly. Nothing is refused
2256
+ * on the strength of it: the restart is still handed to the launcher, which
2257
+ * holds it until it can run it (#448). */
2258
+ export function markDeckReady() {
2259
+ _deckReady = true;
2260
+ }
2261
+
2183
2262
  async function handleQuota(req, res) {
2184
2263
  const { fetchClaudeQuota } = await import(
2185
2264
  pathToFileURL(join(PKG_ROOT, "src/server/quota.mjs")).href
@@ -2837,10 +2916,19 @@ let _onRestart = null;
2837
2916
  // A restart is in flight. Several browser tabs watching the same deck will each
2838
2917
  // ask; the second ask must not re-enter the shutdown.
2839
2918
  let _restarting = false;
2919
+ // Whether the launcher has finished booting — see markDeckReady. False for the
2920
+ // whole window between this listener accepting its first connection and
2921
+ // bin/deck.js reaching the end of its startup, which is a window /api/restart
2922
+ // is reachable in and cannot answer for on its own.
2923
+ let _deckReady = false;
2840
2924
 
2841
2925
  export async function startServer({ port = 4317, host = "127.0.0.1", persist = null, portRange = [4318, 4400], workspace = "", codex = true, claude = true, onRestart = null } = {}) {
2842
2926
  _onRestart = typeof onRestart === "function" ? onRestart : null;
2843
2927
  _canRestart = _onRestart != null && persist != null;
2928
+ // A new listener is a new boot, whatever a previous one had got as far as
2929
+ // reporting. Nothing but bin/deck.js ever sets this, and it does so once, at
2930
+ // the end of the startup that begins with this call.
2931
+ _deckReady = false;
2844
2932
  _workspace = typeof workspace === "string" ? workspace : "";
2845
2933
  // `!== false` rather than a cast: a caller that omits the field means "yes",
2846
2934
  // which is how every embedder that predates this option keeps working.
@@ -20,6 +20,7 @@
20
20
  // by the host CLI, with no path back to the module it came from — the same
21
21
  // reason it re-derives the Claude config dir inline. The two copies are pinned
22
22
  // equal by a test, as challengeProof's pair already is.
23
+ import { open } from "node:fs/promises";
23
24
  import { win32, posix } from "node:path";
24
25
 
25
26
  /**
@@ -148,3 +149,135 @@ export function writesCodexLog({ decks, pid, cwd, platform = process.platform })
148
149
  }
149
150
  return electWriters(group, platform).has(self);
150
151
  }
152
+
153
+ // ─── Appending one whole line ─────────────────────────────────────────────
154
+ //
155
+ // The election above decides WHICH deck writes a line. This decides HOW, and
156
+ // the two are answers to the same question: the log is one file that several
157
+ // processes append to, so a line that arrives in pieces is a line another
158
+ // writer can land inside.
159
+ //
160
+ // What was wrong. events.jsonl was appended with `fsPromises.appendFile`, which
161
+ // is not one write(2). Node's writeFileHandle loops over the payload in chunks
162
+ // of kWriteFileMaxChunkSize — 512 KiB — awaiting each one, so any line longer
163
+ // than 524288 bytes became two or more separate appends with the event loop
164
+ // free in between. Measured here, on Node 26 / macOS 15 / APFS, by watching the
165
+ // file grow while one 5 MiB appendFile was in flight: it arrived in exactly ten
166
+ // steps of 524288 bytes. A single handle.write of the same payload arrived in
167
+ // one step of 5242880.
168
+ //
169
+ // That is reachable in ordinary use. `POST /api/event` accepts a body up to
170
+ // 5,000,000 characters, and a PostToolUse carrying a large Read or Bash
171
+ // response is routinely a good fraction of that. The event that gets spliced
172
+ // into the middle is nearly always this deck's own: pushEvent kicks off
173
+ // maybeResolveUsage / maybeResolveModel / maybeResolveContext on the same call,
174
+ // and each of those pushes its own event a moment later. Reproduced at 8/8 runs
175
+ // with one 1 MiB line and eight small ones issued in one tick, and 4/4 runs
176
+ // across two processes appending 3 MiB lines to one file.
177
+ //
178
+ // The damage is silent and doubled: the oversized event is torn into two
179
+ // unparseable halves AND the small event that landed between them is swallowed
180
+ // inside one of those halves, so a boot replay loses both.
181
+ //
182
+ // What this does instead: exactly one write(2) per line, on a descriptor opened
183
+ // O_APPEND. That is the primitive the atomicity rests on, so it is worth
184
+ // stating exactly what each platform promises.
185
+ //
186
+ // Linux — a write to a regular file holds the inode's i_rwsem for the whole
187
+ // call, so one write(2) is atomic against every other writer regardless of
188
+ // size, up to MAX_RW_COUNT (~2 GiB) after which the call returns short.
189
+ // O_APPEND additionally makes the seek-to-end and the write one step, which
190
+ // is what stops two processes overwriting each other's tail.
191
+ //
192
+ // macOS — measured, not assumed, because the guarantee is not written down as
193
+ // plainly: single writes of 1 MiB and 5 MiB in-process, and 3 MiB from two
194
+ // processes at once, produced no torn line in any run, while appendFile of
195
+ // the same payloads tore in every run.
196
+ //
197
+ // Windows — libuv issues the append as a WriteFile at the documented
198
+ // end-of-file offset on a handle opened FILE_APPEND_DATA, which NTFS serialises
199
+ // per call. This is the platform where the Codex rollout watcher is the only
200
+ // capture path there is, so it is also the platform with the most decks
201
+ // sharing one log.
202
+ //
203
+ // Where the guarantee does NOT hold: any filesystem that can return a short
204
+ // write — NFS and some network/FUSE mounts under memory pressure, and any
205
+ // platform once the payload passes its per-call ceiling. The loop below then
206
+ // issues a second write for the remainder, and another writer can land between
207
+ // them. Nothing available in userland fixes that, so the answer is the reader:
208
+ // replayLog skips a line it cannot parse, counts it, and says so, rather than
209
+ // stopping at it. A torn line costs one event, not the rest of the file.
210
+ //
211
+ // Why not cap or drop oversized events instead. The 5 MB ceiling is already the
212
+ // cap, and it is enforced where it belongs — at ingest, with a 413 the poster
213
+ // can see. Refusing to persist an event the deck is happily drawing would make
214
+ // the canvas and its own replay disagree, which is the bug this fixes wearing a
215
+ // different hat.
216
+ //
217
+ // Why the file is opened per line rather than kept open. maybeRotatePersistFile
218
+ // renames events.jsonl to events.jsonl.1 at 50 MB and `/api/clear` truncates it
219
+ // to zero; a descriptor held across either would go on filling the file nobody
220
+ // reads any more. Opening per line is also exactly the syscall count appendFile
221
+ // already paid — open, write, close — minus the extra writes.
222
+ const appendTails = new Map();
223
+
224
+ /**
225
+ * Append one already-serialized line to the shared log, whole.
226
+ *
227
+ * Appends are serialized per file behind a promise chain. That is not what
228
+ * makes them atomic — the single write(2) below is — but it keeps the order the
229
+ * lines land in equal to the order pushEvent produced them, which is the order
230
+ * a replay reads them back in, and it keeps this process to one open descriptor
231
+ * on the log no matter how many events arrive in one tick.
232
+ *
233
+ * Never rejects. This is called fire-and-forget from the hottest path in the
234
+ * process, and a rejection nobody awaits is a dead deck; a line that could not
235
+ * be written is a line lost, which the caller could not have done anything
236
+ * about anyway. The chain deliberately continues past a failure — one ENOSPC
237
+ * must not stop every later event from being recorded once space is back.
238
+ *
239
+ * @param {string} filePath absolute path to the log
240
+ * @param {string} line the line to append, INCLUDING its trailing newline
241
+ */
242
+ export function appendLogLine(filePath, line) {
243
+ const tail = (appendTails.get(filePath) ?? Promise.resolve())
244
+ .then(() => writeWholeLine(filePath, line))
245
+ .catch(() => {});
246
+ appendTails.set(filePath, tail);
247
+ // Drop the chain once it drains, so a process that writes to several logs
248
+ // over its life does not hold a promise per path it has finished with. Only
249
+ // the tail we just installed is cleared: if another append chained on in the
250
+ // meantime the map already points at that one, and deleting it would let the
251
+ // next line race the one still in flight.
252
+ tail.then(() => { if (appendTails.get(filePath) === tail) appendTails.delete(filePath); });
253
+ return tail;
254
+ }
255
+
256
+ /**
257
+ * One line, one write(2). The loop exists only for the short-write case
258
+ * described above; on every filesystem that does not do that it runs once.
259
+ *
260
+ * `position` is left null on purpose. That is what makes libuv issue write(2)
261
+ * rather than pwrite(2), and pwrite is the version that does not honour
262
+ * O_APPEND on every platform — it would write at an offset computed before the
263
+ * other writer moved the end of the file.
264
+ */
265
+ async function writeWholeLine(filePath, line) {
266
+ // Encoded up front so the short-write loop can count bytes rather than UTF-16
267
+ // units. A multi-byte character split across two chunks would otherwise be
268
+ // resumed mid-sequence and the line would be mojibake even without a race.
269
+ const buf = Buffer.from(line, "utf8");
270
+ const handle = await open(filePath, "a");
271
+ try {
272
+ let written = 0;
273
+ while (written < buf.byteLength) {
274
+ const { bytesWritten } = await handle.write(buf, written, buf.byteLength - written, null);
275
+ // A write that reports no progress would spin this loop forever inside a
276
+ // promise nobody is watching. Give up on the line instead.
277
+ if (bytesWritten <= 0) throw new Error(`append made no progress at ${written}/${buf.byteLength} bytes`);
278
+ written += bytesWritten;
279
+ }
280
+ } finally {
281
+ await handle.close();
282
+ }
283
+ }