ccdeck 3.22.7 → 3.22.9

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.
@@ -274,7 +274,7 @@ export function bareSpecName(spec) {
274
274
  * the answer is null instead, which is what invoked-as.mjs needs: it asks which
275
275
  * name the user typed, and there the package name is not a lesser answer, it is
276
276
  * a wrong one. */
277
- export function npxSpecFromMeta(meta, fallback = "agents-deck") {
277
+ export function npxSpecFromMeta(meta, fallback = PUBLISHED_NAME) {
278
278
  const list = meta && meta._npx && Array.isArray(meta._npx.packages) ? meta._npx.packages : [];
279
279
  for (const entry of list) {
280
280
  const name = bareSpecName(entry);
@@ -286,7 +286,7 @@ export function npxSpecFromMeta(meta, fallback = "agents-deck") {
286
286
  /** The same, answered against the filesystem. Null when this is not an npx run,
287
287
  * and — for a caller that passed no fallback — when the metadata cannot be
288
288
  * read. */
289
- export function npxRestartSpec(pkgRoot, name = "agents-deck") {
289
+ export function npxRestartSpec(pkgRoot, name = PUBLISHED_NAME) {
290
290
  const root = npxRoot(pkgRoot);
291
291
  if (!root) return null;
292
292
  let meta = null;
@@ -331,18 +331,32 @@ export function npxRestartSpec(pkgRoot, name = "agents-deck") {
331
331
  // other. What both of them do carry is their own package.json, which npm's
332
332
  // rename made authoritative: see installedName.
333
333
 
334
- /** Every name this deck is published under.
334
+ /** Every name an install of this deck can have been REACHED under.
335
335
  *
336
- * `agents-deck` and `agent-dag` are one tarball published twice — see
337
- * .github/workflows/publish.yml, which renames it between the two — and since
338
- * #340 `ccdeck` is a third rename of the same tarball rather than a launcher
339
- * package in front of it. The same three strings as
336
+ * Only `ccdeck` is published now. `agents-deck` and `agent-dag` were this
337
+ * tarball published twice more, then (from 3.22.3) small packages that
338
+ * depended on ccdeck, and both stay on the registry at their last version — so
339
+ * a deck can still be running inside one of them, or out of an npx run of one
340
+ * of them, and this list is what lets it recognise that. It is not the list of
341
+ * names to ask npm about: see PUBLISHED_NAME. The same three strings as
340
342
  * invoked-as.mjs's COMMANDS, and deliberately not that list: this is the set
341
- * of npm PACKAGES a `npm i -g` may name, that is the set of bin commands a
342
- * user may type. They are equal only because the rename made them so, and a
343
- * test pins them against each other rather than either side assuming it. */
343
+ * of npm PACKAGES an install may belong to, that is the set of commands a user
344
+ * may have typed. A test pins them against each other rather than either side
345
+ * assuming it. */
344
346
  export const ALIAS_PACKAGES = ["agents-deck", "agent-dag", "ccdeck"];
345
347
 
348
+ /** The one name the deck is published under, and so the only package whose
349
+ * dist-tag still moves. */
350
+ export const PUBLISHED_NAME = "ccdeck";
351
+
352
+ /** The names it is no longer published under. */
353
+ export const RETIRED_NAMES = ["agents-deck", "agent-dag"];
354
+
355
+ /** The published name in place of a retired one; any other name unchanged. */
356
+ export function currentName(name) {
357
+ return RETIRED_NAMES.includes(name) ? PUBLISHED_NAME : name;
358
+ }
359
+
346
360
  /** Which of those three THIS build was published as, out of its own manifest.
347
361
  *
348
362
  * The other half of the same bug, and the easier half. `agent-dag` is not a
@@ -373,7 +387,7 @@ export const ALIAS_PACKAGES = ["agents-deck", "agent-dag", "ccdeck"];
373
387
  * `fallback` is what to answer when the manifest is missing, unreadable, or
374
388
  * names something that is not one of ours — the caller's own `name`, so every
375
389
  * shape that cannot prove which alias it is keeps the behaviour it had. */
376
- export function installedName(pkgRoot, fallback = "agents-deck") {
390
+ export function installedName(pkgRoot, fallback = PUBLISHED_NAME) {
377
391
  const self = readManifest(pkgRoot)?.name;
378
392
  return typeof self === "string" && ALIAS_PACKAGES.includes(self) ? self : fallback;
379
393
  }
@@ -405,7 +419,7 @@ export function hostRoot(pkgRoot) {
405
419
  * `npm i -g their-app@latest`: a package it has no business installing, and on
406
420
  * a private name one that does not exist. Confining the answer to the three
407
421
  * published names leaves every such install with the fallback it has today. */
408
- export function hostNameFromMeta(meta, name = "agents-deck") {
422
+ export function hostNameFromMeta(meta, name = PUBLISHED_NAME) {
409
423
  const host = typeof meta?.name === "string" ? meta.name : null;
410
424
  if (!host || !ALIAS_PACKAGES.includes(host)) return null;
411
425
  return typeof meta?.dependencies?.[name] === "string" ? host : null;
@@ -413,7 +427,7 @@ export function hostNameFromMeta(meta, name = "agents-deck") {
413
427
 
414
428
  /** The same, answered against the filesystem: `{ root, name }` for the alias
415
429
  * package this copy was installed as a dependency of, or null. */
416
- export function hostPackage(pkgRoot, name = "agents-deck") {
430
+ export function hostPackage(pkgRoot, name = PUBLISHED_NAME) {
417
431
  const root = hostRoot(pkgRoot);
418
432
  if (!root) return null;
419
433
  const host = hostNameFromMeta(readManifest(root), name);
@@ -457,8 +471,8 @@ export function successorRoot(pkgRoot) {
457
471
  return typeof name === "string" && ALIAS_PACKAGES.includes(name) ? root : null;
458
472
  }
459
473
 
460
- /** The package an upgrade would actually install here — the only package worth
461
- * asking npm about.
474
+ /** The package an upgrade would actually install here — and, but for a retired
475
+ * name, the one worth asking npm about (see registryName).
462
476
  *
463
477
  * The check used to ask about `agents-deck` no matter what the upgrade
464
478
  * command installed, so a deck started with `npx ccdeck` compared its version
@@ -475,7 +489,7 @@ export function successorRoot(pkgRoot) {
475
489
  * shape carries that answer somewhere different — npx in the spec it recorded,
476
490
  * a stub install in the layout npm built, a plain global install in the
477
491
  * manifest of the package it is. All three are read; none is assumed. */
478
- export function upgradeName(pkgRoot, name = "agents-deck") {
492
+ export function upgradeName(pkgRoot, name = PUBLISHED_NAME) {
479
493
  // The published name this build actually carries, which outranks `name` in
480
494
  // every branch below because `name` is a default at every call site in the
481
495
  // deck and the manifest on disk is not a guess. It replaces the parameter
@@ -483,17 +497,20 @@ export function upgradeName(pkgRoot, name = "agents-deck") {
483
497
  // registry-shaped answers meet — the dist-tag that is fetched, the marker it
484
498
  // is cached in, the command the user is shown, and the argv npm is spawned
485
499
  // with — so resolving the name once here is what keeps those four naming one
486
- // package, which is the property the whole function exists to hold.
500
+ // package. registryName is the one exception, for a retired name, and it
501
+ // starts from this answer.
487
502
  const self = installedName(pkgRoot, name);
488
503
  // A checkout installs nothing at all, so the published name is the only
489
504
  // sensible subject for the version question — and the only one whose
490
505
  // dist-tag says anything about the branch the maintainer is sitting on.
491
506
  if (isGitCheckout(pkgRoot)) return self;
492
507
  // npx recorded the spec the user typed, which is a better answer than the
493
- // manifest: `npx ccdeck` unpacks a deck whose manifest says `agents-deck`,
494
- // and re-running the name they did not type would move them off the stub.
495
- // The manifest is what is left when that record cannot be read.
496
- if (isNpxInstall(pkgRoot)) return bareSpecName(npxRestartSpec(pkgRoot, self)) ?? self;
508
+ // manifest — except that a retired name answers `ccdeck`. npx reinstalls only
509
+ // when a spec resolves to a different version, and `agents-deck@latest`
510
+ // resolves to the same last-published package forever, so re-running it
511
+ // would reuse the cached copy, and the ccdeck inside it, and never move. The
512
+ // manifest is what is left when the record cannot be read.
513
+ if (isNpxInstall(pkgRoot)) return currentName(bareSpecName(npxRestartSpec(pkgRoot, self)) ?? self);
497
514
  // Left: a global install, where the answer is whichever package owns the
498
515
  // directory npm would rewrite. That is the stub for `npm i -g ccdeck`, where
499
516
  // the deck is nested one level down inside a package it is not named after,
@@ -517,7 +534,7 @@ export function upgradeName(pkgRoot, name = "agents-deck") {
517
534
  }
518
535
 
519
536
  /** The exact line the user can paste, for the way THIS copy was installed. */
520
- export function upgradeCommand(pkgRoot, name = "agents-deck") {
537
+ export function upgradeCommand(pkgRoot, name = PUBLISHED_NAME) {
521
538
  // A checkout is updated by pulling, and the bundle is built, not shipped —
522
539
  // so `npm run build` is part of the answer rather than an afterthought.
523
540
  if (isGitCheckout(pkgRoot)) return "git pull && npm run build";
@@ -529,6 +546,21 @@ export function upgradeCommand(pkgRoot, name = "agents-deck") {
529
546
  return `npm i -g ${upgradeName(pkgRoot, name)}@latest`;
530
547
  }
531
548
 
549
+ /** The package to ask npm about: the one an upgrade installs, except that a
550
+ * retired name is asked about as `ccdeck`.
551
+ *
552
+ * One install reaches that exception. A deck sitting inside the old
553
+ * `agents-deck` or `agent-dag` package upgrades by reinstalling THAT package —
554
+ * installing `ccdeck` would write a tree this process never reads, #358 again —
555
+ * but that package's dist-tag stopped moving when it stopped being published.
556
+ * The reinstall is still the right act: the old package depends on
557
+ * `ccdeck@^3`, and npm resolves that to the newest ccdeck on every reinstall,
558
+ * even of the same version. So the version asked about is ccdeck's and the
559
+ * command names the old package — the one place the two differ, on purpose. */
560
+ export function registryName(pkgRoot, name = PUBLISHED_NAME) {
561
+ return currentName(upgradeName(pkgRoot, name));
562
+ }
563
+
532
564
  // ── what npm has ─────────────────────────────────────────────────────────────
533
565
 
534
566
  // The dist-tags endpoint answers with ~20 bytes ({"latest":"1.30.7"}); the full
@@ -597,7 +629,7 @@ async function isPublished(name, version) {
597
629
  * common case reads plainly, and the tail is trimmed so a pathological name
598
630
  * cannot produce a path the OS refuses. An unusable name falls back to the
599
631
  * default rather than to the shared file this fix exists to get rid of. */
600
- export function markerFileName(name = "agents-deck") {
632
+ export function markerFileName(name = PUBLISHED_NAME) {
601
633
  return `.self-update-check-${safeNamePart(name)}`;
602
634
  }
603
635
 
@@ -613,7 +645,7 @@ function safeNamePart(name) {
613
645
  // trailing dot from a file name, so a name that ends in one would write to
614
646
  // a path that is not the path we would later read.
615
647
  .replace(/^[-.]+|[-.]+$/g, "");
616
- return safe || "agents-deck";
648
+ return safe || PUBLISHED_NAME;
617
649
  }
618
650
 
619
651
  function markerPath(name) {
@@ -664,7 +696,7 @@ function readMarker(name) {
664
696
  * between a click and a fetch, and the reason the fetch is about to fail may
665
697
  * well be that there is no network — and it has to be the same number the
666
698
  * banner offered, which is precisely what the marker holds. */
667
- export function lastKnownLatest(name = "agents-deck") {
699
+ export function lastKnownLatest(name = PUBLISHED_NAME) {
668
700
  const v = readMarker(name)?.version;
669
701
  return typeof v === "string" && v ? v : null;
670
702
  }
@@ -1026,7 +1058,7 @@ export function claimRestartFailureKey(env = process.env, pid = process.pid) {
1026
1058
 
1027
1059
  /** `ccdeck` under supervisor 4821 → `.restart-failed-ccdeck-4821`, or null when
1028
1060
  * there is no supervisor, which is not a deck any note can be about. */
1029
- export function restartFailureFileName(name = "agents-deck", key = restartFailureKey()) {
1061
+ export function restartFailureFileName(name = PUBLISHED_NAME, key = restartFailureKey()) {
1030
1062
  const owner = safeOwner(key);
1031
1063
  return owner ? `${NOTE_PREFIX}${safeNamePart(name)}-${owner}` : null;
1032
1064
  }
@@ -1041,7 +1073,7 @@ function restartFailurePath(name, key) {
1041
1073
  * already failed here and is not being tried again. Best-effort: a read-only
1042
1074
  * home costs the report, not the deck. */
1043
1075
  export function recordRestartFailure({
1044
- name = "agents-deck", command = null, error = null, version = null,
1076
+ name = PUBLISHED_NAME, command = null, error = null, version = null,
1045
1077
  target = null, attempts = 1, at = Date.now(), failedAt = at, key = restartFailureKey(),
1046
1078
  } = {}) {
1047
1079
  const file = restartFailureFileName(name, key);
@@ -1072,13 +1104,13 @@ export function recordRestartFailure({
1072
1104
 
1073
1105
  /** Called before each attempt, so a retry is answered by its own outcome rather
1074
1106
  * than by the last one's. */
1075
- export function clearRestartFailure(name = "agents-deck", key = restartFailureKey()) {
1107
+ export function clearRestartFailure(name = PUBLISHED_NAME, key = restartFailureKey()) {
1076
1108
  const path = restartFailurePath(name, key);
1077
1109
  if (!path) return;
1078
1110
  try { rmSync(path, { force: true }); } catch { /* ignore */ }
1079
1111
  }
1080
1112
 
1081
- export function readRestartFailure(name = "agents-deck", key = restartFailureKey()) {
1113
+ export function readRestartFailure(name = PUBLISHED_NAME, key = restartFailureKey()) {
1082
1114
  const path = restartFailurePath(name, key);
1083
1115
  if (!path) return null;
1084
1116
  try {
@@ -1162,7 +1194,7 @@ export function upgradeStatus() {
1162
1194
  * assembled from request input: the only thing that varies is which alias, and
1163
1195
  * that is read off the install on disk and confined to ALIAS_PACKAGES.
1164
1196
  */
1165
- export function startUpgrade({ pkgRoot, name = "agents-deck" }) {
1197
+ export function startUpgrade({ pkgRoot, name = PUBLISHED_NAME }) {
1166
1198
  if (_upgrade.state === "running") return { ok: true, already: true, command: _upgrade.command };
1167
1199
  const blocked = upgradeBlock(pkgRoot);
1168
1200
  if (blocked) return { ok: false, reason: blocked, command: upgradeCommand(pkgRoot, name) };
@@ -1314,13 +1346,15 @@ export function lastMeaningfulLine(text) {
1314
1346
  * yet — the installability probe (see runCheck), each carrying its own
1315
1347
  * AbortSignal timeout. That second request costs its timeout at most once per
1316
1348
  * release; a deck sitting on the current release stays inside one. */
1317
- export async function versionReport({ running, pkgRoot, name = "agents-deck", now = Date.now(), force = false }) {
1349
+ export async function versionReport({ running, pkgRoot, name = PUBLISHED_NAME, now = Date.now(), force = false }) {
1318
1350
  const installed = installedVersion(pkgRoot);
1319
1351
  // Asked about the package the command installs, not about the one this build
1320
- // happens to be named after — see upgradeName. Everything registry-shaped in
1321
- // this report is about `target`: the version, the marker it is cached in, and
1322
- // the name the report gives for it.
1352
+ // happens to be named after — see upgradeName — except that a retired name
1353
+ // is asked about as ccdeck, the only dist-tag that still moves (registryName).
1354
+ // `target` is what the command installs and what the failure note is filed
1355
+ // under; `asked` is whose version this reports and the marker it is cached in.
1323
1356
  const target = upgradeName(pkgRoot, name);
1357
+ const asked = registryName(pkgRoot, name);
1324
1358
  // Only an explicit opt-out silences the registry.
1325
1359
  //
1326
1360
  // A checkout used to be excluded here too, on the reasoning that its version
@@ -1334,8 +1368,8 @@ export async function versionReport({ running, pkgRoot, name = "agents-deck", no
1334
1368
  const skipRegistry =
1335
1369
  process.env.AGENTS_DECK_NO_UPDATE_CHECK === "1" ||
1336
1370
  process.env.AGENTS_DECK_NO_INSTALL === "1";
1337
- const latest = skipRegistry ? null : await latestOnNpm(target, now, force);
1338
- const marker = skipRegistry ? null : readMarker(target);
1371
+ const latest = skipRegistry ? null : await latestOnNpm(asked, now, force);
1372
+ const marker = skipRegistry ? null : readMarker(asked);
1339
1373
  const blocked = upgradeBlock(pkgRoot);
1340
1374
  // An install started in THIS process outranks the note on disk: it is newer
1341
1375
  // by construction, and a running one must not be reported as a past failure.