agent-dag 1.33.22 → 1.33.24

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-BC3e-3-4.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-Bt8oQ4sT.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.22",
3
+ "version": "1.33.24",
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": {
@@ -166,20 +166,35 @@ async function safePythons() {
166
166
  * any `pip install --user pipx` where ~/.local/bin was never added to PATH. The
167
167
  * two-entry version of this list reported "needs uv or pipx" to people who had
168
168
  * pipx installed, which is the kind of wrong answer that stops someone looking.
169
+ *
170
+ * Every entry carries its UPGRADE command line as well as its install one. The
171
+ * upgrade used to be re-derived from the `via` label instead, and any label that
172
+ * derivation did not recognise fell through to `-m pipx upgrade` — so the
173
+ * bundled uv, which is the only installer present on a machine that had neither
174
+ * uv nor pipx nor a usable python, was asked to run a pipx command it rejects.
169
175
  */
170
176
  async function installers() {
171
177
  const out = [
172
- { cmd: "uv", probe: ["--version"], args: ["tool", "install", "claude-swap"], via: "uv" },
173
- { cmd: "pipx", probe: ["--version"], args: ["install", "claude-swap"], via: "pipx" },
178
+ { cmd: "uv", probe: ["--version"], args: ["tool", "install", "claude-swap"], upgrade: ["tool", "upgrade", "claude-swap"], via: "uv" },
179
+ { cmd: "pipx", probe: ["--version"], args: ["install", "claude-swap"], upgrade: ["upgrade", "claude-swap"], via: "pipx" },
174
180
  ];
175
181
  // A uv fetched on an earlier run counts as installed tooling from here on.
176
182
  const own = existingBootstrappedUv();
177
- if (own) out.push({ cmd: own, probe: ["--version"], args: ["tool", "install", "claude-swap"], via: "uv (bundled)" });
183
+ if (own) {
184
+ out.push({
185
+ cmd: own,
186
+ probe: ["--version"],
187
+ args: ["tool", "install", "claude-swap"],
188
+ upgrade: ["tool", "upgrade", "claude-swap"],
189
+ via: "uv (bundled)",
190
+ });
191
+ }
178
192
  for (const py of await safePythons()) {
179
193
  out.push({
180
194
  cmd: py,
181
195
  probe: ["-m", "pipx", "--version"],
182
196
  args: ["-m", "pipx", "install", "claude-swap"],
197
+ upgrade: ["-m", "pipx", "upgrade", "claude-swap"],
183
198
  via: `${py} -m pipx`,
184
199
  });
185
200
  }
@@ -270,21 +285,19 @@ function isOlder(a, b) {
270
285
  * Detached and unawaited: an upgrade resolves a Python environment and can
271
286
  * take tens of seconds, which is not a thing to put in front of the server
272
287
  * starting. The running copy keeps working; the new one is there next launch.
288
+ *
289
+ * The command line comes from the installer entry rather than from its label:
290
+ * runDetached captures nothing, so an upgrade aimed at the wrong tool fails
291
+ * where nobody can see it while the caller still reports "upgrading".
273
292
  */
274
- function upgradeInBackground(found) {
275
- const { cmd, via } = found;
276
- const args = via === "uv"
277
- ? ["tool", "upgrade", "claude-swap"]
278
- : via === "pipx"
279
- ? ["upgrade", "claude-swap"]
280
- : ["-m", "pipx", "upgrade", "claude-swap"]; // python -m pipx
281
- runDetached(cmd, args);
293
+ function upgradeInBackground({ cmd, upgrade }) {
294
+ runDetached(cmd, upgrade);
282
295
  }
283
296
 
284
297
  /** Whichever Python tool installer is available, or null. */
285
298
  async function findInstaller() {
286
- for (const { cmd, probe, via } of await installers()) {
287
- if ((await run(cmd, probe, { timeout: 8_000 })).ok) return { cmd, via };
299
+ for (const { cmd, probe, upgrade, via } of await installers()) {
300
+ if ((await run(cmd, probe, { timeout: 8_000 })).ok) return { cmd, upgrade, via };
288
301
  }
289
302
  return null;
290
303
  }
@@ -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);