agent-dag 1.33.86 → 1.33.87
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/dist/web/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>agents-deck</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-qV8K11wl.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-dwRx-2y0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.87",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch parallel subagents fork, call tools, and return on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// it by name and only where it can actually work: a global install, on a
|
|
22
22
|
// directory we can write, outside a git checkout and outside an npx cache.
|
|
23
23
|
// Everywhere else this stays what it has always been — a printed command.
|
|
24
|
-
import { accessSync, constants as FS, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { accessSync, constants as FS, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
25
25
|
import { spawn } from "node:child_process";
|
|
26
26
|
import { homedir } from "node:os";
|
|
27
27
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -533,22 +533,68 @@ export function upgradeBlock(pkgRoot) {
|
|
|
533
533
|
//
|
|
534
534
|
// A file is the only channel between the two processes: the supervisor writes
|
|
535
535
|
// one when the relaunch fails, and the worker it starts instead reads it here.
|
|
536
|
-
// Same directory
|
|
537
|
-
// same reason
|
|
538
|
-
//
|
|
536
|
+
// Same directory as the update markers, and named after the package for the
|
|
537
|
+
// same reason they are.
|
|
538
|
+
//
|
|
539
|
+
// The package name alone was not enough, and one release was enough to show it.
|
|
540
|
+
// Two `npx ccdeck` decks resolve into the same content-addressed _npx directory,
|
|
541
|
+
// so they run the same package at the same version out of the same home: when
|
|
542
|
+
// one user's upgrade failed, the other deck — which had never asked for
|
|
543
|
+
// anything — read that note as its own, reported `upgrade: {state:"failed"}`
|
|
544
|
+
// and labelled its first ever click "Retry update". The version-staleness rule
|
|
545
|
+
// below cannot catch it, because both decks are the same version.
|
|
546
|
+
//
|
|
547
|
+
// So the name carries WHOSE failure it is as well as which package's: the pid
|
|
548
|
+
// of the supervisor that wrote it. That is unique among the decks alive on the
|
|
549
|
+
// machine, and it crosses the process boundary on its own — the worker that
|
|
550
|
+
// reads the note is a child the supervisor spawns after the failure, and
|
|
551
|
+
// inherits the pid through AGENTS_DECK_SUPERVISOR_PID. A worker with no
|
|
552
|
+
// supervisor to answer for reads nothing rather than falling back to a shared
|
|
553
|
+
// file, which is the bug this whole naming exists to avoid.
|
|
554
|
+
const NOTE_PREFIX = ".restart-failed-";
|
|
555
|
+
|
|
556
|
+
/** Which supervisor a note belongs to, as the file name may spell it. Digits
|
|
557
|
+
* and nothing else: legal on every filesystem, and readable back as the pid
|
|
558
|
+
* the sweep below asks about. Anything else is refused rather than scrubbed
|
|
559
|
+
* into digits, since two keys must never collapse into one file name. */
|
|
560
|
+
function safeOwner(key) {
|
|
561
|
+
const raw = String(key ?? "").trim();
|
|
562
|
+
return /^\d{1,12}$/.test(raw) && Number(raw) > 0 ? raw : null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** This process's own key, set by the supervisor on itself and inherited by
|
|
566
|
+
* every worker it launches. */
|
|
567
|
+
export function restartFailureKey(env = process.env) {
|
|
568
|
+
return safeOwner(env?.AGENTS_DECK_SUPERVISOR_PID);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** Called once by the supervisor, on its own environment, which every worker it
|
|
572
|
+
* spawns then inherits. Assigned rather than defaulted: a deck launched by
|
|
573
|
+
* another deck's npx relaunch inherits that supervisor's key and must answer
|
|
574
|
+
* for itself, not for the parent whose upgrade it is the result of. */
|
|
575
|
+
export function claimRestartFailureKey(env = process.env, pid = process.pid) {
|
|
576
|
+
env.AGENTS_DECK_SUPERVISOR_PID = String(pid);
|
|
577
|
+
return safeOwner(pid);
|
|
578
|
+
}
|
|
539
579
|
|
|
540
|
-
|
|
541
|
-
|
|
580
|
+
/** `ccdeck` under supervisor 4821 → `.restart-failed-ccdeck-4821`, or null when
|
|
581
|
+
* there is no supervisor, which is not a deck any note can be about. */
|
|
582
|
+
export function restartFailureFileName(name = "agents-deck", key = restartFailureKey()) {
|
|
583
|
+
const owner = safeOwner(key);
|
|
584
|
+
return owner ? `${NOTE_PREFIX}${safeNamePart(name)}-${owner}` : null;
|
|
542
585
|
}
|
|
543
586
|
|
|
544
|
-
function restartFailurePath(name) {
|
|
545
|
-
|
|
587
|
+
function restartFailurePath(name, key) {
|
|
588
|
+
const file = restartFailureFileName(name, key);
|
|
589
|
+
return file ? join(MARKER_DIR, file) : null;
|
|
546
590
|
}
|
|
547
591
|
|
|
548
592
|
/** Called by the supervisor, in the moment between "npx failed" and "relaunch
|
|
549
593
|
* the old copy". Best-effort: a read-only home costs the report, not the deck. */
|
|
550
|
-
export function recordRestartFailure({ name = "agents-deck", command = null, error = null, version = null, at = Date.now() } = {}) {
|
|
551
|
-
const
|
|
594
|
+
export function recordRestartFailure({ name = "agents-deck", command = null, error = null, version = null, at = Date.now(), key = restartFailureKey() } = {}) {
|
|
595
|
+
const file = restartFailureFileName(name, key);
|
|
596
|
+
if (!file) return;
|
|
597
|
+
const path = join(MARKER_DIR, file);
|
|
552
598
|
try {
|
|
553
599
|
mkdirSync(dirname(path), { recursive: true });
|
|
554
600
|
writeFileSync(path, JSON.stringify({
|
|
@@ -558,27 +604,59 @@ export function recordRestartFailure({ name = "agents-deck", command = null, err
|
|
|
558
604
|
version,
|
|
559
605
|
at,
|
|
560
606
|
}));
|
|
607
|
+
sweepOrphanedNotes(file);
|
|
561
608
|
} catch { /* ignore */ }
|
|
562
609
|
}
|
|
563
610
|
|
|
564
611
|
/** Called before each attempt, so a retry is answered by its own outcome rather
|
|
565
612
|
* than by the last one's. */
|
|
566
|
-
export function clearRestartFailure(name = "agents-deck") {
|
|
567
|
-
|
|
613
|
+
export function clearRestartFailure(name = "agents-deck", key = restartFailureKey()) {
|
|
614
|
+
const path = restartFailurePath(name, key);
|
|
615
|
+
if (!path) return;
|
|
616
|
+
try { rmSync(path, { force: true }); } catch { /* ignore */ }
|
|
568
617
|
}
|
|
569
618
|
|
|
570
|
-
export function readRestartFailure(name = "agents-deck") {
|
|
619
|
+
export function readRestartFailure(name = "agents-deck", key = restartFailureKey()) {
|
|
620
|
+
const path = restartFailurePath(name, key);
|
|
621
|
+
if (!path) return null;
|
|
571
622
|
try {
|
|
572
|
-
const m = JSON.parse(readFileSync(
|
|
623
|
+
const m = JSON.parse(readFileSync(path, "utf8"));
|
|
573
624
|
return m && typeof m === "object" ? m : null;
|
|
574
625
|
} catch {
|
|
575
626
|
return null;
|
|
576
627
|
}
|
|
577
628
|
}
|
|
578
629
|
|
|
630
|
+
// A note is named after a process, so it outlives its deck whenever that
|
|
631
|
+
// supervisor is killed before anyone reads the tab, and nothing would ever
|
|
632
|
+
// delete it: the retry that clears one is exactly the thing that never happened.
|
|
633
|
+
// Swept from the only path that creates notes, and only where the owner is
|
|
634
|
+
// provably gone — another deck's note is another deck's to clear. Names without
|
|
635
|
+
// a pid are the single shared file of v1.33.82, which nothing reads any more.
|
|
636
|
+
function sweepOrphanedNotes(keep) {
|
|
637
|
+
let files;
|
|
638
|
+
try { files = readdirSync(MARKER_DIR); } catch { return; }
|
|
639
|
+
for (const f of files) {
|
|
640
|
+
if (f === keep || !f.startsWith(NOTE_PREFIX)) continue;
|
|
641
|
+
const owner = Number(f.slice(f.lastIndexOf("-") + 1));
|
|
642
|
+
if (Number.isInteger(owner) && owner > 0 && processAlive(owner)) continue;
|
|
643
|
+
try { rmSync(join(MARKER_DIR, f), { force: true }); } catch { /* ignore */ }
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Signal 0 delivers nothing; it only asks whether the pid could be signalled.
|
|
648
|
+
// EPERM is someone else's process answering, which is still a process — the
|
|
649
|
+
// same test sweepStaleDiscovery makes of the discovery files.
|
|
650
|
+
function processAlive(pid) {
|
|
651
|
+
try { process.kill(pid, 0); return true; }
|
|
652
|
+
catch (e) { return e?.code === "EPERM"; }
|
|
653
|
+
}
|
|
654
|
+
|
|
579
655
|
/**
|
|
580
656
|
* The note as the version report should carry it, or null when it no longer
|
|
581
657
|
* describes this deck. Pure, because the staleness rule is the whole subtlety.
|
|
658
|
+
* WHOSE failure it is was settled by the file name; this decides only whether
|
|
659
|
+
* it is still current.
|
|
582
660
|
*
|
|
583
661
|
* The note names the version that was running when the upgrade failed. While
|
|
584
662
|
* that is still the version on disk, the failure is current: the deck really is
|
|
@@ -730,6 +808,9 @@ export async function versionReport({ running, pkgRoot, name = "agents-deck", no
|
|
|
730
808
|
// by construction, and a running one must not be reported as a past failure.
|
|
731
809
|
// The note is read whatever the registry is doing — it is a local event, not
|
|
732
810
|
// a lookup — so an offline deck still explains why its update did nothing.
|
|
811
|
+
// Only the note addressed to this deck's own supervisor is read: the decks
|
|
812
|
+
// sharing this home directory are usually the same package at the same
|
|
813
|
+
// version, and none of them may answer for another's failed upgrade.
|
|
733
814
|
const live = upgradeStatus();
|
|
734
815
|
const upgrade = live.state === "idle"
|
|
735
816
|
? (restartFailureNotice(readRestartFailure(target), installed) ?? live)
|