agent-dag 3.8.8 → 3.8.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.
@@ -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-DLRqo4Ty.js"></script>
43
+ <script type="module" crossorigin src="/assets/index-Dq0gpZ5v.js"></script>
44
44
  <link rel="stylesheet" crossorigin href="/assets/index-ByAgTqB8.css">
45
45
  </head>
46
46
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-dag",
3
- "version": "3.8.8",
3
+ "version": "3.8.9",
4
4
  "description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Run it with npx ccdeck.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,6 +31,12 @@
31
31
  "about a defect in the type, and the suite refuses both it and no space at",
32
32
  "all."
33
33
  ],
34
+ "3.8.9": [
35
+ {
36
+ "title": "\ud83d\udda5\ufe0f Four things that only went wrong on somebody else's operating system",
37
+ "body": "**Ubuntu:** the deck knew where Chromium and Brave keep their profiles when installed as a snap or a flatpak \u2014 which on a default Ubuntu is the only place Chromium is \u2014 but not what those programs are called. So it reported them as not running while they were, and refused to quit them. Both now work.\n\n**Windows:** the deck asked the system whether it could write to the folder an update installs into, and Windows always answers yes for a folder whatever the permissions say. It offered you an update that then died inside npm with a permission error. It now finds out by trying, and declines up front with the command to run yourself \u2014 which is what every other platform already got.\n\n**Linux:** if `XDG_DATA_HOME` was set to something starting with `~`, as it is from a systemd unit or a Dockerfile, the deck and claude-swap looked in two different places for your accounts. The panel said you had none while `cswap list` showed them all.\n\n**Plain terminals:** six messages used a typographic dash the old Windows console and the Linux text console cannot draw, so a box appeared mid-sentence \u2014 including in the line that explains why no events are arriving. They use the plain form on those terminals now, like everything else already did."
38
+ }
39
+ ],
34
40
  "3.8.8": [
35
41
  {
36
42
  "title": "\ud83d\udcc9 A false 99% on the memory chart, and three other quiet failures",
@@ -76,6 +76,15 @@ const APP_NAME = {
76
76
  "chrome-beta": "chrome",
77
77
  "chrome-canary": "chrome",
78
78
  chromium: "chromium",
79
+ // The two roots `browserRoots` emits and this table used to miss (#794).
80
+ // The snap path's own comment there says it "is the only Chromium root a
81
+ // default Ubuntu install has" — so on stock Ubuntu the panel reported
82
+ // Chromium as not running while it was running, and the quit reaction,
83
+ // which `available("linux")` does offer, answered `unknown_browser` on
84
+ // every finding from either of them. darwin covers all 8 of its roots and
85
+ // win32 all 7 of its own; only this table was short.
86
+ "chromium-snap": "chromium",
87
+ "brave-flatpak": "brave",
79
88
  brave: "brave",
80
89
  edge: "msedge",
81
90
  vivaldi: "vivaldi-bin",
@@ -206,10 +206,16 @@ export async function closeTab(browserKey, url, platform = process.platform, dep
206
206
 
207
207
  /** Quit a browser. The only reaction that takes the session back. */
208
208
  export async function quitBrowser(browserKey, platform = process.platform, deps = {}) {
209
- const app = appName(browserKey);
210
- if (!app) return { ok: false, reason: "unknown_browser" };
211
209
  const exec = deps.run ?? run;
212
210
  if (platform === "darwin") {
211
+ // The darwin display-name table is consulted HERE rather than at the top of
212
+ // the function (#794). It used to gate every platform, so a browser missing
213
+ // from a macOS-only table — `chromium-snap` and `brave-flatpak`, the two
214
+ // roots a default Ubuntu install actually has — answered `unknown_browser`
215
+ // on Linux before the branch that would have known what to do with it was
216
+ // ever reached.
217
+ const app = appName(browserKey);
218
+ if (!app) return { ok: false, reason: "unknown_browser" };
213
219
  const r = await exec("osascript", [
214
220
  // `--`, as everywhere else that hands osascript an operand. `app` comes
215
221
  // from a fixed table so it is safe by construction; the separator costs
@@ -39,7 +39,23 @@ import { homedir, platform } from "node:os";
39
39
  export function backupRoot() {
40
40
  if (process.env.CLAUDE_SWAP_BACKUP) return process.env.CLAUDE_SWAP_BACKUP;
41
41
  if (platform() === "linux") {
42
- const xdg = process.env.XDG_DATA_HOME;
42
+ // `~` IS EXPANDED BEFORE THE ABSOLUTENESS TEST (#796), which is what
43
+ // claude-swap's own paths.py does: `Path(os.path.expanduser(xdg))` and then
44
+ // `is_absolute()`, under a docstring saying it exists so that "values like
45
+ // `~/data` set via systemd unit files or Dockerfiles (which don't get shell
46
+ // expansion) still work". This function claims to mirror that and did not.
47
+ //
48
+ // Unexpanded, `XDG_DATA_HOME=~/data` failed `startsWith("/")` and the deck
49
+ // read ~/.local/share/claude-swap while cswap read and wrote
50
+ // ~/data/claude-swap. The Accounts panel then reported `no_accounts` while
51
+ // `cswap list` showed the roster — and the damaging part is
52
+ // `seedFirstAccount`, which reads a missing sequence file as zero accounts,
53
+ // passes its `before > 0` guard, and runs `cswap add` against a populated
54
+ // store, re-pointing activeAccountNumber with nothing here to restore it.
55
+ const raw = process.env.XDG_DATA_HOME;
56
+ const xdg = raw === "~" ? homedir()
57
+ : raw?.startsWith("~/") ? join(homedir(), raw.slice(2))
58
+ : raw;
43
59
  if (xdg && xdg.startsWith("/")) return join(xdg, "claude-swap");
44
60
  return join(homedir(), ".local/share/claude-swap");
45
61
  }
@@ -22,7 +22,7 @@
22
22
  // it by name and only where it can actually work: a global install, on a
23
23
  // directory we can write, outside a git checkout and outside an npx cache.
24
24
  // Everywhere else this stays what it has always been — a printed command.
25
- import { accessSync, constants as FS, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
25
+ import { accessSync, constants as FS, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
26
26
  import { spawn } from "node:child_process";
27
27
  import { homedir } from "node:os";
28
28
  import { dirname, join, resolve } from "node:path";
@@ -894,8 +894,42 @@ export function upgradeMode(blockedReason) {
894
894
  return blockedReason === "npx" ? "npx" : null;
895
895
  }
896
896
 
897
+ /**
898
+ * Can this user write into `p`?
899
+ *
900
+ * ON WINDOWS THE QUESTION HAS TO BE ASKED BY WRITING (#795). libuv's
901
+ * `fs__access` short-circuits to success for anything carrying
902
+ * FILE_ATTRIBUTE_DIRECTORY — "Directories cannot be read-only on Windows" — and
903
+ * never consults the ACL; Node documents this. Both arguments this is called
904
+ * with are always directories, so `writable` was unconditionally true there,
905
+ * `upgradeBlockedReason` could never answer `not_writable`, and the banner
906
+ * offered "Update & restart" on the common nvm-windows layout where the global
907
+ * prefix is C:\Program Files\nodejs and a non-elevated shell cannot write it.
908
+ * npm then died with EPERM and the user got a truncated npm log tail in a
909
+ * 46-character box — where a POSIX user in the same position gets "the install
910
+ * directory is not writable by this user" and the command to paste. The comment
911
+ * on upgradeBlockedReason states the intent the platform defeated: "Failing
912
+ * inside npm with EACCES tells the user less than declining up front does."
913
+ *
914
+ * The probe is `createTemp`'s "wx" pattern, synchronously: a name nothing else
915
+ * can be using, created exclusively and removed at once. POSIX keeps
916
+ * `accessSync`, where it is correct and cheaper.
917
+ */
897
918
  function dirWritable(p) {
898
- try { accessSync(p, FS.W_OK); return true; } catch { return false; }
919
+ if (process.platform !== "win32") {
920
+ try { accessSync(p, FS.W_OK); return true; } catch { return false; }
921
+ }
922
+ // `wx` fails if the name exists, so a collision reads as "not writable"
923
+ // rather than clobbering somebody's file — hence pid and a random suffix.
924
+ const probe = join(p, `.ccdeck-w-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
925
+ try {
926
+ writeFileSync(probe, "", { flag: "wx" });
927
+ return true;
928
+ } catch {
929
+ return false;
930
+ } finally {
931
+ try { unlinkSync(probe); } catch { /* never created, or already gone */ }
932
+ }
899
933
  }
900
934
 
901
935
  /** The same question, answered against the real filesystem and environment. */
@@ -225,13 +225,13 @@ export function upgradeAttempt({
225
225
  * banner and the terminal say, so it has to name the version, say what already
226
226
  * happened to it, and leave the user somewhere to go. The copyable command is
227
227
  * already on screen next to it. */
228
- export function upgradeRefusalText({ reason, waitMs = 0, attempt = 0 } = {}, target = null) {
228
+ export function upgradeRefusalText({ reason, waitMs = 0, attempt = 0, dash = "—" } = {}, target = null) {
229
229
  const what = target ? `v${target}` : "this update";
230
230
  if (reason === "exhausted") {
231
- return `${what} failed to fetch ${attempt} times not trying again from here; run the command yourself`;
231
+ return `${what} failed to fetch ${attempt} times ${dash} not trying again from here; run the command yourself`;
232
232
  }
233
233
  const left = waitMs >= 60_000 ? `${Math.ceil(waitMs / 60_000)}m` : `${Math.max(1, Math.ceil(waitMs / 1000))}s`;
234
- return `${what} failed to fetch a moment ago waiting ${left} before trying again`;
234
+ return `${what} failed to fetch a moment ago ${dash} waiting ${left} before trying again`;
235
235
  }
236
236
 
237
237
  /**
@@ -628,12 +628,19 @@ export function pulseDot(beat, { registered = true, claude = true, busy = null }
628
628
  *
629
629
  * @param file the discovery file that could not be written.
630
630
  * @param claude whether this deck is watching Claude Code at all.
631
+ * @param dash the em dash, or the ASCII stand-in on a console that cannot draw
632
+ * one (#797). A parameter for the same reason `renameNotice` takes one: this
633
+ * module's own header says "every glyph the deck prints — punctuation
634
+ * included, since an em dash is as absent from CP437 as a check mark — comes
635
+ * from one of them", and this sentence had it hardcoded. On a legacy cmd.exe
636
+ * or the Linux virtual console the reader got a box in the middle of the one
637
+ * line explaining why no events are arriving.
631
638
  */
632
- export function unregisteredDetail({ file, claude = true }) {
639
+ export function unregisteredDetail({ file, claude = true, dash = "—" }) {
633
640
  if (claude) {
634
641
  return `Claude Code hooks find this deck through ${file}, so until that file exists no Claude Code events arrive.`;
635
642
  }
636
- return `Codex capture does not use ${file} the deck tails the rollout files itself so events still arrive. Only decks sharing one events log need it, to agree on which of them records.`;
643
+ return `Codex capture does not use ${file} ${dash} the deck tails the rollout files itself ${dash} so events still arrive. Only decks sharing one events log need it, to agree on which of them records.`;
637
644
  }
638
645
 
639
646
  // ── cursor ───────────────────────────────────────────────────────────────────