agent-dag 1.33.55 → 1.33.57
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/dist/web/index.html
CHANGED
|
@@ -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-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BMwi5hcI.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.
|
|
3
|
+
"version": "1.33.57",
|
|
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": {
|
package/src/server/exec.mjs
CHANGED
|
@@ -126,8 +126,23 @@ export const tryNext = (err) =>
|
|
|
126
126
|
Boolean(err) && (err.code === "ENOENT" || err.code === "EACCES" ||
|
|
127
127
|
err.code === "EINVAL" || err.code === "UNKNOWN");
|
|
128
128
|
|
|
129
|
+
// The three lines cmd.exe prints when it cannot find what it was asked to run,
|
|
130
|
+
// anchored to a whole line each. First the two-line pair for a bare name, then
|
|
131
|
+
// the one it uses when a directory in an explicit path does not exist.
|
|
132
|
+
const CMD_UNKNOWN = /^'(.+)' is not recognized as an internal or external command,?$/i;
|
|
133
|
+
const CMD_UNKNOWN_TAIL = /^operable program or batch file\.?$/i;
|
|
134
|
+
const CMD_NO_PATH = /^the system cannot find the (?:path|file) specified\.?$/i;
|
|
135
|
+
|
|
136
|
+
// cmd.exe echoes the command token exactly as it was given, so an exact match
|
|
137
|
+
// is what we expect; the comparison is only case- and quote-insensitive because
|
|
138
|
+
// Windows paths are.
|
|
139
|
+
const sameCommand = (quoted, name) => {
|
|
140
|
+
const norm = (s) => String(s).trim().replace(/^"+|"+$/g, "").toLowerCase();
|
|
141
|
+
return norm(quoted) === norm(name);
|
|
142
|
+
};
|
|
143
|
+
|
|
129
144
|
/**
|
|
130
|
-
* cmd.exe's way of saying ENOENT.
|
|
145
|
+
* cmd.exe's way of saying ENOENT — and only cmd.exe's.
|
|
131
146
|
*
|
|
132
147
|
* A .cmd or .bat candidate is launched THROUGH cmd.exe, and cmd.exe exists — so
|
|
133
148
|
* a missing tool is not a spawn error at all. It is a healthy shell exiting 1
|
|
@@ -140,11 +155,39 @@ export const tryNext = (err) =>
|
|
|
140
155
|
* second line — on its own, meaningless — in front of the user. Reported from
|
|
141
156
|
* Windows on 2026-08-14: the accounts panel said only "operable program or
|
|
142
157
|
* batch file." when sharing an account.
|
|
158
|
+
*
|
|
159
|
+
* The fussiness is about the other direction, which is worse. "The system
|
|
160
|
+
* cannot find the file specified" is Windows' generic text for ENOENT, and it
|
|
161
|
+
* appears INSIDE the output of tools that ran perfectly well — cswap is a
|
|
162
|
+
* Python CLI, and a FileNotFoundError traceback ends in that exact sentence.
|
|
163
|
+
* Believed there, it threw the real error away, told the user the tool was not
|
|
164
|
+
* on PATH when PATH was fine, and — the dangerous half — sent the candidate
|
|
165
|
+
* loop on to re-run the entire command, which for `cswap remove 3` means asking
|
|
166
|
+
* to delete an account a second time.
|
|
167
|
+
*
|
|
168
|
+
* So the output has to be cmd.exe's message and nothing else: cmd.exe prints it
|
|
169
|
+
* INSTEAD of running anything, so any other line, or any prefix on the line,
|
|
170
|
+
* means something ran and this is its report. And when cmd.exe names the
|
|
171
|
+
* command it could not find, that name must be the candidate we asked for — a
|
|
172
|
+
* tool that shells out itself can forward the message about some other command.
|
|
173
|
+
*
|
|
174
|
+
* `name` is the candidate spelling that produced the output. Callers that only
|
|
175
|
+
* have the text (failureText) omit it and get the shape rules alone.
|
|
143
176
|
*/
|
|
144
|
-
export
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
177
|
+
export function looksMissing(text, name = "") {
|
|
178
|
+
const lines = String(text ?? "").split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
179
|
+
if (!lines.length) return false;
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const named = CMD_UNKNOWN.exec(line);
|
|
182
|
+
if (named) {
|
|
183
|
+
if (name && !sameCommand(named[1], name)) return false;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (CMD_UNKNOWN_TAIL.test(line) || CMD_NO_PATH.test(line)) continue;
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
148
191
|
|
|
149
192
|
/**
|
|
150
193
|
* Run a command and collect its output. Never rejects, and never throws —
|
|
@@ -175,8 +218,12 @@ export function run(cmd, args, { timeout = 20_000, maxBuffer = 4 << 20 } = {}) {
|
|
|
175
218
|
const done = (err, stdout, stderr) => {
|
|
176
219
|
clearTimeout(timer);
|
|
177
220
|
// cmd.exe's "is not recognized" counts as "not this spelling" too, and
|
|
178
|
-
// it arrives as a normal non-zero exit rather than a spawn error.
|
|
179
|
-
|
|
221
|
+
// it arrives as a normal non-zero exit rather than a spawn error. Only
|
|
222
|
+
// a batch candidate goes through a shell, so only there can the output
|
|
223
|
+
// be a shell's verdict rather than the tool's own words — spawned
|
|
224
|
+
// directly, a missing file is a plain ENOENT and anything printed came
|
|
225
|
+
// from a tool that ran.
|
|
226
|
+
const missing = Boolean(err) && tree && looksMissing(`${stderr ?? ""}\n${stdout ?? ""}`, raw);
|
|
180
227
|
if (err && (tryNext(err) || missing) && i + 1 < tries.length) return attempt(i + 1);
|
|
181
228
|
if (!err) resolved.set(cmd, raw);
|
|
182
229
|
resolve({
|
|
@@ -317,8 +364,11 @@ export function runInteractive(cmd, args, { timeout = 300_000, maxOutput = 256 <
|
|
|
317
364
|
proc.on("close", (code) => {
|
|
318
365
|
if (stale()) return;
|
|
319
366
|
// Same cmd.exe case as in `run`: exit 1 with "is not recognized" means
|
|
320
|
-
// this spelling does not exist, not that the tool failed.
|
|
321
|
-
|
|
367
|
+
// this spelling does not exist, not that the tool failed. Restricted to a
|
|
368
|
+
// batch candidate, which is the only kind launched through a shell, and
|
|
369
|
+
// to output that is cmd.exe's message alone — everything below re-runs
|
|
370
|
+
// the whole command, and these commands remove accounts.
|
|
371
|
+
if (code !== 0 && isBatch(raw) && looksMissing(`${stderr}\n${stdout}`, raw)) {
|
|
322
372
|
if (i + 1 < tries.length) {
|
|
323
373
|
stdout = ""; stderr = ""; pending = "";
|
|
324
374
|
child = null;
|
|
@@ -458,24 +458,54 @@ export function startUpgrade({ pkgRoot, name = "agents-deck" }) {
|
|
|
458
458
|
child.stdout.on("data", d => keepTail(String(d)));
|
|
459
459
|
child.stderr.on("data", d => keepTail(String(d)));
|
|
460
460
|
|
|
461
|
+
// The deadline states the outcome itself, and only then kills.
|
|
462
|
+
//
|
|
461
463
|
// npm is a .cmd shim on Windows and is therefore spawned through a shell, so
|
|
462
464
|
// `child` is cmd.exe and npm itself is a grandchild — a plain kill would
|
|
463
|
-
// report the install as timed out while it carried on writing to
|
|
464
|
-
|
|
465
|
+
// report the install as timed out while it carried on writing to
|
|
466
|
+
// node_modules. killTree is what reaches it (taskkill /T there, the same
|
|
467
|
+
// plain signal everywhere else).
|
|
468
|
+
//
|
|
469
|
+
// Leaving the verdict to 'close' was the other half of the same bug. 'close'
|
|
470
|
+
// waits for the stdio pipes, which the grandchild inherited, so on Windows it
|
|
471
|
+
// could arrive minutes after the deadline — or never, if the tree kill could
|
|
472
|
+
// not run at all — and until it did, /api/version kept reporting
|
|
473
|
+
// `state: "running"` with the UI spinning on "installing…" and the guard at
|
|
474
|
+
// the top of this function refusing every retry. When it finally arrived it
|
|
475
|
+
// carried the killed wrapper's status, so a five-minute timeout was announced
|
|
476
|
+
// to the user as "npm exited null".
|
|
477
|
+
let timedOut = false;
|
|
478
|
+
const timer = setTimeout(() => {
|
|
479
|
+
timedOut = true;
|
|
480
|
+
_upgrade = {
|
|
481
|
+
state: "failed",
|
|
482
|
+
command,
|
|
483
|
+
error: `timed out after ${Math.round(INSTALL_TIMEOUT_MS / 60_000)} minutes`,
|
|
484
|
+
at: Date.now(),
|
|
485
|
+
};
|
|
486
|
+
killTree(child);
|
|
487
|
+
}, INSTALL_TIMEOUT_MS);
|
|
465
488
|
timer.unref?.();
|
|
466
489
|
|
|
467
490
|
child.on("error", (e) => {
|
|
468
491
|
clearTimeout(timer);
|
|
492
|
+
// A kill can make the child emit one of these; whatever it says, the reason
|
|
493
|
+
// this install failed is the deadline that has already been reported.
|
|
494
|
+
if (timedOut) return;
|
|
469
495
|
_upgrade = { state: "failed", command, error: e?.message ?? String(e), at: Date.now() };
|
|
470
496
|
});
|
|
471
497
|
child.on("close", (code) => {
|
|
472
498
|
clearTimeout(timer);
|
|
473
499
|
if (code === 0) {
|
|
500
|
+
// A clean exit is a real install even if it lands after the deadline —
|
|
501
|
+
// npm finishing in the same breath as the timer is the one case where the
|
|
502
|
+
// files on disk disagree with the verdict above, and the files win.
|
|
503
|
+
//
|
|
474
504
|
// Deliberately does not restart anything. The new files on disk make
|
|
475
505
|
// installedVersion() disagree with the running one, and the ordinary
|
|
476
506
|
// drift path takes it from there — including its wait for an idle moment.
|
|
477
507
|
_upgrade = { state: "done", command, error: null, at: Date.now() };
|
|
478
|
-
} else {
|
|
508
|
+
} else if (!timedOut) {
|
|
479
509
|
_upgrade = { state: "failed", command, error: lastMeaningfulLine(err) || `npm exited ${code}`, at: Date.now() };
|
|
480
510
|
}
|
|
481
511
|
});
|