@cydm/magic-shell-agent-node 0.1.19 → 0.1.20
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/adapters/pty-adapter.js +27 -93
- package/dist/adapters/rpc-adapter.js +39 -0
- package/dist/adapters/stdio-adapter.js +39 -0
- package/dist/claude-exec.js +15 -3
- package/dist/codex-exec.js +15 -3
- package/dist/command-resolution.d.ts +4 -0
- package/dist/command-resolution.js +85 -0
- package/dist/node.js +1 -3
- package/package.json +1 -1
|
@@ -1,97 +1,29 @@
|
|
|
1
1
|
import { spawn } from "node-pty";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return `"${value.replace(/"/g, '\\"')}"`;
|
|
16
|
-
}
|
|
17
|
-
function resolvePackageBinScript(packageName, binName) {
|
|
18
|
-
try {
|
|
19
|
-
const packageJsonPath = require.resolve(`${packageName}/package.json`);
|
|
20
|
-
const packageDir = path.dirname(packageJsonPath);
|
|
21
|
-
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
22
|
-
const binField = typeof pkg.bin === "string"
|
|
23
|
-
? pkg.bin
|
|
24
|
-
: pkg.bin && typeof pkg.bin[binName] === "string"
|
|
25
|
-
? pkg.bin[binName]
|
|
26
|
-
: null;
|
|
27
|
-
if (!binField)
|
|
28
|
-
return null;
|
|
29
|
-
const entry = path.resolve(packageDir, binField);
|
|
30
|
-
return existsSync(entry) ? entry : null;
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function resolveWindowsCommand(command, args) {
|
|
37
|
-
if (!command || process.platform !== "win32") {
|
|
38
|
-
return { command, args };
|
|
39
|
-
}
|
|
40
|
-
if (command === "pie") {
|
|
41
|
-
const pieEntry = resolvePackageBinScript("@cydm/pie", "pie");
|
|
42
|
-
if (pieEntry) {
|
|
43
|
-
return {
|
|
44
|
-
command: process.execPath,
|
|
45
|
-
args: [pieEntry, ...args],
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const lowerCommand = command.toLowerCase();
|
|
50
|
-
if (lowerCommand.endsWith(".cmd") || lowerCommand.endsWith(".bat")) {
|
|
51
|
-
return {
|
|
52
|
-
command: DEFAULT_WINDOWS_SHELL,
|
|
53
|
-
args: ["/d", "/s", "/c", [quoteWindowsArg(command), ...args.map(quoteWindowsArg)].join(" ")],
|
|
2
|
+
import { resolveRuntimeCommand } from "../command-resolution.js";
|
|
3
|
+
const STOP_TIMEOUT_MS = 5_000;
|
|
4
|
+
function waitForPtyExit(agent) {
|
|
5
|
+
if (agent.stopPromise) {
|
|
6
|
+
return agent.stopPromise;
|
|
7
|
+
}
|
|
8
|
+
agent.stopPromise = new Promise((resolve) => {
|
|
9
|
+
let settled = false;
|
|
10
|
+
const finish = () => {
|
|
11
|
+
if (settled)
|
|
12
|
+
return;
|
|
13
|
+
settled = true;
|
|
14
|
+
resolve();
|
|
54
15
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (existsSync(bareCandidate)) {
|
|
67
|
-
return { command: bareCandidate, args };
|
|
68
|
-
}
|
|
69
|
-
for (const ext of pathext) {
|
|
70
|
-
const candidate = `${entry}\\${command}${ext.toLowerCase()}`;
|
|
71
|
-
if (existsSync(candidate)) {
|
|
72
|
-
const lowerExt = ext.toLowerCase();
|
|
73
|
-
if (lowerExt === ".cmd" || lowerExt === ".bat") {
|
|
74
|
-
return {
|
|
75
|
-
command: DEFAULT_WINDOWS_SHELL,
|
|
76
|
-
args: ["/d", "/s", "/c", [quoteWindowsArg(candidate), ...args.map(quoteWindowsArg)].join(" ")],
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
return { command: candidate, args };
|
|
80
|
-
}
|
|
81
|
-
const candidateUpper = `${entry}\\${command}${ext.toUpperCase()}`;
|
|
82
|
-
if (existsSync(candidateUpper)) {
|
|
83
|
-
const upperExt = ext.toUpperCase();
|
|
84
|
-
if (upperExt === ".CMD" || upperExt === ".BAT") {
|
|
85
|
-
return {
|
|
86
|
-
command: DEFAULT_WINDOWS_SHELL,
|
|
87
|
-
args: ["/d", "/s", "/c", [quoteWindowsArg(candidateUpper), ...args.map(quoteWindowsArg)].join(" ")],
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
return { command: candidateUpper, args };
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return { command, args };
|
|
16
|
+
const timeout = setTimeout(() => {
|
|
17
|
+
console.warn(`[PtyAdapter] Timed out waiting for ${agent.id} to exit`);
|
|
18
|
+
finish();
|
|
19
|
+
}, STOP_TIMEOUT_MS);
|
|
20
|
+
timeout.unref?.();
|
|
21
|
+
agent.pty.onExit(() => {
|
|
22
|
+
clearTimeout(timeout);
|
|
23
|
+
finish();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
return agent.stopPromise;
|
|
95
27
|
}
|
|
96
28
|
export class PtyAdapter {
|
|
97
29
|
name = "pty";
|
|
@@ -106,7 +38,7 @@ export class PtyAdapter {
|
|
|
106
38
|
}
|
|
107
39
|
async start(config) {
|
|
108
40
|
const agentId = `pty-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
109
|
-
const resolved =
|
|
41
|
+
const resolved = resolveRuntimeCommand(config.command, config.args || []);
|
|
110
42
|
const pty = spawn(resolved.command, resolved.args, {
|
|
111
43
|
name: "xterm-256color",
|
|
112
44
|
cols: 80,
|
|
@@ -152,7 +84,9 @@ export class PtyAdapter {
|
|
|
152
84
|
const agent = this.agents.get(agentId);
|
|
153
85
|
if (!agent)
|
|
154
86
|
return;
|
|
87
|
+
const exitPromise = waitForPtyExit(agent);
|
|
155
88
|
agent.pty.kill();
|
|
89
|
+
await exitPromise;
|
|
156
90
|
this.agents.delete(agentId);
|
|
157
91
|
console.log(`[PtyAdapter] Stopped ${agentId}`);
|
|
158
92
|
}
|
|
@@ -1,4 +1,41 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
+
const STOP_TIMEOUT_MS = 5_000;
|
|
3
|
+
function waitForProcessExit(agent) {
|
|
4
|
+
if (agent.stopPromise) {
|
|
5
|
+
return agent.stopPromise;
|
|
6
|
+
}
|
|
7
|
+
agent.stopPromise = new Promise((resolve) => {
|
|
8
|
+
if (agent.process.exitCode !== null || agent.process.killed) {
|
|
9
|
+
resolve();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
let settled = false;
|
|
13
|
+
const finish = () => {
|
|
14
|
+
if (settled)
|
|
15
|
+
return;
|
|
16
|
+
settled = true;
|
|
17
|
+
resolve();
|
|
18
|
+
};
|
|
19
|
+
const timeout = setTimeout(() => {
|
|
20
|
+
console.warn(`[RpcAdapter] Timed out waiting for ${agent.id} to exit`);
|
|
21
|
+
finish();
|
|
22
|
+
}, STOP_TIMEOUT_MS);
|
|
23
|
+
timeout.unref?.();
|
|
24
|
+
agent.process.once("close", () => {
|
|
25
|
+
clearTimeout(timeout);
|
|
26
|
+
finish();
|
|
27
|
+
});
|
|
28
|
+
agent.process.once("exit", () => {
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
finish();
|
|
31
|
+
});
|
|
32
|
+
agent.process.once("error", () => {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
finish();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return agent.stopPromise;
|
|
38
|
+
}
|
|
2
39
|
export class RpcAdapter {
|
|
3
40
|
name = "rpc";
|
|
4
41
|
description = "JSON-RPC adapter for line-oriented RPC workers";
|
|
@@ -141,7 +178,9 @@ export class RpcAdapter {
|
|
|
141
178
|
const agent = this.agents.get(agentId);
|
|
142
179
|
if (!agent)
|
|
143
180
|
return;
|
|
181
|
+
const exitPromise = waitForProcessExit(agent);
|
|
144
182
|
agent.process.kill();
|
|
183
|
+
await exitPromise;
|
|
145
184
|
this.agents.delete(agentId);
|
|
146
185
|
console.log(`[RpcAdapter] Stopped ${agentId}`);
|
|
147
186
|
}
|
|
@@ -1,4 +1,41 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
+
const STOP_TIMEOUT_MS = 5_000;
|
|
3
|
+
function waitForProcessExit(agent) {
|
|
4
|
+
if (agent.stopPromise) {
|
|
5
|
+
return agent.stopPromise;
|
|
6
|
+
}
|
|
7
|
+
agent.stopPromise = new Promise((resolve) => {
|
|
8
|
+
if (agent.process.exitCode !== null) {
|
|
9
|
+
resolve();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
let settled = false;
|
|
13
|
+
const finish = () => {
|
|
14
|
+
if (settled)
|
|
15
|
+
return;
|
|
16
|
+
settled = true;
|
|
17
|
+
resolve();
|
|
18
|
+
};
|
|
19
|
+
const timeout = setTimeout(() => {
|
|
20
|
+
console.warn(`[StdioAdapter] Timed out waiting for ${agent.id} to exit`);
|
|
21
|
+
finish();
|
|
22
|
+
}, STOP_TIMEOUT_MS);
|
|
23
|
+
timeout.unref?.();
|
|
24
|
+
agent.process.once("close", () => {
|
|
25
|
+
clearTimeout(timeout);
|
|
26
|
+
finish();
|
|
27
|
+
});
|
|
28
|
+
agent.process.once("exit", () => {
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
finish();
|
|
31
|
+
});
|
|
32
|
+
agent.process.once("error", () => {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
finish();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return agent.stopPromise;
|
|
38
|
+
}
|
|
2
39
|
export class StdioAdapter {
|
|
3
40
|
name = "stdio";
|
|
4
41
|
description = "Stdio pipe adapter for CLI agents";
|
|
@@ -72,7 +109,9 @@ export class StdioAdapter {
|
|
|
72
109
|
const agent = this.agents.get(agentId);
|
|
73
110
|
if (!agent)
|
|
74
111
|
return;
|
|
112
|
+
const exitPromise = waitForProcessExit(agent);
|
|
75
113
|
agent.process.kill();
|
|
114
|
+
await exitPromise;
|
|
76
115
|
this.agents.delete(agentId);
|
|
77
116
|
console.log(`[StdioAdapter] Stopped ${agentId}`);
|
|
78
117
|
}
|
package/dist/claude-exec.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveRuntimeCommand } from "./command-resolution.js";
|
|
3
|
+
import { claudeNeedsTrustConfirmation } from "./claude-worker.js";
|
|
2
4
|
export async function runClaudeExec(options) {
|
|
3
5
|
return new Promise((resolve, reject) => {
|
|
4
|
-
const
|
|
6
|
+
const resolved = resolveRuntimeCommand("claude", [
|
|
5
7
|
"-p",
|
|
6
8
|
"--dangerously-skip-permissions",
|
|
7
9
|
options.message,
|
|
8
|
-
]
|
|
10
|
+
]);
|
|
11
|
+
const child = spawn(resolved.command, resolved.args, {
|
|
9
12
|
cwd: options.cwd,
|
|
10
13
|
env: process.env,
|
|
11
|
-
stdio: ["
|
|
14
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
12
15
|
});
|
|
13
16
|
let stdout = "";
|
|
14
17
|
let stderr = "";
|
|
15
18
|
let settled = false;
|
|
19
|
+
let trustConfirmed = false;
|
|
16
20
|
const finish = (handler) => {
|
|
17
21
|
if (settled)
|
|
18
22
|
return;
|
|
@@ -22,9 +26,17 @@ export async function runClaudeExec(options) {
|
|
|
22
26
|
};
|
|
23
27
|
child.stdout.on("data", (chunk) => {
|
|
24
28
|
stdout += chunk.toString();
|
|
29
|
+
if (!trustConfirmed && claudeNeedsTrustConfirmation([stdout, stderr].join("\n"))) {
|
|
30
|
+
trustConfirmed = true;
|
|
31
|
+
child.stdin?.write("\r");
|
|
32
|
+
}
|
|
25
33
|
});
|
|
26
34
|
child.stderr.on("data", (chunk) => {
|
|
27
35
|
stderr += chunk.toString();
|
|
36
|
+
if (!trustConfirmed && claudeNeedsTrustConfirmation([stdout, stderr].join("\n"))) {
|
|
37
|
+
trustConfirmed = true;
|
|
38
|
+
child.stdin?.write("\r");
|
|
39
|
+
}
|
|
28
40
|
});
|
|
29
41
|
child.on("error", (error) => {
|
|
30
42
|
finish(() => reject(error));
|
package/dist/codex-exec.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveRuntimeCommand } from "./command-resolution.js";
|
|
3
|
+
import { codexNeedsTrustConfirmation } from "./codex-worker.js";
|
|
2
4
|
function isRecord(value) {
|
|
3
5
|
return !!value && typeof value === "object";
|
|
4
6
|
}
|
|
@@ -32,7 +34,7 @@ export function extractCodexExecResult(rawOutput) {
|
|
|
32
34
|
}
|
|
33
35
|
export async function runCodexExec(options) {
|
|
34
36
|
return new Promise((resolve, reject) => {
|
|
35
|
-
const
|
|
37
|
+
const resolved = resolveRuntimeCommand("codex", [
|
|
36
38
|
"exec",
|
|
37
39
|
"--json",
|
|
38
40
|
"--skip-git-repo-check",
|
|
@@ -40,14 +42,16 @@ export async function runCodexExec(options) {
|
|
|
40
42
|
"-C",
|
|
41
43
|
options.cwd,
|
|
42
44
|
options.message,
|
|
43
|
-
]
|
|
45
|
+
]);
|
|
46
|
+
const child = spawn(resolved.command, resolved.args, {
|
|
44
47
|
cwd: options.cwd,
|
|
45
48
|
env: process.env,
|
|
46
|
-
stdio: ["
|
|
49
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
47
50
|
});
|
|
48
51
|
let stdout = "";
|
|
49
52
|
let stderr = "";
|
|
50
53
|
let settled = false;
|
|
54
|
+
let trustConfirmed = false;
|
|
51
55
|
const finish = (handler) => {
|
|
52
56
|
if (settled)
|
|
53
57
|
return;
|
|
@@ -57,9 +61,17 @@ export async function runCodexExec(options) {
|
|
|
57
61
|
};
|
|
58
62
|
child.stdout.on("data", (chunk) => {
|
|
59
63
|
stdout += chunk.toString();
|
|
64
|
+
if (!trustConfirmed && codexNeedsTrustConfirmation([stdout, stderr].join("\n"))) {
|
|
65
|
+
trustConfirmed = true;
|
|
66
|
+
child.stdin?.write("\r");
|
|
67
|
+
}
|
|
60
68
|
});
|
|
61
69
|
child.stderr.on("data", (chunk) => {
|
|
62
70
|
stderr += chunk.toString();
|
|
71
|
+
if (!trustConfirmed && codexNeedsTrustConfirmation([stdout, stderr].join("\n"))) {
|
|
72
|
+
trustConfirmed = true;
|
|
73
|
+
child.stdin?.write("\r");
|
|
74
|
+
}
|
|
63
75
|
});
|
|
64
76
|
child.on("error", (error) => {
|
|
65
77
|
finish(() => reject(error));
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const DEFAULT_WINDOWS_SHELL = process.env.ComSpec || "C:\\Windows\\System32\\cmd.exe";
|
|
6
|
+
function quoteWindowsArg(value) {
|
|
7
|
+
if (!value)
|
|
8
|
+
return "\"\"";
|
|
9
|
+
if (!/[\s"]/u.test(value))
|
|
10
|
+
return value;
|
|
11
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
12
|
+
}
|
|
13
|
+
function resolvePackageBinScript(packageName, binName) {
|
|
14
|
+
try {
|
|
15
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`);
|
|
16
|
+
const packageDir = path.dirname(packageJsonPath);
|
|
17
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
18
|
+
const binField = typeof pkg.bin === "string"
|
|
19
|
+
? pkg.bin
|
|
20
|
+
: pkg.bin && typeof pkg.bin[binName] === "string"
|
|
21
|
+
? pkg.bin[binName]
|
|
22
|
+
: null;
|
|
23
|
+
if (!binField)
|
|
24
|
+
return null;
|
|
25
|
+
const entry = path.resolve(packageDir, binField);
|
|
26
|
+
return existsSync(entry) ? entry : null;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function resolveRuntimeCommand(command, args = []) {
|
|
33
|
+
if (!command || process.platform !== "win32") {
|
|
34
|
+
return { command, args };
|
|
35
|
+
}
|
|
36
|
+
if (command === "pie") {
|
|
37
|
+
const pieEntry = resolvePackageBinScript("@cydm/pie", "pie");
|
|
38
|
+
if (pieEntry) {
|
|
39
|
+
return {
|
|
40
|
+
command: process.execPath,
|
|
41
|
+
args: [pieEntry, ...args],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const lowerCommand = command.toLowerCase();
|
|
46
|
+
if (lowerCommand.endsWith(".cmd") || lowerCommand.endsWith(".bat")) {
|
|
47
|
+
return {
|
|
48
|
+
command: DEFAULT_WINDOWS_SHELL,
|
|
49
|
+
args: ["/d", "/s", "/c", [quoteWindowsArg(command), ...args.map(quoteWindowsArg)].join(" ")],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (command.includes("\\") || command.includes("/") || /^[a-zA-Z]:/.test(command)) {
|
|
53
|
+
return { command, args };
|
|
54
|
+
}
|
|
55
|
+
const pathEntries = (process.env.PATH || "").split(";").filter(Boolean);
|
|
56
|
+
const pathext = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
|
|
57
|
+
.split(";")
|
|
58
|
+
.map((value) => value.trim())
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
for (const entry of pathEntries) {
|
|
61
|
+
const bareCandidate = `${entry}\\${command}`;
|
|
62
|
+
if (existsSync(bareCandidate)) {
|
|
63
|
+
return { command: bareCandidate, args };
|
|
64
|
+
}
|
|
65
|
+
for (const ext of pathext) {
|
|
66
|
+
const candidates = [
|
|
67
|
+
`${entry}\\${command}${ext.toLowerCase()}`,
|
|
68
|
+
`${entry}\\${command}${ext.toUpperCase()}`,
|
|
69
|
+
];
|
|
70
|
+
for (const candidate of candidates) {
|
|
71
|
+
if (!existsSync(candidate))
|
|
72
|
+
continue;
|
|
73
|
+
const lowerExt = path.extname(candidate).toLowerCase();
|
|
74
|
+
if (lowerExt === ".cmd" || lowerExt === ".bat") {
|
|
75
|
+
return {
|
|
76
|
+
command: DEFAULT_WINDOWS_SHELL,
|
|
77
|
+
args: ["/d", "/s", "/c", [quoteWindowsArg(candidate), ...args.map(quoteWindowsArg)].join(" ")],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { command: candidate, args };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { command, args };
|
|
85
|
+
}
|
package/dist/node.js
CHANGED
|
@@ -307,9 +307,7 @@ export class AgentNode {
|
|
|
307
307
|
sessionManager: this.sessionManager,
|
|
308
308
|
workerRegistry: this.workerRegistry,
|
|
309
309
|
});
|
|
310
|
-
|
|
311
|
-
void this.primeInteractiveWorkerSession(options.sessionId).catch(() => { });
|
|
312
|
-
}
|
|
310
|
+
void this.primeInteractiveWorkerSession(options.sessionId).catch(() => { });
|
|
313
311
|
if (options.taskSummary) {
|
|
314
312
|
await this.dispatchInitialWorkerTask(options.sessionId, options.taskSummary);
|
|
315
313
|
}
|