agent-dag 1.33.64 → 1.33.66

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-B5DB7xOD.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-D6KFW6DO.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-CF2yG9oG.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.64",
3
+ "version": "1.33.66",
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": {
@@ -4,7 +4,7 @@
4
4
  // Both providers share the discovery dir at <claude config dir>/agent-dag/ so a
5
5
  // single running server can receive events from either CLI. Re-runs are safe;
6
6
  // entries are tagged with __agent-dag and de-duped.
7
- import { readFile, writeFile, mkdir, copyFile, unlink, rename, open, stat, chmod } from "node:fs/promises";
7
+ import { readFile, writeFile, mkdir, unlink, rename, open, stat, chmod } from "node:fs/promises";
8
8
  import { existsSync } from "node:fs";
9
9
  import { homedir } from "node:os";
10
10
  import { join, resolve, dirname } from "node:path";
@@ -203,11 +203,33 @@ async function writeFileAtomic(target, text) {
203
203
  }
204
204
  }
205
205
 
206
+ /**
207
+ * Install one of the packaged hook scripts without ever exposing a partial one.
208
+ *
209
+ * copyFile truncates the destination and then fills it, and the destination here
210
+ * is a script every live Claude Code session runs on each tool call. Starting the
211
+ * deck while sessions are open — the normal way this is used — puts a hook
212
+ * invocation inside that window sooner or later, and what it executes is an
213
+ * empty or half-written program: a dropped event at best, a SyntaxError in the
214
+ * user's session at worst. Renaming a finished copy over the name closes it, so
215
+ * a session opens either the old script or the new one and both are whole.
216
+ *
217
+ * Re-installs are the common case and almost always produce the same bytes, so
218
+ * identical content skips the write and the file is not replaced at all.
219
+ */
220
+ async function installScript(src, dst) {
221
+ const text = await readFile(src, "utf8");
222
+ const current = await readFile(dst, "utf8").catch(() => null);
223
+ if (current === text) return false;
224
+ await writeFileAtomic(dst, text);
225
+ return true;
226
+ }
227
+
206
228
  async function installHookScript(installDir) {
207
229
  await ensureDir(installDir);
208
230
  const src = join(PKG_ROOT, "hook", "hook.js");
209
231
  const dst = join(installDir, "hook.js");
210
- await copyFile(src, dst);
232
+ await installScript(src, dst);
211
233
  return dst;
212
234
  }
213
235
 
@@ -300,5 +322,7 @@ export { AGENT_DAG_DIR, CLAUDE_DIR, CODEX_DIR, CLAUDE_EVENTS, CODEX_EVENTS };
300
322
  // Exported for the other modules that rewrite settings.json — the sound toggle
301
323
  // today. Every one of them needs the same two guarantees: a file we cannot
302
324
  // parse is never treated as an empty one, and the replacement is a single
303
- // rename rather than a truncate a reader can land inside.
304
- export { readSettingsForWrite, writeFileAtomic };
325
+ // rename rather than a truncate a reader can land inside. installScript carries
326
+ // the same guarantee to the hook scripts themselves, which are the files live
327
+ // sessions are actually executing.
328
+ export { readSettingsForWrite, writeFileAtomic, installScript };
@@ -10,13 +10,13 @@
10
10
  // Only ever touches its own entry, tagged `__agent-dag-sound`. Hooks the user
11
11
  // wrote themselves are left exactly as found — including the platform-specific
12
12
  // ones this replaces, which are reported rather than deleted.
13
- import { readFile, writeFile, copyFile, mkdir } from "node:fs/promises";
13
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
14
14
  import { existsSync } from "node:fs";
15
15
  import { join, dirname } from "node:path";
16
16
  import { homedir } from "node:os";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { claudeConfigDir } from "./claude-dir.mjs";
19
- import { readSettingsForWrite, writeFileAtomic } from "./installer.mjs";
19
+ import { readSettingsForWrite, writeFileAtomic, installScript } from "./installer.mjs";
20
20
 
21
21
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
22
22
  const CLAUDE_DIR = claudeConfigDir();
@@ -229,7 +229,10 @@ export async function setSoundHook(enabled) {
229
229
 
230
230
  if (enabled) {
231
231
  if (!existsSync(INSTALL_DIR)) await mkdir(INSTALL_DIR, { recursive: true });
232
- await copyFile(join(PKG_ROOT, "hook", "notify.js"), NOTIFY_PATH);
232
+ // Same reason the event forwarder is installed this way: the Stop hook fires
233
+ // notify.js from sessions that are already running, and toggling the sound
234
+ // on must not leave one of them executing a half-copied file.
235
+ await installScript(join(PKG_ROOT, "hook", "notify.js"), NOTIFY_PATH);
233
236
  others.push({
234
237
  [MARK]: true,
235
238
  hooks: [{