agent-dag 1.36.3 → 1.36.4

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
@@ -446,17 +446,6 @@ async function reportStartup(jobs) {
446
446
  }
447
447
  }
448
448
 
449
- // Everything above is once-per-session setup — hook install, tool probes,
450
- // registry lookups, and the banner it now runs underneath. A respawn is the
451
- // same session continuing, so it skips the lot and prints one line instead.
452
- // This is the difference between a restart that feels instant and one that
453
- // makes you wonder whether it worked.
454
- if (!RESPAWN) {
455
- const jobs = startupWork();
456
- await printBanner();
457
- await reportStartup(jobs);
458
- }
459
-
460
449
  // Asking the supervisor to bring us back. It is the only party that can, and
461
450
  // only after this process is gone — which is precisely what keeps the
462
451
  // replacement from racing this listener onto a random fallback port.
@@ -472,20 +461,22 @@ let upgradeTimer = null;
472
461
  //
473
462
  // The server below starts accepting connections from inside startServer, before
474
463
  // that call has returned — so /api/restart is reachable for the whole of the
475
- // boot that follows it: the port report to the supervisor, the discovery file
476
- // and its first fsynced write, and on a cold start the browser spawn. A restart
477
- // landing in that window used to reach `shutdown` before the binding holding it
478
- // was initialised and die of a ReferenceError, having already set the latch
479
- // above, with nothing left to clear it — after which every restart from every
480
- // tab was answered "ok" and did nothing, for the life of the process (#448).
464
+ // boot that follows it: the startup report, the port report to the supervisor,
465
+ // the discovery file and its first fsynced write, and on a cold start the
466
+ // browser spawn. A restart landing in that window used to reach `shutdown`
467
+ // before the binding holding it was initialised and die of a ReferenceError,
468
+ // having already set the latch above, with nothing left to clear it — after
469
+ // which every restart from every tab was answered "ok" and did nothing, for the
470
+ // life of the process (#448).
481
471
  //
482
- // So an ask that arrives too early is held rather than run: the window is
483
- // bounded and short, the user asked for something this deck can genuinely give
484
- // a second later, and refusing outright would put back the same silence in a
485
- // politer form. BOOT_RESTART_MS is the outer bound, for the reason
486
- // UPGRADE_ANSWER_MS above is one: a boot that has not finished in ten seconds is
487
- // itself the fault, and the restart is the answer to it rather than a casualty
488
- // of it.
472
+ // So an ask that arrives too early is held rather than run: the user asked for
473
+ // something this deck can genuinely give a moment later, and refusing outright
474
+ // would put back the same silence in a politer form. BOOT_RESTART_MS is the
475
+ // outer bound, for the reason UPGRADE_ANSWER_MS above is one — and since #483
476
+ // moved the listen in front of the report, it is a bound that gets used: a boot
477
+ // waiting out a real `uv tool install` is minutes long, and the restart is the
478
+ // right answer to it rather than a casualty of it. Ten seconds in, the ask is
479
+ // run; the respawn skips the report entirely and is up in about a second.
489
480
  let booted = false;
490
481
  let heldRestart = null;
491
482
  let bootTimer = null;
@@ -626,18 +617,69 @@ let server = null;
626
617
  let discovery = null;
627
618
  let discoveryFile = null;
628
619
 
