agent-dag 1.33.21 → 1.33.23

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,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-DLOmF1C9.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DTNEy5xO.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-Cpi89XJ8.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.21",
3
+ "version": "1.33.23",
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": {
@@ -37,6 +37,16 @@ const NPM_SHELL = process.platform === "win32";
37
37
  let _installing = null; // Promise guard so concurrent calls share one install
38
38
  let _checkedThisRun = false; // only kick the daily check once per process boot
39
39
 
40
+ // AGENTS_DECK_NO_INSTALL=1 is documented as "never install or update
41
+ // claude-swap / ccusage, and never ask npm about releases", so it has to hold
42
+ // on the lazy path too — opening the usage-history modal must not be a way to
43
+ // pull ccusage off the registry behind the user's back. Read per call rather
44
+ // than once at import, because the module is imported lazily and a test (or an
45
+ // embedder) may set it after load.
46
+ function installsDisabled() {
47
+ return process.env.AGENTS_DECK_NO_INSTALL === "1";
48
+ }
49
+
40
50
  // ── managed install ─────────────────────────────────────────────────────────
41
51
 
42
52
  // Absolute path to ccusage's CLI entry inside our managed install, or null if
@@ -103,6 +113,7 @@ function touchMarker() {
103
113
 
104
114
  // Non-blocking: compare installed version to npm `latest`; install if newer.
105
115
  function maybeBackgroundUpdate(installedVersion) {
116
+ if (installsDisabled()) return; // no `npm view`, no upgrade install
106
117
  if (_checkedThisRun || !updateCheckDue()) return;
107
118
  _checkedThisRun = true;
108
119
  touchMarker();
@@ -127,6 +138,13 @@ async function getRunner() {
127
138
  maybeBackgroundUpdate(resolved.version);
128
139
  return { kind: "node", entry: resolved.entry };
129
140
  }
141
+ // Nothing installed and installs are forbidden. The npx fallback is not an
142
+ // escape hatch — `npx -y ccusage@latest` downloads and runs the same package
143
+ // — so there is no runner to hand back. Fail with the reason, which the
144
+ // usage-history modal shows, instead of quietly installing.
145
+ if (installsDisabled()) {
146
+ throw new Error("ccusage is not installed, and installs are off (AGENTS_DECK_NO_INSTALL=1)");
147
+ }
130
148
  // Cold: install once (deduped across concurrent callers).
131
149
  if (!_installing) {
132
150
  _installing = (async () => { installSync("latest"); })()
@@ -232,6 +250,13 @@ export async function fetchCcusageDaily({ since, until, force = false } = {}) {
232
250
  * not hold up the server.
233
251
  */
234
252
  export function primeCcusage() {
253
+ // The CLI already skips this call under AGENTS_DECK_NO_INSTALL=1; repeating
254
+ // the check here keeps the promise a property of the module rather than of
255
+ // one caller.
256
+ if (installsDisabled()) {
257
+ const have = resolveEntry();
258
+ return have ? { state: "present", version: have.version } : { state: "unavailable" };
259
+ }
235
260
  const resolved = resolveEntry();
236
261
  if (resolved) {
237
262
  // Already installed: the daily check may still queue a background upgrade.
@@ -224,7 +224,16 @@ export function runInteractive(cmd, args, { timeout = 300_000, maxOutput = 256 <
224
224
  if (tryNext(err) && !stdout && !stderr) { child = null; return attempt(i + 1); }
225
225
  finish(-1, err);
226
226
  });
227
- proc.on("spawn", () => resolved.set(cmd, raw));
227
+ // Spawning proves a spelling exists — except a .cmd/.bat one, which is
228
+ // launched THROUGH cmd.exe. cmd.exe is always there, so it spawns just as
229
+ // happily for a batch file that is not, and only says so later by exiting
230
+ // non-zero with "is not recognized". Caching at spawn time therefore
231
+ // remembered a spelling that never existed, and since `candidates` then
232
+ // offers only the remembered one, the tool stayed unrunnable — with the
233
+ // false message "not on PATH" — even after it was installed. A batch
234
+ // spelling is confirmed by the clean exit below instead, which is the rule
235
+ // `run` already applies with `if (!err)`.
236
+ if (!isBatch(raw)) proc.on("spawn", () => resolved.set(cmd, raw));
228
237
  // Capped so a runaway child cannot grow the heap without bound; the tail is
229
238
  // what carries the error, so the head is what gets dropped.
230
239
  const keep = (buf, text) => (buf + text).slice(-maxOutput);
@@ -242,6 +251,9 @@ export function runInteractive(cmd, args, { timeout = 300_000, maxOutput = 256 <
242
251
  }
243
252
  return finish(-1, { code: "ENOENT" });
244
253
  }
254
+ // Ran to a clean exit, so this spelling is real — the only confirmation a
255
+ // batch one ever gets.
256
+ if (code === 0 && !killed && !timedOut) resolved.set(cmd, raw);
245
257
  finish(code ?? -1, null);
246
258
  });
247
259
  };
@@ -281,7 +293,11 @@ export function runDetached(cmd, args) {
281
293
  try {
282
294
  const child = spawn(file, argv, { stdio: "ignore", shell: false, windowsHide: true, ...opts });
283
295
  child.on("error", (err) => { if (tryNext(err)) attempt(i + 1); });
284
- child.on("spawn", () => resolved.set(cmd, raw));
296
+ // Same trap as above: a batch spelling is spawned through cmd.exe, which
297
+ // succeeds whether or not the batch file is there, so only a clean exit
298
+ // proves this one is worth remembering.
299
+ if (isBatch(raw)) child.on("exit", (code) => { if (code === 0) resolved.set(cmd, raw); });
300
+ else child.on("spawn", () => resolved.set(cmd, raw));
285
301
  child.unref?.();
286
302
  } catch {
287
303
  attempt(i + 1);