agent-dag 1.33.43 → 1.33.44
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-B5ak55yo.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.44",
|
|
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": {
|
|
@@ -36,6 +36,13 @@ import { dirname, join, resolve } from "node:path";
|
|
|
36
36
|
// is the case where it bites hardest, since npx runs are short-lived and each
|
|
37
37
|
// one inherits the same stale marker. The request is ~20 bytes.
|
|
38
38
|
const CHECK_MS = 3600_000;
|
|
39
|
+
// A lookup that failed is not an answer, so it must not spend the hour an
|
|
40
|
+
// answer buys. It still has to spend something: the reason to rate-limit is
|
|
41
|
+
// gone, but a registry that is down would otherwise be asked again on every
|
|
42
|
+
// single poll. Five minutes is the compromise — short enough that a network
|
|
43
|
+
// coming back is noticed while the user is still looking at the deck, long
|
|
44
|
+
// enough that a full npm outage costs a dozen ~20-byte requests an hour.
|
|
45
|
+
const RETRY_MS = 300_000;
|
|
39
46
|
const FETCH_TIMEOUT_MS = 6_000;
|
|
40
47
|
// A third marker path: ccusage owns ~/.agents-deck/ccusage/.last-update-check
|
|
41
48
|
// and cswap owns ~/.agents-deck/.cswap-update-check. Sharing one would make the
|
|
@@ -158,38 +165,66 @@ export function upgradeCommand(pkgRoot, name = "agents-deck") {
|
|
|
158
165
|
|
|
159
166
|
// The dist-tags endpoint answers with ~20 bytes ({"latest":"1.30.7"}); the full
|
|
160
167
|
// packument is >2 KB and needs parsing we have no use for.
|
|
168
|
+
//
|
|
169
|
+
// Answers `{ ok, version }` rather than a bare string, because "npm says the
|
|
170
|
+
// latest is X" and "npm did not answer" used to arrive here as the same null.
|
|
171
|
+
// `ok` is true only when the registry handed back a usable version — a
|
|
172
|
+
// timeout, a non-200 and a 200 with no `latest` in it are all failures, and
|
|
173
|
+
// the caller has to be able to tell them from an up-to-date deck.
|
|
161
174
|
async function fetchLatest(name) {
|
|
162
175
|
try {
|
|
163
176
|
const res = await fetch(`https://registry.npmjs.org/-/package/${name}/dist-tags`, {
|
|
164
177
|
headers: { accept: "application/json", "user-agent": "agents-deck" },
|
|
165
178
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
166
179
|
});
|
|
167
|
-
if (!res.ok) return null;
|
|
180
|
+
if (!res.ok) return { ok: false, version: null };
|
|
168
181
|
const v = (await res.json())?.latest;
|
|
169
|
-
return typeof v === "string" ? v : null;
|
|
182
|
+
return typeof v === "string" ? { ok: true, version: v } : { ok: false, version: null };
|
|
170
183
|
} catch {
|
|
171
|
-
return null;
|
|
184
|
+
return { ok: false, version: null };
|
|
172
185
|
}
|
|
173
186
|
}
|
|
174
187
|
|
|
175
188
|
// The marker carries the answer, not just the timestamp. The existing markers
|
|
176
189
|
// store only an mtime, which means a restart inside the 24h window forgets what
|
|
177
190
|
// npm said and shows nothing until the window expires.
|
|
191
|
+
//
|
|
192
|
+
// It carries the outcome too. `at` is when npm last ANSWERED and `version` is
|
|
193
|
+
// what it said; `failedAt` is when the last attempt failed, and is null the
|
|
194
|
+
// moment one succeeds. Keeping them apart is what lets the deck say "checked
|
|
195
|
+
// 3m ago" and "could not reach npm" as the different things they are, instead
|
|
196
|
+
// of reporting a timeout as a fresh, up-to-date check.
|
|
178
197
|
function readMarker() {
|
|
179
198
|
try {
|
|
180
199
|
const m = JSON.parse(readFileSync(MARKER, "utf8"));
|
|
181
|
-
|
|
200
|
+
// Either half is enough to be worth keeping: a marker written by a first
|
|
201
|
+
// attempt that failed has no `at` yet, and markers written before
|
|
202
|
+
// `failedAt` existed have no `failedAt` at all.
|
|
203
|
+
return (typeof m?.at === "number" || typeof m?.failedAt === "number") ? m : null;
|
|
182
204
|
} catch {
|
|
183
205
|
return null;
|
|
184
206
|
}
|
|
185
207
|
}
|
|
186
|
-
function writeMarker(
|
|
208
|
+
function writeMarker(marker) {
|
|
187
209
|
try {
|
|
188
210
|
mkdirSync(dirname(MARKER), { recursive: true });
|
|
189
|
-
writeFileSync(MARKER, JSON.stringify(
|
|
211
|
+
writeFileSync(MARKER, JSON.stringify(marker));
|
|
190
212
|
} catch { /* a read-only home must not break the deck */ }
|
|
191
213
|
}
|
|
192
214
|
|
|
215
|
+
/** What the marker should hold after an attempt. Pure, because "a failed
|
|
216
|
+
* lookup must not be recorded as a successful one" is precisely the rule this
|
|
217
|
+
* file used to get wrong, and a rule worth a bug is worth a test.
|
|
218
|
+
*
|
|
219
|
+
* A success stamps the hour and clears the failure. A failure records only
|
|
220
|
+
* itself, leaving the last real answer and the time it arrived untouched —
|
|
221
|
+
* the deck keeps showing what it knew, and stops claiming it just confirmed
|
|
222
|
+
* it. */
|
|
223
|
+
export function nextMarker({ prev, now, ok, version }) {
|
|
224
|
+
if (ok) return { at: now, version: version ?? null, failedAt: null };
|
|
225
|
+
return { at: prev?.at ?? null, version: prev?.version ?? null, failedAt: now };
|
|
226
|
+
}
|
|
227
|
+
|
|
193
228
|
// The marker is shared by every deck on the machine, so one deck's check
|
|
194
229
|
// silences the others for the rest of the window. Asking once per PROCESS fixes
|
|
195
230
|
// that, and it lands the check exactly where the user expects the truth: a
|
|
@@ -201,8 +236,16 @@ let _askedThisProcess = false;
|
|
|
201
236
|
* banner appear" is the question this feature gets asked, and the rule behind
|
|
202
237
|
* it should be readable in one place.
|
|
203
238
|
*/
|
|
204
|
-
export function checkDue({ at, now, first = false, force = false, ttlMs = CHECK_MS }) {
|
|
239
|
+
export function checkDue({ at, failedAt, now, first = false, force = false, ttlMs = CHECK_MS, retryMs = RETRY_MS }) {
|
|
205
240
|
if (force || first) return true; // explicit ask, or this process's first
|
|
241
|
+
// The last attempt failed, so there is nothing to reuse and the long window
|
|
242
|
+
// does not apply — but the short one does, or an unreachable registry turns
|
|
243
|
+
// every poll into another request. Answered first: `at` here is the older,
|
|
244
|
+
// successful check, and letting it decide would ask again immediately.
|
|
245
|
+
if (typeof failedAt === "number") {
|
|
246
|
+
if (failedAt > now) return true; // clock moved; do not wait it out
|
|
247
|
+
return now - failedAt >= retryMs;
|
|
248
|
+
}
|
|
206
249
|
if (typeof at !== "number") return true; // never checked
|
|
207
250
|
if (at > now) return true; // marker from the future: a moved clock
|
|
208
251
|
return now - at >= ttlMs;
|
|
@@ -217,13 +260,16 @@ async function latestOnNpm(name, now, force = false) {
|
|
|
217
260
|
const m = readMarker();
|
|
218
261
|
const first = !_askedThisProcess;
|
|
219
262
|
_askedThisProcess = true;
|
|
220
|
-
if (!checkDue({ at: m?.at, now, first, force })) return m?.version ?? null;
|
|
263
|
+
if (!checkDue({ at: m?.at, failedAt: m?.failedAt, now, first, force })) return m?.version ?? null;
|
|
221
264
|
if (_inflight) return _inflight;
|
|
222
265
|
_inflight = fetchLatest(name)
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
// already knew.
|
|
226
|
-
.then(
|
|
266
|
+
// Record the outcome, not just the moment. Only an answer stamps `at`; a
|
|
267
|
+
// failure takes the short retry window instead of the hour, and keeps the
|
|
268
|
+
// version we already knew rather than erasing it.
|
|
269
|
+
.then(({ ok, version }) => {
|
|
270
|
+
writeMarker(nextMarker({ prev: m, now, ok, version }));
|
|
271
|
+
return (ok ? version : null) ?? m?.version ?? null;
|
|
272
|
+
})
|
|
227
273
|
.catch(() => m?.version ?? null)
|
|
228
274
|
.finally(() => { _inflight = null; });
|
|
229
275
|
return _inflight;
|
|
@@ -398,15 +444,22 @@ export async function versionReport({ running, pkgRoot, name = "agents-deck", no
|
|
|
398
444
|
process.env.AGENTS_DECK_NO_UPDATE_CHECK === "1" ||
|
|
399
445
|
process.env.AGENTS_DECK_NO_INSTALL === "1";
|
|
400
446
|
const latest = skipRegistry ? null : await latestOnNpm(name, now, force);
|
|
447
|
+
const marker = skipRegistry ? null : readMarker();
|
|
401
448
|
const blocked = upgradeBlock(pkgRoot);
|
|
402
449
|
return {
|
|
403
450
|
name,
|
|
404
451
|
running: running ?? null,
|
|
405
452
|
installed,
|
|
406
453
|
latest,
|
|
407
|
-
// When npm
|
|
408
|
-
// user to wonder whether the check runs at all.
|
|
409
|
-
|
|
454
|
+
// When npm last ANSWERED, so the UI can say it rather than leaving the
|
|
455
|
+
// user to wonder whether the check runs at all. A lookup that failed does
|
|
456
|
+
// not move this: "checked 2 minutes ago" over an hour-old answer is the
|
|
457
|
+
// one thing this field must never say.
|
|
458
|
+
checkedAt: marker?.at ?? null,
|
|
459
|
+
// …and when it last failed, null once one succeeds. Without it, the single
|
|
460
|
+
// most common reason for a missing update button — a proxy, a flaky line,
|
|
461
|
+
// an offline machine — is indistinguishable from being up to date.
|
|
462
|
+
checkFailedAt: marker?.failedAt ?? null,
|
|
410
463
|
checkDisabled: skipRegistry,
|
|
411
464
|
notice: pickNotice({ running, installed, latest }),
|
|
412
465
|
command: upgradeCommand(pkgRoot, name),
|