agent-dag 1.36.3 → 1.37.0
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,
|
|
476
|
-
// and its first fsynced write, and on a cold start the
|
|
477
|
-
// landing in that window used to reach `shutdown`
|
|
478
|
-
// was initialised and die of a ReferenceError,
|
|
479
|
-
// above, with nothing left to clear it — after
|
|
480
|
-
// tab was answered "ok" and did nothing, for the
|
|
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
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
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
|
-
|
|
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
|
|
753
|
-
* discovery file, the browser spawn — executes with the
|
|
754
|
-
* accepting connections. A restart arriving in that window
|
|
755
|
-
* `ReferenceError: Cannot access 'shutdown' before
|
|
756
|
-
* latch it had already set is what made that
|
|
757
|
-
* hoisted, so from the first instruction of this
|
|
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
|