agent-dag 1.29.0 → 1.29.2
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-C7r26eKG.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-BLOOP_Fy.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.29.
|
|
3
|
+
"version": "1.29.2",
|
|
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
|
@@ -6,10 +6,18 @@
|
|
|
6
6
|
// ENOENT on Windows even though the tool is installed and on PATH — which
|
|
7
7
|
// looks exactly like "not installed" and is why this is worth a module.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// quote or an ampersand stops being an
|
|
12
|
-
//
|
|
9
|
+
// Resolving the extension ourselves keeps the argument vector intact, which
|
|
10
|
+
// blanket `shell: true` would not: it concatenates arguments into a command
|
|
11
|
+
// line, so an argument containing a quote or an ampersand stops being an
|
|
12
|
+
// argument.
|
|
13
|
+
//
|
|
14
|
+
// The exception is .cmd and .bat, which since Node 20.12 CANNOT be spawned
|
|
15
|
+
// without a shell at all — the fix for CVE-2024-27980 makes that throw EINVAL,
|
|
16
|
+
// synchronously, from inside execFile. Those are routed through cmd.exe the
|
|
17
|
+
// same way Node's own `shell: true` does it, with the arguments quoted here
|
|
18
|
+
// rather than pasted together. Getting this wrong is not a degraded feature:
|
|
19
|
+
// the throw escaped the retry path and took the whole process down on Windows
|
|
20
|
+
// before the server ever started.
|
|
13
21
|
import { execFile, spawn } from "node:child_process";
|
|
14
22
|
|
|
15
23
|
// Extensions Windows will execute, most specific first. `.com` is omitted —
|
|
@@ -28,29 +36,75 @@ function candidates(cmd) {
|
|
|
28
36
|
return known ? [known] : WIN_EXTS.map(ext => cmd + ext);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
/** Exported for tests: the platform is a parameter so both can be checked. */
|
|
40
|
+
export const isBatch = (file, platform = process.platform) =>
|
|
41
|
+
platform === "win32" && /\.(cmd|bat)$/i.test(file);
|
|
32
42
|
|
|
33
43
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
44
|
+
* Rewrite a batch-file invocation as a cmd.exe one.
|
|
45
|
+
*
|
|
46
|
+
* Mirrors what Node does internally for `shell: true` on Windows — comspec,
|
|
47
|
+
* /d /s /c, the whole command line as a single quoted argument, and
|
|
48
|
+
* windowsVerbatimArguments so Node does not quote it a second time. Each
|
|
49
|
+
* argument is quoted here, with embedded quotes doubled, which is the escape
|
|
50
|
+
* cmd.exe understands inside a quoted string.
|
|
51
|
+
*/
|
|
52
|
+
export function viaCmd(file, args) {
|
|
53
|
+
const q = (s) => `"${String(s).replace(/"/g, '""')}"`;
|
|
54
|
+
const line = [file, ...args].map(q).join(" ");
|
|
55
|
+
return {
|
|
56
|
+
file: process.env.comspec || process.env.ComSpec || "cmd.exe",
|
|
57
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
58
|
+
opts: { windowsVerbatimArguments: true },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Reasons to try the next candidate spelling rather than give up. EINVAL and
|
|
63
|
+
// UNKNOWN show up on Windows for a file that exists but cannot be executed the
|
|
64
|
+
// way it was asked for; both mean "not this one", not "no such tool".
|
|
65
|
+
export const tryNext = (err) =>
|
|
66
|
+
Boolean(err) && (err.code === "ENOENT" || err.code === "EACCES" ||
|
|
67
|
+
err.code === "EINVAL" || err.code === "UNKNOWN");
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run a command and collect its output. Never rejects, and never throws —
|
|
71
|
+
* failures come back as `{ ok: false }`, because every caller here is a poll or
|
|
72
|
+
* a UI action where a missing tool is an expected state rather than an
|
|
73
|
+
* exception. execFile can throw synchronously on Windows, so the call itself is
|
|
74
|
+
* guarded as well as its callback.
|
|
37
75
|
*/
|
|
38
76
|
export function run(cmd, args, { timeout = 20_000, maxBuffer = 4 << 20 } = {}) {
|
|
39
77
|
const tries = candidates(cmd);
|
|
40
78
|
return new Promise((resolve) => {
|
|
41
79
|
const attempt = (i) => {
|
|
42
|
-
|
|
43
|
-
(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
80
|
+
if (i >= tries.length) {
|
|
81
|
+
return resolve({ ok: false, code: "ENOENT", killed: false, stdout: "", stderr: "" });
|
|
82
|
+
}
|
|
83
|
+
const raw = tries[i];
|
|
84
|
+
const { file, args: argv, opts } = isBatch(raw)
|
|
85
|
+
? viaCmd(raw, args)
|
|
86
|
+
: { file: raw, args, opts: {} };
|
|
87
|
+
|
|
88
|
+
const done = (err, stdout, stderr) => {
|
|
89
|
+
if (err && tryNext(err) && i + 1 < tries.length) return attempt(i + 1);
|
|
90
|
+
if (!err) resolved.set(cmd, raw);
|
|
91
|
+
resolve({
|
|
92
|
+
ok: !err,
|
|
93
|
+
code: err?.code ?? 0,
|
|
94
|
+
killed: Boolean(err?.killed),
|
|
95
|
+
stdout: String(stdout ?? ""),
|
|
96
|
+
stderr: String(stderr ?? ""),
|
|
53
97
|
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
execFile(file, argv, { timeout, shell: false, windowsHide: true, maxBuffer, ...opts }, done);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
// Synchronous throw — the EINVAL case. Same handling as a callback
|
|
104
|
+
// error; letting it propagate here is what crashed the server, because
|
|
105
|
+
// this runs inside the previous attempt's error handler.
|
|
106
|
+
done(err, "", "");
|
|
107
|
+
}
|
|
54
108
|
};
|
|
55
109
|
attempt(0);
|
|
56
110
|
});
|
|
@@ -64,15 +118,18 @@ export function run(cmd, args, { timeout = 20_000, maxBuffer = 4 << 20 } = {}) {
|
|
|
64
118
|
export function runDetached(cmd, args) {
|
|
65
119
|
const tries = candidates(cmd);
|
|
66
120
|
const attempt = (i) => {
|
|
121
|
+
if (i >= tries.length) return;
|
|
122
|
+
const raw = tries[i];
|
|
123
|
+
const { file, args: argv, opts } = isBatch(raw)
|
|
124
|
+
? viaCmd(raw, args)
|
|
125
|
+
: { file: raw, args, opts: {} };
|
|
67
126
|
try {
|
|
68
|
-
const child = spawn(
|
|
69
|
-
child.on("error", (err) => {
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
child.on("spawn", () => resolved.set(cmd, tries[i]));
|
|
127
|
+
const child = spawn(file, argv, { stdio: "ignore", shell: false, windowsHide: true, ...opts });
|
|
128
|
+
child.on("error", (err) => { if (tryNext(err)) attempt(i + 1); });
|
|
129
|
+
child.on("spawn", () => resolved.set(cmd, raw));
|
|
73
130
|
child.unref?.();
|
|
74
131
|
} catch {
|
|
75
|
-
|
|
132
|
+
attempt(i + 1);
|
|
76
133
|
}
|
|
77
134
|
};
|
|
78
135
|
attempt(0);
|