agent-dag 1.33.49 → 1.33.51
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-O75ELHTf.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-CF2yG9oG.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.51",
|
|
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": {
|
package/src/server/index.mjs
CHANGED
|
@@ -898,9 +898,18 @@ async function codexScanOnce(firstRun) {
|
|
|
898
898
|
}
|
|
899
899
|
}
|
|
900
900
|
|
|
901
|
-
function startCodexWatcher(workspace) {
|
|
901
|
+
export function startCodexWatcher(workspace) {
|
|
902
902
|
codexWorkspace = workspace ?? "";
|
|
903
|
-
|
|
903
|
+
// Deliberately not gated on CODEX_SESSIONS_DIR existing. `codex login`
|
|
904
|
+
// creates ~/.codex, but sessions/ only appears when the first session
|
|
905
|
+
// starts — and rollout tailing is the only Codex capture path there is, so
|
|
906
|
+
// bailing out here meant a fresh install had to restart the deck before any
|
|
907
|
+
// Codex agent ever showed up, while the banner claimed to be watching.
|
|
908
|
+
// listRecentCodexRollouts returns [] while the directory is missing, so the
|
|
909
|
+
// poll below is a cheap no-op that doubles as the existence re-check. A
|
|
910
|
+
// filesystem watch is no help: fs.watch on a missing path throws, and
|
|
911
|
+
// watching the parent recursively is macOS/Windows-only.
|
|
912
|
+
//
|
|
904
913
|
// Initial catalog: create roots for in-progress sessions, skip their
|
|
905
914
|
// history, then poll for new lines.
|
|
906
915
|
codexScanOnce(true).catch(() => {});
|
|
@@ -909,6 +918,12 @@ function startCodexWatcher(workspace) {
|
|
|
909
918
|
return codexWatchTimer;
|
|
910
919
|
}
|
|
911
920
|
|
|
921
|
+
/** Envelopes newer than `seq` from the ring buffer, oldest first. */
|
|
922
|
+
export function eventsSince(seq) {
|
|
923
|
+
const after = Number(seq) || 0;
|
|
924
|
+
return events.filter(e => e.seq > after);
|
|
925
|
+
}
|
|
926
|
+
|
|
912
927
|
function pushEvent(raw, source, opts = {}) {
|
|
913
928
|
// Synchronous enrichment: if we already know this session's model, stamp
|
|
914
929
|
// it on the payload so the client's recursive scanner picks it up.
|
|
@@ -1513,8 +1528,7 @@ export async function startServer({ port = 4317, host = "127.0.0.1", persist = n
|
|
|
1513
1528
|
if (req.method === "POST" && url.pathname === "/api/cswap-auto") return guard(handleCswapAutoAction(req, res), res);
|
|
1514
1529
|
|
|
1515
1530
|
if (req.method === "GET" && url.pathname === "/api/events") {
|
|
1516
|
-
|
|
1517
|
-
return send(res, 200, events.filter(e => e.seq > since));
|
|
1531
|
+
return send(res, 200, eventsSince(url.searchParams.get("since") ?? 0));
|
|
1518
1532
|
}
|
|
1519
1533
|
|
|
1520
1534
|
// POST /api/clear — wipe in-memory buffer + persistence file (UI reset)
|
|
@@ -45,12 +45,26 @@ const CHECK_MS = 3600_000;
|
|
|
45
45
|
// enough that a full npm outage costs a dozen ~20-byte requests an hour.
|
|
46
46
|
const RETRY_MS = 300_000;
|
|
47
47
|
const FETCH_TIMEOUT_MS = 6_000;
|
|
48
|
-
// A third marker
|
|
48
|
+
// A third marker family: ccusage owns ~/.agents-deck/ccusage/.last-update-check
|
|
49
49
|
// and cswap owns ~/.agents-deck/.cswap-update-check. Sharing one would make the
|
|
50
50
|
// three features fight over a single daily slot.
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const MARKER_DIR = join(homedir(), ".agents-deck");
|
|
52
|
+
// The name this deck was published under is part of the marker's identity.
|
|
53
|
+
//
|
|
54
|
+
// A single ~/.agents-deck/.self-update-check was shared by every deck on the
|
|
55
|
+
// machine, so whichever one asked npm first pinned the answer for all of them
|
|
56
|
+
// until the window expired — including a freshly started `npx ccdeck` that had
|
|
57
|
+
// never written it, and including decks running a DIFFERENT package where the
|
|
58
|
+
// cached version means nothing. Reported with four decks alive at once: three
|
|
59
|
+
// served the same `checkedAt` to the millisecond and none of them showed the
|
|
60
|
+
// seven releases that had shipped meanwhile. One file per package name means
|
|
61
|
+
// `agents-deck`, `ccdeck` and `agent-dag` decks stop silencing each other; the
|
|
62
|
+
// old shared path stays here only to be inherited from once.
|
|
63
|
+
const LEGACY_MARKER = join(MARKER_DIR, ".self-update-check");
|
|
64
|
+
|
|
65
|
+
// Dedupe concurrent /api/version calls into one fetch, per package name — two
|
|
66
|
+
// names are two different questions and must not be answered with one answer.
|
|
67
|
+
const _inflight = new Map();
|
|
54
68
|
|
|
55
69
|
// ── version comparison ───────────────────────────────────────────────────────
|
|
56
70
|
|
|
@@ -186,18 +200,44 @@ async function fetchLatest(name) {
|
|
|
186
200
|
}
|
|
187
201
|
}
|
|
188
202
|
|
|
203
|
+
/** Marker file name for a package: `ccdeck` → `.self-update-check-ccdeck`.
|
|
204
|
+
*
|
|
205
|
+
* Pure, and deliberately strict about what reaches the filesystem — a package
|
|
206
|
+
* name may be scoped (`@scope/pkg`), and `/`, `\` and `:` are either a path
|
|
207
|
+
* separator or outright illegal in a Windows file name. Everything outside
|
|
208
|
+
* `[a-z0-9._-]` collapses to `-`, the scope's leading `@` is dropped so the
|
|
209
|
+
* common case reads plainly, and the tail is trimmed so a pathological name
|
|
210
|
+
* cannot produce a path the OS refuses. An unusable name falls back to the
|
|
211
|
+
* default rather than to the shared file this fix exists to get rid of. */
|
|
212
|
+
export function markerFileName(name = "agents-deck") {
|
|
213
|
+
const raw = typeof name === "string" ? name.trim().toLowerCase() : "";
|
|
214
|
+
const safe = raw
|
|
215
|
+
.replace(/^@/, "")
|
|
216
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
217
|
+
.slice(0, 64)
|
|
218
|
+
// Trimmed after the slice, and at both ends: Windows silently drops a
|
|
219
|
+
// trailing dot from a file name, so a name that ends in one would write to
|
|
220
|
+
// a path that is not the path we would later read.
|
|
221
|
+
.replace(/^[-.]+|[-.]+$/g, "");
|
|
222
|
+
return `.self-update-check-${safe || "agents-deck"}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function markerPath(name) {
|
|
226
|
+
return join(MARKER_DIR, markerFileName(name));
|
|
227
|
+
}
|
|
228
|
+
|
|
189
229
|
// The marker carries the answer, not just the timestamp. The existing markers
|
|
190
|
-
// store only an mtime, which means a restart inside the
|
|
191
|
-
//
|
|
230
|
+
// store only an mtime, which means a restart inside the window forgets what npm
|
|
231
|
+
// said and shows nothing until the window expires.
|
|
192
232
|
//
|
|
193
233
|
// It carries the outcome too. `at` is when npm last ANSWERED and `version` is
|
|
194
234
|
// what it said; `failedAt` is when the last attempt failed, and is null the
|
|
195
235
|
// moment one succeeds. Keeping them apart is what lets the deck say "checked
|
|
196
236
|
// 3m ago" and "could not reach npm" as the different things they are, instead
|
|
197
237
|
// of reporting a timeout as a fresh, up-to-date check.
|
|
198
|
-
function
|
|
238
|
+
function readMarkerFile(path) {
|
|
199
239
|
try {
|
|
200
|
-
const m = JSON.parse(readFileSync(
|
|
240
|
+
const m = JSON.parse(readFileSync(path, "utf8"));
|
|
201
241
|
// Either half is enough to be worth keeping: a marker written by a first
|
|
202
242
|
// attempt that failed has no `at` yet, and markers written before
|
|
203
243
|
// `failedAt` existed have no `failedAt` at all.
|
|
@@ -206,10 +246,26 @@ function readMarker() {
|
|
|
206
246
|
return null;
|
|
207
247
|
}
|
|
208
248
|
}
|
|
209
|
-
|
|
249
|
+
|
|
250
|
+
// The unsuffixed marker every earlier deck wrote, read at most once per name
|
|
251
|
+
// per process: upgrading to this version should not throw away an answer npm
|
|
252
|
+
// already gave. It is consulted only while the per-name file is still missing,
|
|
253
|
+
// so the shared file never goes back to being in charge.
|
|
254
|
+
const _legacy = new Map();
|
|
255
|
+
function legacyMarker(name) {
|
|
256
|
+
const key = markerFileName(name);
|
|
257
|
+
if (!_legacy.has(key)) _legacy.set(key, readMarkerFile(LEGACY_MARKER));
|
|
258
|
+
return _legacy.get(key);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function readMarker(name) {
|
|
262
|
+
return readMarkerFile(markerPath(name)) ?? legacyMarker(name);
|
|
263
|
+
}
|
|
264
|
+
function writeMarker(name, marker) {
|
|
265
|
+
const path = markerPath(name);
|
|
210
266
|
try {
|
|
211
|
-
mkdirSync(dirname(
|
|
212
|
-
writeFileSync(
|
|
267
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
268
|
+
writeFileSync(path, JSON.stringify(marker));
|
|
213
269
|
} catch { /* a read-only home must not break the deck */ }
|
|
214
270
|
}
|
|
215
271
|
|
|
@@ -226,11 +282,12 @@ export function nextMarker({ prev, now, ok, version }) {
|
|
|
226
282
|
return { at: prev?.at ?? null, version: prev?.version ?? null, failedAt: now };
|
|
227
283
|
}
|
|
228
284
|
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
// freshly started deck — which for `npx ccdeck` is every single run.
|
|
233
|
-
|
|
285
|
+
// Even a per-package marker is shared by every deck running that package, so
|
|
286
|
+
// one deck's check still answers for the others inside the window. Asking once
|
|
287
|
+
// per PROCESS lands the check exactly where the user expects the truth: a
|
|
288
|
+
// freshly started deck — which for `npx ccdeck` is every single run. Keyed by
|
|
289
|
+
// name, so a process that asked about one package has not asked about another.
|
|
290
|
+
const _askedThisProcess = new Set();
|
|
234
291
|
|
|
235
292
|
/**
|
|
236
293
|
* Whether to ask npm, or reuse the answer on disk. Pure, because "why did no
|
|
@@ -258,22 +315,27 @@ export function checkDue({ at, failedAt, now, first = false, force = false, ttlM
|
|
|
258
315
|
* `force` skips the window: the first call in this process, and an explicit
|
|
259
316
|
* "check now" from the UI. */
|
|
260
317
|
async function latestOnNpm(name, now, force = false) {
|
|
261
|
-
const m = readMarker();
|
|
262
|
-
const
|
|
263
|
-
|
|
318
|
+
const m = readMarker(name);
|
|
319
|
+
const key = markerFileName(name);
|
|
320
|
+
const first = !_askedThisProcess.has(key);
|
|
321
|
+
_askedThisProcess.add(key);
|
|
264
322
|
if (!checkDue({ at: m?.at, failedAt: m?.failedAt, now, first, force })) return m?.version ?? null;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
-
//
|
|
323
|
+
const pending = _inflight.get(key);
|
|
324
|
+
if (pending) return pending;
|
|
325
|
+
const run = fetchLatest(name)
|
|
326
|
+
// Record the outcome, not just the moment, and record it against THIS
|
|
327
|
+
// package: only an answer stamps `at`; a failure takes the short retry
|
|
328
|
+
// window instead of the hour, keeps the version we already knew rather
|
|
329
|
+
// than erasing it, and lands in this name's marker rather than spending
|
|
330
|
+
// another package's window on a lookup that was never about it.
|
|
270
331
|
.then(({ ok, version }) => {
|
|
271
|
-
writeMarker(nextMarker({ prev: m, now, ok, version }));
|
|
332
|
+
writeMarker(name, nextMarker({ prev: m, now, ok, version }));
|
|
272
333
|
return (ok ? version : null) ?? m?.version ?? null;
|
|
273
334
|
})
|
|
274
335
|
.catch(() => m?.version ?? null)
|
|
275
|
-
.finally(() => { _inflight
|
|
276
|
-
|
|
336
|
+
.finally(() => { _inflight.delete(key); });
|
|
337
|
+
_inflight.set(key, run);
|
|
338
|
+
return run;
|
|
277
339
|
}
|
|
278
340
|
|
|
279
341
|
// ── the notice ───────────────────────────────────────────────────────────────
|
|
@@ -448,7 +510,7 @@ export async function versionReport({ running, pkgRoot, name = "agents-deck", no
|
|
|
448
510
|
process.env.AGENTS_DECK_NO_UPDATE_CHECK === "1" ||
|
|
449
511
|
process.env.AGENTS_DECK_NO_INSTALL === "1";
|
|
450
512
|
const latest = skipRegistry ? null : await latestOnNpm(name, now, force);
|
|
451
|
-
const marker = skipRegistry ? null : readMarker();
|
|
513
|
+
const marker = skipRegistry ? null : readMarker(name);
|
|
452
514
|
const blocked = upgradeBlock(pkgRoot);
|
|
453
515
|
return {
|
|
454
516
|
name,
|