agent-dag 1.33.147 → 1.33.148

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.
@@ -40,7 +40,7 @@
40
40
  document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
41
41
  })();
42
42
  </script>
43
- <script type="module" crossorigin src="/assets/index-BaqIyhG1.js"></script>
43
+ <script type="module" crossorigin src="/assets/index-BJ7WjcQT.js"></script>
44
44
  <link rel="stylesheet" crossorigin href="/assets/index-DhGa0tR_.css">
45
45
  </head>
46
46
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-dag",
3
- "version": "1.33.147",
3
+ "version": "1.33.148",
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": {
@@ -1375,14 +1375,18 @@ async function handleVersion(req, res) {
1375
1375
  //
1376
1376
  // It is deliberately not folded into `name`, which is documented as the
1377
1377
  // package an upgrade would install and is load-bearing for the update flow.
1378
- // The two answer different questions and diverge everywhere but npx: `name`
1379
- // for a global install is the published package whichever bin was typed.
1378
+ // The two answer different questions and give different answers: `name` is
1379
+ // read off the install on disk and is always known, while this one is the
1380
+ // command that was typed and is silent wherever it cannot be proven — every
1381
+ // Windows global install, and every `npm i -g ccdeck`, whose stub spawns the
1382
+ // deck by absolute path and leaves nothing in argv to read.
1380
1383
  send(res, 200, { ...report, canRestart: _canRestart, invokedAs: invokedName({ pkgRoot: PKG_ROOT }) });
1381
1384
  }
1382
1385
 
