agent-dag 1.33.73 → 1.33.74

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-BNuS947v.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-8oJEgTRz.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-CF2yG9oG.css">
10
10
  </head>
11
11
  <body>
package/hook/hook.js CHANGED
@@ -119,6 +119,30 @@ function challengeProof(token, nonce) {
119
119
  return crypto.createHash("sha256").update(`${token}:${nonce}`).digest("hex");
120
120
  }
121
121
 
122
+ /**
123
+ * Must this target answer the challenge before it is handed a payload?
124
+ *
125
+ * Only a deck that advertises a token can be asked to prove it holds one. And
126
+ * hook.js is a single shared file — <claude config dir>/agent-dag/hook.js,
127
+ * installed by whichever deck booted most recently — while running several
128
+ * decks at once is ordinary use. So a hook that knows about the handshake
129
+ * routinely reads discovery files written by decks that predate it, which serve
130
+ * no /api/hook-challenge route at all. Refusing those outright leaves every one
131
+ * of them listening and permanently empty, with its banner still saying it is
132
+ * receiving events.
133
+ *
134
+ * A tokenless file therefore falls back to what shipped before the handshake:
135
+ * pid liveness and nothing else. That is not a weakening of anything — it is
136
+ * the exact risk every release up to 1.33.70 already carried, unchanged — and
137
+ * it costs the hardening nothing, because a file that does carry a token still
138
+ * gets no payload until the port answers correctly. Drop this fallback, and
139
+ * refuse tokenless files again, once no deck older than 1.33.71 is plausibly
140
+ * still running.
141
+ */
142
+ function requiresProof(d) {
143
+ return typeof d.token === "string" && d.token !== "";
144
+ }
145
+
122
146
  // Constant-time compare, purely so a hostile listener cannot walk the expected
123
147
  // proof out of us one byte at a time by timing how long we take to hang up. The
124
148
  // lengths are public (64 hex chars) and a mismatched one is rejected outright,
@@ -142,11 +166,15 @@ const POST_TIMEOUT_MS = 1000;
142
166
  * only once it has. `done` runs exactly once, whatever the outcome — a refused
143
167
  * connection, a silent port, a wrong answer and a delivered event all just mean
144
168
  * this target is finished.
169
+ *
170
+ * A deck that advertised no token is posted to directly: see requiresProof.
145
171
  */
146
172
  function deliver(d, body, done) {
147
173
  let settled = false;
148
174
  const finish = () => { if (settled) return; settled = true; done(); };
149
175
 
176
+ if (!requiresProof(d)) return post(d, body, finish);
177
+
150
178
  const nonce = crypto.randomBytes(16).toString("hex");
151
179
  const want = challengeProof(d.token, nonce);
152
180
 
@@ -236,12 +264,9 @@ function main() {
236
264
  let d;
237
265
  try { d = JSON.parse(fs.readFileSync(path.join(DIR, file), "utf8")); } catch { continue; }
238
266
  if (typeof d.workspace !== "string" || !d.pid || !d.port) continue;
239
- // No token, no payload. A file this old was written by a deck from before
240
- // the handshake existed; that deck rewrites its file with a token the
241
- // moment it restarts, so the cost of refusing is a blind deck until the
242
- // next start, against the cost of trusting an unauthenticated port with
243
- // every prompt the user types.
244
- if (typeof d.token !== "string" || d.token === "") continue;
267
+ // A missing token is not a reason to drop the file here — deliver()
268
+ // decides what a target has to prove, and a deck older than the handshake
269
+ // can prove nothing. See requiresProof.
245
270
 
246
271
  if (!isAlive(d.pid)) {
247
272
  try { fs.unlinkSync(path.join(DIR, file)); } catch {}
@@ -275,5 +300,5 @@ function main() {
275
300
  // the installer writes is `"<node>" "<...>/hook.js" --provider <name>`. Under a
276
301
  // require() it exports the matching rule and starts nothing, which is what lets
277
302
  // the rule be tested without a 1.5s exit timer in the test runner.
278
- module.exports = { cwdInWorkspace, foldsCase, challengeProof };
303
+ module.exports = { cwdInWorkspace, foldsCase, challengeProof, requiresProof };
279
304
  if (require.main === module) main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-dag",
3
- "version": "1.33.73",
3
+ "version": "1.33.74",
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": {