agent-dag 1.33.4 → 1.33.6

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.
@@ -5,8 +5,8 @@
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-CUIo58CG.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-hRjhJVfb.css">
8
+ <script type="module" crossorigin src="/assets/index-z0Jp5RyB.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Cezbph7B.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-dag",
3
- "version": "1.33.4",
3
+ "version": "1.33.6",
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": {
@@ -407,10 +407,12 @@ export async function importAccount(blob) {
407
407
  const slot = newSlot(before, after);
408
408
  invalidateClaudeAccountsCache();
409
409
  if (slot != null) runDetached(await cswapBin(), ["list"]);
410
+ // Who arrived, so the dialog can name them instead of saying "an account".
411
+ const email = slot != null ? (after.emails[slot] || null) : null;
410
412
  // No new slot is not an error: without --force, cswap skips an account it
411
413
  // already holds. Saying which happened is the difference between "it
412
414
  // worked" and "why is nothing different".
413
- return { ok: true, added: slot != null, num: slot, output: firstUseful(r.stdout) };
415
+ return { ok: true, added: slot != null, num: slot, email, output: firstUseful(r.stdout) };
414
416
  });
415
417
  }
416
418
 
@@ -1024,11 +1024,15 @@ function handleSse(req, res) {
1024
1024
  // no file to read — so the deck must not offer to do it.
1025
1025
  let _canRestart = false;
1026
1026
 
1027
- async function handleVersion(_req, res) {
1027
+ async function handleVersion(req, res) {
1028
1028
  const { versionReport } = await import(
1029
1029
  pathToFileURL(join(PKG_ROOT, "src/server/self-update.mjs")).href
1030
1030
  );
1031
- const report = await versionReport({ running: RUNNING_VERSION, pkgRoot: PKG_ROOT });
1031
+ // ?refresh=1 asks npm now rather than reusing the cached answer — what the
1032
+ // version chip does when clicked, for the user who has just published or is
1033
+ // wondering whether the check is working at all.
1034
+ const force = new URL(req.url, "http://localhost").searchParams.get("refresh") === "1";
1035
+ const report = await versionReport({ running: RUNNING_VERSION, pkgRoot: PKG_ROOT, force });
1032
1036
  send(res, 200, { ...report, canRestart: _canRestart });
1033
1037
  }
1034
1038
 
@@ -26,7 +26,16 @@ import { spawn } from "node:child_process";
26
26
  import { homedir } from "node:os";
27
27
  import { dirname, join, resolve } from "node:path";
28
28
 
29
- const CHECK_MS = 24 * 3600_000; // same daily cadence as the ccusage/cswap checks
29
+ // Once an hour, not once a day.
30
+ //
31
+ // The daily cadence was copied from the ccusage and cswap checks, where it is
32
+ // right: those answer "is a different tool out of date", and nobody is waiting
33
+ // on it. This one answers "is the thing you are looking at out of date", and a
34
+ // day is long enough that a deck started shortly before a release shows nothing
35
+ // at all until tomorrow — reported from a machine running `npx ccdeck`, which
36
+ // is the case where it bites hardest, since npx runs are short-lived and each
37
+ // one inherits the same stale marker. The request is ~20 bytes.
38
+ const CHECK_MS = 3600_000;
30
39
  const FETCH_TIMEOUT_MS = 6_000;
31
40
  // A third marker path: ccusage owns ~/.agents-deck/ccusage/.last-update-check
32
41
  // and cswap owns ~/.agents-deck/.cswap-update-check. Sharing one would make the
@@ -181,11 +190,34 @@ function writeMarker(at, version) {
181
190
  } catch { /* a read-only home must not break the deck */ }
182
191
  }
183
192
 
193
+ // The marker is shared by every deck on the machine, so one deck's check
194
+ // silences the others for the rest of the window. Asking once per PROCESS fixes
195
+ // that, and it lands the check exactly where the user expects the truth: a
196
+ // freshly started deck — which for `npx ccdeck` is every single run.
197
+ let _askedThisProcess = false;
198
+
199
+ /**
200
+ * Whether to ask npm, or reuse the answer on disk. Pure, because "why did no
201
+ * banner appear" is the question this feature gets asked, and the rule behind
202
+ * it should be readable in one place.
203
+ */
204
+ export function checkDue({ at, now, first = false, force = false, ttlMs = CHECK_MS }) {
205
+ if (force || first) return true; // explicit ask, or this process's first
206
+ if (typeof at !== "number") return true; // never checked
207
+ if (at > now) return true; // marker from the future: a moved clock
208
+ return now - at >= ttlMs;
209
+ }
210
+
184
211
  /** Last known npm `latest`, refreshed at most once per CHECK_MS. Returns the
185
- * cached answer immediately when the window has not elapsed. */
186
- async function latestOnNpm(name, now) {
212
+ * cached answer immediately when the window has not elapsed.
213
+ *
214
+ * `force` skips the window: the first call in this process, and an explicit
215
+ * "check now" from the UI. */
216
+ async function latestOnNpm(name, now, force = false) {
187
217
  const m = readMarker();
188
- if (m && now - m.at < CHECK_MS) return m.version ?? null;
218
+ const first = !_askedThisProcess;
219
+ _askedThisProcess = true;
220
+ if (!checkDue({ at: m?.at, now, first, force })) return m?.version ?? null;
189
221
  if (_inflight) return _inflight;
190
222
  _inflight = fetchLatest(name)
191
223
  // Stamp before deciding what to keep: a failed lookup must burn the day's
@@ -350,7 +382,7 @@ export function lastMeaningfulLine(text) {
350
382
  /** Full answer for GET /api/version. Never throws, never blocks on the network
351
383
  * for longer than FETCH_TIMEOUT_MS, and answers the local half even when the
352
384
  * registry is unreachable. */
353
- export async function versionReport({ running, pkgRoot, name = "agents-deck", now = Date.now() }) {
385
+ export async function versionReport({ running, pkgRoot, name = "agents-deck", now = Date.now(), force = false }) {
354
386
  const installed = installedVersion(pkgRoot);
355
387
  // Only an explicit opt-out silences the registry.
356
388
  //
@@ -365,13 +397,17 @@ export async function versionReport({ running, pkgRoot, name = "agents-deck", no
365
397
  const skipRegistry =
366
398
  process.env.AGENTS_DECK_NO_UPDATE_CHECK === "1" ||
367
399
  process.env.AGENTS_DECK_NO_INSTALL === "1";
368
- const latest = skipRegistry ? null : await latestOnNpm(name, now);
400
+ const latest = skipRegistry ? null : await latestOnNpm(name, now, force);
369
401
  const blocked = upgradeBlock(pkgRoot);
370
402
  return {
371
403
  name,
372
404
  running: running ?? null,
373
405
  installed,
374
406
  latest,
407
+ // When npm was last asked, so the UI can say it rather than leaving the
408
+ // user to wonder whether the check runs at all.
409
+ checkedAt: skipRegistry ? null : (readMarker()?.at ?? null),
410
+ checkDisabled: skipRegistry,
375
411
  notice: pickNotice({ running, installed, latest }),
376
412
  command: upgradeCommand(pkgRoot, name),
377
413
  // Why an in-app `npm i -g` is refused, when it is — so the UI can say so