1383
- // Runs `npm i -g agents-deck@latest`, and only that: the argument vector is
1384
- // fixed inside self-update.mjs, so nothing about what gets installed comes from
1385
- // the request. Answers immediately — progress is read back from /api/version.
1386
+ // Runs `npm i -g <this deck>@latest`, and only that: the argument vector is
1387
+ // fixed inside self-update.mjs — including which of the three published names
1388
+ // it installs, which comes from the layout npm built rather than from the
1389
+ // request. Answers immediately — progress is read back from /api/version.
1386
1390
  async function handleUpgrade(_req, res) {
1387
1391
  const { startUpgrade } = await import(
1388
1392
  pathToFileURL(join(PKG_ROOT, "src/server/self-update.mjs")).href
@@ -176,6 +176,89 @@ export function npxRestartSpec(pkgRoot, name = "agents-deck") {
176
176
  return npxSpecFromMeta(meta, name);
177
177
  }
178
178
 
179
+ // ── the package that installed us ────────────────────────────────────────────
180
+ //
181
+ // Two of the three names on npm are this tarball republished; the third is not
182
+ // this tarball at all. `ccdeck` is a stub that ships nothing but bin/ and
183
+ // depends on `agents-deck`, so `npm i -g ccdeck` produces a layout no other
184
+ // install does: npm stopped hoisting a global package's dependencies in v7, so
185
+ // the deck lands at `<prefix>/lib/node_modules/ccdeck/node_modules/agents-deck`
186
+ // and the process runs out of a directory owned by a package it is not named
187
+ // after.
188
+ //
189
+ // #351 taught the stub to FIND the deck there. Nothing downstream learned about
190
+ // it, so every self-update surface went on naming `agents-deck` — which in that
191
+ // layout resolves to `<prefix>/lib/node_modules/agents-deck`, a directory this
192
+ // process never reads and a `npm i -g ccdeck` user usually does not have at
193
+ // all. "Update now" therefore installed a second, unrelated tree: the running
194
+ // install never moved, so installedVersion stayed put, the restart notice never
195
+ // came, and the banner offered the identical update forever while the upgrade
196
+ // reported "done".
197
+ //
198
+ // So the name to install is not the name this build was PUBLISHED under, it is
199
+ // the name the install was REACHED under — and under the stub layout that is
200
+ // written on disk one directory up. Read from there rather than from argv[1]
201
+ // (invoked-as.mjs answers the neighbouring question of which command the user
202
+ // TYPED, and cannot answer this one): the stub spawns the deck by absolute
203
+ // path, so argv[1] reads `agent-dag.js` under all three names, and on Windows
204
+ // npm's .cmd/.ps1 shims never pass the typed name on at all. The directory npm
205
+ // built is the one carrier that survives every platform.
206
+
207
+ /** Every name this deck is published under.
208
+ *
209
+ * `agents-deck` and `agent-dag` are one tarball published twice — see
210
+ * .github/workflows/publish.yml, which renames it between the two — and
211
+ * `ccdeck` is the stub that depends on it. The same three strings as
212
+ * invoked-as.mjs's COMMANDS, and deliberately not that list: this is the set
213
+ * of npm PACKAGES a `npm i -g` may name, that is the set of bin commands a
214
+ * user may type. They are equal only because the rename made them so, and a
215
+ * test pins them against each other rather than either side assuming it. */
216
+ export const ALIAS_PACKAGES = ["agents-deck", "agent-dag", "ccdeck"];
217
+
218
+ /** The directory of the package this one is installed INSIDE, or null when it
219
+ * is not inside one. Pure — path arithmetic only, so a Windows layout can be
220
+ * tested on a POSIX box and both separators are split on, exactly as npxRoot
221
+ * does it and for the same reason. */
222
+ export function hostRoot(pkgRoot) {
223
+ if (typeof pkgRoot !== "string") return null;
224
+ const parts = pkgRoot.split(/[\\/]/);
225
+ // `<host>/node_modules/<us>` and nothing else: the segment directly above us
226
+ // has to be the node_modules npm nested us into, and the one above that is
227
+ // the host. A scoped package would sit one level deeper, under `@scope`, and
228
+ // is refused here rather than guessed at — none of the three names is scoped.
229
+ if (parts.length < 3 || parts[parts.length - 2] !== "node_modules") return null;
230
+ // Keep the separator the input used: a Windows path must come back as one.
231
+ const sep = pkgRoot.includes("\\") && !pkgRoot.includes("/") ? "\\" : "/";
232
+ return parts.slice(0, -2).join(sep) || null;
233
+ }
234
+
235
+ /** The host package's name, out of its own manifest — but only when it is one
236
+ * of this deck's aliases AND it declares a dependency on us.
237
+ *
238
+ * The alias half is the load-bearing one. Any project that lists `agents-deck`
239
+ * in its dependencies and runs it out of its own node_modules — a workspace, a
240
+ * CI job, a tool that embeds the deck — is in exactly the same shape on disk
241
+ * as the stub, and answering with THAT name would have the deck offering to
242
+ * `npm i -g their-app@latest`: a package it has no business installing, and on
243
+ * a private name one that does not exist. Confining the answer to the three
244
+ * published names leaves every such install with the fallback it has today. */
245
+ export function hostNameFromMeta(meta, name = "agents-deck") {
246
+ const host = typeof meta?.name === "string" ? meta.name : null;
247
+ if (!host || !ALIAS_PACKAGES.includes(host)) return null;
248
+ return typeof meta?.dependencies?.[name] === "string" ? host : null;
249
+ }
250
+
251
+ /** The same, answered against the filesystem: `{ root, name }` for the alias
252
+ * package this copy was installed as a dependency of, or null. */
253
+ export function hostPackage(pkgRoot, name = "agents-deck") {
254
+ const root = hostRoot(pkgRoot);
255
+ if (!root) return null;
256
+ let meta = null;
257
+ try { meta = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); } catch { return null; }
258
+ const host = hostNameFromMeta(meta, name);
259
+ return host ? { root, name: host } : null;
260
+ }
261
+
179
262
  /** The package an upgrade would actually install here — the only package worth
180
263
  * asking npm about.
181
264
  *
@@ -188,14 +271,24 @@ export function npxRestartSpec(pkgRoot, name = "agents-deck") {
188
271
  * install — the ETARGET below, with a window measured in publishes rather than
189
272
  * in seconds of propagation.
190
273
  *
191
- * Deriving the name from the resolved upgrade spec keeps the two halves
274
+ * Deriving the name from what would actually be installed keeps the two halves
192
275
  * consistent by construction: whatever the command will install is what gets
193
- * asked about, and the per-name marker follows the same name. A global install
194
- * installs `name` and a checkout installs nothing, so both keep asking about
195
- * the name this build was published under. */
276
+ * asked about, and the per-name marker follows the same name. Each install
277
+ * shape carries that answer somewhere different — npx in the spec it recorded,
278
+ * a stub install in the layout npm built, a plain global install nowhere,
279
+ * because there the published name is already the right one. */
196
280
  export function upgradeName(pkgRoot, name = "agents-deck") {
197
- if (!isNpxInstall(pkgRoot) || isGitCheckout(pkgRoot)) return name;
198
- return bareSpecName(npxRestartSpec(pkgRoot, name)) ?? name;
281
+ // A checkout installs nothing at all, so the published name is the only
282
+ // sensible subject for the version question — and the only one whose
283
+ // dist-tag says anything about the branch the maintainer is sitting on.
284
+ if (isGitCheckout(pkgRoot)) return name;
285
+ if (isNpxInstall(pkgRoot)) return bareSpecName(npxRestartSpec(pkgRoot, name)) ?? name;
286
+ // Left: a global install, where the answer is whichever package owns the
287
+ // directory npm would rewrite. That is `name` for `npm i -g agents-deck` and
288
+ // for `npm i -g agent-dag`, and the stub for `npm i -g ccdeck` — the one
289
+ // shape where the two differ, and the one where naming `name` installed a
290
+ // tree this process could not see.
291
+ return hostPackage(pkgRoot, name)?.name ?? name;
199
292
  }
200
293
 
201
294
  /** The exact line the user can paste, for the way THIS copy was installed. */
@@ -204,7 +297,11 @@ export function upgradeCommand(pkgRoot, name = "agents-deck") {
204
297
  // so `npm run build` is part of the answer rather than an afterthought.
205
298
  if (isGitCheckout(pkgRoot)) return "git pull && npm run build";
206
299
  if (isNpxInstall(pkgRoot)) return `npx -y ${upgradeName(pkgRoot, name)}@latest`;
207
- return `npm i -g ${name}@latest`;
300
+ // Through upgradeName for the same reason the npx line above it is: the
301
+ // printed command is the user's escape hatch when the button fails or is not
302
+ // offered, and one that names a package this install cannot be replaced by is
303
+ // worse than none — it looks like it worked.
304
+ return `npm i -g ${upgradeName(pkgRoot, name)}@latest`;
208
305
  }
209
306
 
210
307
  // ── what npm has ─────────────────────────────────────────────────────────────
@@ -533,12 +630,19 @@ function dirWritable(p) {
533
630
 
534
631
  /** The same question, answered against the real filesystem and environment. */
535
632
  export function upgradeBlock(pkgRoot) {
633
+ // Whatever `npm i -g` would rewrite is what has to be writable, and under the
634
+ // stub layout that is not this directory: `npm i -g ccdeck` replaces
635
+ // <prefix>/lib/node_modules/ccdeck, and the copy running out of its nested
636
+ // node_modules goes with it. In practice both pairs answer the same, since
637
+ // one npm install created them with one owner — but the question is about the
638
+ // tree the command touches, and that tree is the host's.
639
+ const target = hostPackage(pkgRoot)?.root ?? pkgRoot;
536
640
  return upgradeBlockedReason({
537
641
  git: isGitCheckout(pkgRoot),
538
642
  npx: isNpxInstall(pkgRoot),
539
643
  // npm -g rewrites the package directory and its parent (the global
540
644
  // node_modules), so both have to be ours to write.
541
- writable: dirWritable(pkgRoot) && dirWritable(resolve(pkgRoot, "..")),
645
+ writable: dirWritable(target) && dirWritable(resolve(target, "..")),
542
646
  optedOut: process.env.AGENTS_DECK_NO_INSTALL === "1",
543
647
  });
544
648
  }
@@ -726,15 +830,22 @@ export function upgradeStatus() {
726
830
  * Start `npm i -g <name>@latest` in the background.
727
831
  *
728
832
  * Returns immediately with the accepted command, or a refusal. Never installs
729
- * anything except this package, and never at a version the caller chose — the
730
- * argument vector is fixed here, not assembled from request input.
833
+ * anything except this deck — under one of its own three published names, and
834
+ * never at a version the caller chose. The argument vector is fixed here, not
835
+ * assembled from request input: the only thing that varies is which alias, and
836
+ * that is read off the install on disk and confined to ALIAS_PACKAGES.
731
837
  */
732
838
  export function startUpgrade({ pkgRoot, name = "agents-deck" }) {
733
839
  if (_upgrade.state === "running") return { ok: true, already: true, command: _upgrade.command };
734
840
  const blocked = upgradeBlock(pkgRoot);
735
841
  if (blocked) return { ok: false, reason: blocked, command: upgradeCommand(pkgRoot, name) };
736
842
 
737
- const args = ["install", "-g", `${name}@latest`, "--no-audit", "--no-fund", "--loglevel", "error"];
843
+ // The package this install can actually be replaced by — the stub for a
844
+ // `npm i -g ccdeck`, the published name everywhere else. Installing `name`
845
+ // there wrote a tree this process never reads, so the version on disk never
846
+ // moved and the same update was offered forever.
847
+ const target = upgradeName(pkgRoot, name);
848
+ const args = ["install", "-g", `${target}@latest`, "--no-audit", "--no-fund", "--loglevel", "error"];
738
849
  const command = `npm ${args.join(" ")}`;
739
850
  _upgrade = { state: "running", command, error: null, at: Date.now() };
740
851