620
+ // The listen, begun HERE and awaited below the startup report rather than after
621
+ // it. The report is a narration; the port is the product, and it was queued
622
+ // behind three tool probes for no reason but the order these two statements
623
+ // were written in (#483).
624
+ //
625
+ // What that cost: `reportStartup` awaits `ensureCswap`, which on a machine with
626
+ // no Python tooling runs a real `uv tool install` under a 180-second timeout. So
627
+ // the deck printed its rows and then refused every connection until that install
628
+ // was over — on a first run, which is the one boot a new user judges the tool by.
629
+ // #476 stopped that job blocking the event loop; the socket was shut either way,
630
+ // because the bind had not been attempted yet.
631
+ //
632
+ // Nothing in the report has to finish before the socket opens, and the window
633
+ // this opens is narrow on purpose — the discovery file and the browser are both
634
+ // still written after the report, so the only callers who can arrive inside it
635
+ // are a tab left open by an earlier deck and the user's own curl:
636
+ //
637
+ // • the event log is replayed and the sequence counter primed INSIDE
638
+ // startServer, before it binds — so /events and /api/events answer from a
639
+ // full buffer from the first connection, not a growing one.
640
+ // • the hook install can only make hooks fire; a hook that fires early finds
641
+ // no discovery file and posts nowhere, exactly as it does today.
642
+ // • claude-swap: cswapBin memoizes only a lookup that WORKED, so a probe
643
+ // landing mid-install caches nothing and the next one asks again — see
644
+ // resetCswapBin's note. The accounts panel can report the tool missing for
645
+ // the second it is missing, and answers properly the moment it is not.
646
+ // • ccusage: getRunner awaits the very `_installing` promise primeCcusage
647
+ // started, so a request in this window joins that install rather than
648
+ // racing a second one.
649
+ //
650
+ // Settled into a tagged result rather than left bare, for the reason every job
651
+ // in startupWork carries its own handler: this promise now lives across the
652
+ // whole report, and a bind that fails in there with nothing attached to it is an
653
+ // unhandledRejection — which Node answers by killing the process over a port it
654
+ // could have named.
629
655
  const starting = startServer({
630
656
  port, persist, workspace, codex: wantCodex, claude: wantClaude,
631
657
  // Withheld when nothing is supervising us: without a parent, exiting is just
632
658
  // exiting, and /api/restart answers 501 so the UI hides the control.
633
659
  onRestart: SUPERVISED ? requestRestart : null,
634
- });
635
- server = await (RESPAWN ? starting : step(`starting server${G.ellipsis}`, starting)).catch(err => {
660
+ }).then(s => ({ ok: true, s }), err => ({ ok: false, err }));
661
+
662
+ // Once-per-session setup — hook install, tool probes, registry lookups, and the
663
+ // banner it runs underneath. A respawn is the same session continuing, so it
664
+ // skips the lot and prints one line instead. This is the difference between a
665
+ // restart that feels instant and one that makes you wonder whether it worked.
666
+ if (!RESPAWN) {
667
+ const jobs = startupWork();
668
+ await printBanner();
669
+ await reportStartup(jobs);
670
+ }
671
+
672
+ // Usually settled long ago by the time we get here, which is the point: `step`
673
+ // paints nothing for a promise that has already resolved, so the spinner this
674
+ // used to show is simply gone from the boots that were slow enough to need one.
675
+ const bound = await (RESPAWN ? starting : step(`starting server${G.ellipsis}`, starting));
676
+ if (!bound.ok) {
636
677
  // stderr, not a row: a deck that could not bind is not a status line, and
637
678
  // whatever launched it reads this stream.
638
- console.error(`${PRODUCT}: server failed: ${err.message}`);
679
+ console.error(`${PRODUCT}: server failed: ${bound.err.message}`);
639
680
  process.exit(1);
640
- });
681
+ }
682
+ server = bound.s;
641
683
  const addr = server.address();
642
684
  const realPort = typeof addr === "object" && addr ? addr.port : port;
643
685
  const url = `http://127.0.0.1:${realPort}`;
@@ -749,13 +791,13 @@ runHeldRestart();
749
791
  * A declaration, not the `const` arrow this was for eight months.
750
792
  *
751
793
  * The difference is the whole of #448: a const is in its temporal dead zone
752
- * until the line declaring it runs, and every line above — the port report, the
753
- * discovery file, the browser spawn — executes with the server already
754
- * accepting connections. A restart arriving in that window called this and got
755
- * `ReferenceError: Cannot access 'shutdown' before initialization`, and the
756
- * latch it had already set is what made that permanent. A declaration is
757
- * hoisted, so from the first instruction of this module there is a function
758
- * here to call.
794
+ * until the line declaring it runs, and every line above — the startup report,
795
+ * the port report, the discovery file, the browser spawn — executes with the
796
+ * server already accepting connections. A restart arriving in that window
797
+ * called this and got `ReferenceError: Cannot access 'shutdown' before
798
+ * initialization`, and the latch it had already set is what made that
799
+ * permanent. A declaration is hoisted, so from the first instruction of this
800
+ * module there is a function here to call.
759
801
  *
760
802
  * Hoisting alone would only have moved the fault one line down, onto `server`,
761
803
  * `discovery` and `discoveryFile` — which is why those are `let … = null` above