agent-dag 1.33.80 → 1.33.82

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.
package/bin/agent-dag.js CHANGED
@@ -26,17 +26,16 @@ import { spawn } from "node:child_process";
26
26
  import { connect } from "node:net";
27
27
  import { dirname, join } from "node:path";
28
28
  import { fileURLToPath } from "node:url";
29
- import { spawnSpec } from "../src/server/exec.mjs";
30
- import { installedVersion, npxRestartSpec } from "../src/server/self-update.mjs";
29
+ import { npxFailureHint, npxFailureSummary, npxLaunch } from "../src/server/npx.mjs";
30
+ import {
31
+ bareSpecName, clearRestartFailure, installedVersion, npxRestartSpec, recordRestartFailure,
32
+ } from "../src/server/self-update.mjs";
31
33
  import { dieOfSignal, workerExitAction } from "../src/server/supervisor.mjs";
32
34
 
33
35
  const BIN_DIR = dirname(fileURLToPath(import.meta.url));
34
36
  const WORKER = join(BIN_DIR, "deck.js");
35
37
  const PKG_ROOT = dirname(BIN_DIR);
36
38
 
37
- // npx is a .cmd shim on Windows, which spawn can only launch through cmd.exe —
38
- // spawnSpec is how, with the arguments quoted rather than pasted together.
39
- const NPX = process.platform === "win32" ? "npx.cmd" : "npx";
40
39
  const VERSION = installedVersion(PKG_ROOT) ?? "?";
41
40
 
42
41
  // The port the worker actually bound, which is not necessarily the one it was
@@ -147,17 +146,47 @@ function launchNpx() {
147
146
  // the deck talking over itself.
148
147
  args.push("--no-open");
149
148
 
149
+ // A retry answers for itself: whatever the last attempt left on disk is about
150
+ // to be replaced by this attempt's outcome, and leaving it there would keep
151
+ // the browser showing an old failure over a running fetch.
152
+ const pkgName = bareSpecName(spec) ?? "agents-deck";
153
+ clearRestartFailure(pkgName);
154
+
150
155
  process.stdout.write(`\n ↻ fetching ${spec}…\n`);
151
- // Not `shell: true`. Everything after the spec is the user's own argv, and
152
- // Node would paste it into one command line unquoted: `--workspace C:\Users\
153
- // John Smith\proj` would reach the new deck as two arguments, the second one
154
- // silently dropped by its parser — an upgraded deck watching the wrong
155
- // directory. spawnSpec routes npx.cmd through cmd.exe with every argument
156
- // quoted, and is a plain spawn everywhere else.
157
- const { file, args: argv, opts } = spawnSpec(NPX, args);
158
- const started = spawn(file, argv, { stdio: "inherit", ...opts });
156
+ // npxLaunch prefers npm's own npx-cli.js next to this Node binary, which
157
+ // needs no PATH lookup and no batch shim; the PATH shim is the fallback and
158
+ // still goes through cmd.exe with each argument quoted.
159
+ //
160
+ // Not `shell: true` on either path. Everything after the spec is the user's
161
+ // own argv, and Node would paste it into one command line unquoted:
162
+ // `--workspace C:\Users\John Smith\proj` would reach the new deck as two
163
+ // arguments, the second one silently dropped by its parser — an upgraded deck
164
+ // watching the wrong directory.
165
+ const { file, args: argv, opts } = npxLaunch(args);
166
+ // stderr is piped rather than inherited so a crash can be summarised instead
167
+ // of dumped: npm's MODULE_NOT_FOUND stack, with its `requireStack` and its
168
+ // caret line, is not something a user of a DAG dashboard can act on. stdout
169
+ // stays inherited — the new deck's banner, colours and URL line are the whole
170
+ // point of the supervisor staying out of the way.
171
+ const started = spawn(file, argv, { stdio: ["inherit", "inherit", "pipe"], ...opts });
159
172
  child = started;
160
173
 
174
+ // Held, not discarded: the moment the replacement is serving, everything it
175
+ // wrote goes to the terminal and every later byte passes straight through.
176
+ // Until then it is only evidence for a failure that may not happen.
177
+ let tail = "";
178
+ let teeing = false;
179
+ const tee = () => {
180
+ if (teeing) return;
181
+ teeing = true;
182
+ if (tail) process.stderr.write(tail);
183
+ };
184
+ started.stderr?.on("data", (d) => {
185
+ const s = String(d);
186
+ if (teeing) { process.stderr.write(s); return; }
187
+ tail = (tail + s).slice(-8000);
188
+ });
189
+
161
190
  // Whether the replacement ever got as far as serving. An npx that cannot
162
191
  // resolve exits in seconds having bound nothing; a deck the user stops with
163
192
  // Ctrl+C exits non-zero too, and only this tells the two apart.
@@ -166,7 +195,7 @@ function launchNpx() {
166
195
  if (boundPort == null || served) return;
167
196
  const sock = connect({ port: boundPort, host: "127.0.0.1" });
168
197
  sock.setTimeout(1000);
169
- sock.on("connect", () => { served = true; sock.destroy(); });
198
+ sock.on("connect", () => { served = true; tee(); sock.destroy(); });
170
199
  sock.on("timeout", () => sock.destroy());
171
200
  sock.on("error", () => { /* not up yet */ });
172
201
  }, 1000);
@@ -176,7 +205,19 @@ function launchNpx() {
176
205
  clearInterval(probe);
177
206
  child = null;
178
207
  if (stopping || served) return; // the user stopped it, or it ran and ended
208
+ const summary = npxFailureSummary(tail);
209
+ const hint = npxFailureHint(tail);
179
210
  console.error(`agents-deck: ${why} — staying on v${VERSION}`);
211
+ if (summary) console.error(` ${summary}`);
212
+ if (hint) console.error(` ${hint}`);
213
+ // Left for the worker about to be launched: it is the only way the browser
214
+ // learns this happened at all. See recordRestartFailure.
215
+ recordRestartFailure({
216
+ name: pkgName,
217
+ command: `npx -y ${spec}`,
218
+ error: [summary ?? why, hint].filter(Boolean).join(" — "),
219
+ version: VERSION === "?" ? null : VERSION,
220
+ });
180
221
  restarts++;
181
222
  launch(true);
182
223
  };
@@ -189,9 +230,9 @@ function launchNpx() {
189
230
  else process.exit(code ?? 0);
190
231
  return;
191
232
  }
192
- giveUp(`${NPX} ${spec} exited ${code ?? signal}`);
233
+ giveUp(`npx ${spec} exited ${code ?? signal}`);
193
234
  });
194
- started.on("error", (err) => giveUp(`could not run ${NPX}: ${err.message}`));
235
+ started.on("error", (err) => giveUp(`could not run npx: ${err.message}`));
195
236
  }
196
237
 
197
238
  // Ctrl+C already reaches the child directly — it shares this process group — so