@cydm/magic-shell-agent-node 0.1.18 → 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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local-direct-server.js +2 -21
- package/dist/node.js +33 -4
- package/dist/plugin-loader.js +8 -17
- package/dist/primary-agent-bridge.d.ts +0 -1
- package/dist/primary-agent-bridge.js +35 -5
- package/dist/primary-pie-extension/magic-shell-agent/index.js +1019 -0
- package/dist/primary-pie-extension/magic-shell-agent/package.json +9 -0
- package/dist/runtime-assets.d.ts +4 -0
- package/dist/runtime-assets.js +83 -0
- 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/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { existsSync, createReadStream } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { WebSocketServer, WebSocket } from "ws";
|
|
5
|
+
import { getWorkbenchRoot } from "./runtime-assets.js";
|
|
6
6
|
function createConnectionId() {
|
|
7
7
|
return `local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8
8
|
}
|
|
@@ -21,25 +21,6 @@ function guessContentType(filePath) {
|
|
|
21
21
|
return "image/png";
|
|
22
22
|
return "text/plain; charset=utf-8";
|
|
23
23
|
}
|
|
24
|
-
function findWorkbenchRoot() {
|
|
25
|
-
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
const envOverride = process.env.MAGIC_SHELL_WORKBENCH_ROOT;
|
|
27
|
-
const candidates = [
|
|
28
|
-
envOverride ? path.resolve(envOverride) : null,
|
|
29
|
-
path.resolve(currentDir, "./workbench"),
|
|
30
|
-
path.resolve(currentDir, "../dist/workbench"),
|
|
31
|
-
path.resolve(currentDir, "../workbench"),
|
|
32
|
-
path.resolve(currentDir, "../../../apps/web/src"),
|
|
33
|
-
path.resolve(currentDir, "../../../../apps/web/src"),
|
|
34
|
-
path.resolve(process.cwd(), "apps/web/src"),
|
|
35
|
-
].filter((value) => Boolean(value));
|
|
36
|
-
for (const candidate of candidates) {
|
|
37
|
-
if (existsSync(path.join(candidate, "index.html"))) {
|
|
38
|
-
return candidate;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
24
|
export class LocalDirectServer {
|
|
44
25
|
options;
|
|
45
26
|
delegate;
|
|
@@ -47,7 +28,7 @@ export class LocalDirectServer {
|
|
|
47
28
|
wsServer = new WebSocketServer({ noServer: true });
|
|
48
29
|
connections = new Map();
|
|
49
30
|
sessionToConnections = new Map();
|
|
50
|
-
workbenchRoot =
|
|
31
|
+
workbenchRoot = getWorkbenchRoot();
|
|
51
32
|
constructor(options, delegate) {
|
|
52
33
|
this.options = options;
|
|
53
34
|
this.delegate = delegate;
|
package/dist/node.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { WebSocketClient } from "./ws-client.js";
|
|
2
3
|
import { SessionManager } from "./session-manager.js";
|
|
3
4
|
import { LocalDirectServer } from "./local-direct-server.js";
|
|
@@ -16,9 +17,10 @@ import { runClaudeExec } from "./claude-exec.js";
|
|
|
16
17
|
import { codexNeedsTrustConfirmation, getLastCodexWorkerMessage, isCodexReadyForTask, isCodexWorker, summarizeCodexWorkerOutput, } from "./codex-worker.js";
|
|
17
18
|
import { runCodexExec } from "./codex-exec.js";
|
|
18
19
|
import { extractTerminalMetadata, normalizeWorkerTitleCandidate, } from "./terminal-metadata.js";
|
|
19
|
-
import { existsSync } from "node:fs";
|
|
20
|
+
import { chmodSync, existsSync, readdirSync } from "node:fs";
|
|
20
21
|
import os from "node:os";
|
|
21
22
|
import path from "node:path";
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
22
24
|
function generateSessionId() {
|
|
23
25
|
return `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
24
26
|
}
|
|
@@ -84,6 +86,7 @@ export class AgentNode {
|
|
|
84
86
|
console.log(`[AgentNode] Starting...`);
|
|
85
87
|
console.log(`[AgentNode] Node ID: ${this.options.nodeId}`);
|
|
86
88
|
console.log(`[AgentNode] Relay: ${this.options.relayUrl}`);
|
|
89
|
+
ensureSpawnHelperExecutable();
|
|
87
90
|
// 1. 加载插件
|
|
88
91
|
this.plugins = loadPlugins({ dir: this.options.pluginDir });
|
|
89
92
|
if (this.plugins.size === 0) {
|
|
@@ -304,9 +307,7 @@ export class AgentNode {
|
|
|
304
307
|
sessionManager: this.sessionManager,
|
|
305
308
|
workerRegistry: this.workerRegistry,
|
|
306
309
|
});
|
|
307
|
-
|
|
308
|
-
void this.primeInteractiveWorkerSession(options.sessionId).catch(() => { });
|
|
309
|
-
}
|
|
310
|
+
void this.primeInteractiveWorkerSession(options.sessionId).catch(() => { });
|
|
310
311
|
if (options.taskSummary) {
|
|
311
312
|
await this.dispatchInitialWorkerTask(options.sessionId, options.taskSummary);
|
|
312
313
|
}
|
|
@@ -1933,6 +1934,34 @@ Environment Variables:
|
|
|
1933
1934
|
MAGIC_SHELL_DISABLE_LOCAL_DIRECT Set to 1 to disable the local direct server
|
|
1934
1935
|
`);
|
|
1935
1936
|
}
|
|
1937
|
+
function ensureSpawnHelperExecutable() {
|
|
1938
|
+
try {
|
|
1939
|
+
const pkgPath = require.resolve("node-pty/package.json");
|
|
1940
|
+
const prebuildRoot = path.join(path.dirname(pkgPath), "prebuilds");
|
|
1941
|
+
if (!existsSync(prebuildRoot))
|
|
1942
|
+
return;
|
|
1943
|
+
for (const entry of walk(prebuildRoot)) {
|
|
1944
|
+
if (path.basename(entry) === "spawn-helper") {
|
|
1945
|
+
chmodSync(entry, 0o755);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
catch {
|
|
1950
|
+
// Ignore best-effort fixups during startup.
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
function walk(dir) {
|
|
1954
|
+
const files = [];
|
|
1955
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1956
|
+
const fullPath = path.join(dir, entry.name);
|
|
1957
|
+
if (entry.isDirectory()) {
|
|
1958
|
+
files.push(...walk(fullPath));
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
files.push(fullPath);
|
|
1962
|
+
}
|
|
1963
|
+
return files;
|
|
1964
|
+
}
|
|
1936
1965
|
// 主函数
|
|
1937
1966
|
async function main() {
|
|
1938
1967
|
const options = parseArgs();
|
package/dist/plugin-loader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
2
2
|
import { join, extname, dirname, isAbsolute, resolve } from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
3
|
import { createRequire } from "module";
|
|
4
|
+
import { getPackagedPluginDir } from "./runtime-assets.js";
|
|
5
5
|
const require = createRequire(import.meta.url);
|
|
6
6
|
/**
|
|
7
7
|
* 加载单个插件配置
|
|
@@ -39,9 +39,6 @@ export function loadPlugin(path) {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
function normalizePluginConfigForRuntime(config) {
|
|
42
|
-
if (process.platform !== "win32") {
|
|
43
|
-
return config;
|
|
44
|
-
}
|
|
45
42
|
if (config.name === "pie") {
|
|
46
43
|
const pieEntry = resolvePackageBinEntry("@cydm/pie", "pie");
|
|
47
44
|
if (pieEntry) {
|
|
@@ -52,6 +49,9 @@ function normalizePluginConfigForRuntime(config) {
|
|
|
52
49
|
};
|
|
53
50
|
}
|
|
54
51
|
}
|
|
52
|
+
if (process.platform !== "win32") {
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
55
|
if ((config.name === "claude-code" || config.name === "codex") && isShellCommand(config.command)) {
|
|
56
56
|
return {
|
|
57
57
|
...config,
|
|
@@ -174,18 +174,9 @@ export function loadPlugins(options) {
|
|
|
174
174
|
* 获取默认插件目录
|
|
175
175
|
*/
|
|
176
176
|
export function getDefaultPluginDir() {
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
const packagePluginDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "plugins");
|
|
182
|
-
if (existsSync(packagePluginDir)) {
|
|
183
|
-
return packagePluginDir;
|
|
184
|
-
}
|
|
185
|
-
const distPluginDir = resolve(dirname(fileURLToPath(import.meta.url)), "plugins");
|
|
186
|
-
if (existsSync(distPluginDir)) {
|
|
187
|
-
return distPluginDir;
|
|
177
|
+
const pluginDir = getPackagedPluginDir();
|
|
178
|
+
if (pluginDir) {
|
|
179
|
+
return pluginDir;
|
|
188
180
|
}
|
|
189
|
-
|
|
190
|
-
return join(process.cwd(), "plugins");
|
|
181
|
+
throw new Error("Magic Shell plugin assets are unavailable. Set MAGIC_SHELL_PLUGINS_DIR or run from a built source checkout.");
|
|
191
182
|
}
|
|
@@ -55,7 +55,6 @@ export interface PrimarySessionSnapshot {
|
|
|
55
55
|
lastMessageRole?: string;
|
|
56
56
|
lastAssistantHasToolCall?: boolean;
|
|
57
57
|
}
|
|
58
|
-
export declare function getPrimaryPieExtensionDistDir(): string;
|
|
59
58
|
export declare function withPrimaryPieExtensionPath(plugin: PluginConfig): PluginConfig;
|
|
60
59
|
export declare function buildPrimaryPrompt(text: string, context: PrimaryAgentPromptContext): string;
|
|
61
60
|
export declare function buildPrimarySessionPrompt(text: string, context: PrimarySessionPromptContext): string;
|
|
@@ -2,20 +2,50 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import { getMagicShellCliPath, getPrimaryPieExtensionDistDir } from "./runtime-assets.js";
|
|
6
|
+
function getPrimaryPieExtensionPackageDir(extensionRoot) {
|
|
7
|
+
const packagedDir = path.join(extensionRoot, "magic-shell-agent");
|
|
8
|
+
if (fs.existsSync(path.join(packagedDir, "package.json"))) {
|
|
9
|
+
return packagedDir;
|
|
10
|
+
}
|
|
11
|
+
if (fs.existsSync(path.join(extensionRoot, "package.json"))) {
|
|
12
|
+
return extensionRoot;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function ensurePieExtensionDiscovery(extensionRoot) {
|
|
17
|
+
const extensionPackageDir = getPrimaryPieExtensionPackageDir(extensionRoot);
|
|
18
|
+
if (!extensionPackageDir) {
|
|
19
|
+
throw new Error(`Primary PIE extension package is missing under ${extensionRoot}`);
|
|
20
|
+
}
|
|
21
|
+
const targetDir = path.join(os.homedir(), ".pie", "extensions", path.basename(extensionPackageDir));
|
|
22
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
23
|
+
fs.mkdirSync(path.dirname(targetDir), { recursive: true });
|
|
24
|
+
fs.cpSync(extensionPackageDir, targetDir, { recursive: true });
|
|
25
|
+
return targetDir;
|
|
10
26
|
}
|
|
11
27
|
export function withPrimaryPieExtensionPath(plugin) {
|
|
12
28
|
if (plugin.name !== "pie") {
|
|
13
29
|
return plugin;
|
|
14
30
|
}
|
|
15
31
|
const extensionDir = getPrimaryPieExtensionDistDir();
|
|
32
|
+
if (!extensionDir) {
|
|
33
|
+
throw new Error("Primary PIE extension assets are unavailable. Use the packaged @cydm/magic-shell runtime or build packages/primary-pie-extension first.");
|
|
34
|
+
}
|
|
35
|
+
const cliPath = getMagicShellCliPath();
|
|
36
|
+
if (!cliPath) {
|
|
37
|
+
throw new Error("Magic Shell CLI runtime is unavailable for the primary PIE extension. Start the node via `magic-shell node start` or set MAGIC_SHELL_CLI_PATH.");
|
|
38
|
+
}
|
|
39
|
+
const installedExtensionDir = ensurePieExtensionDiscovery(extensionDir);
|
|
16
40
|
return {
|
|
17
41
|
...plugin,
|
|
18
42
|
args: [...(plugin.args || []), "--extension-path", extensionDir],
|
|
43
|
+
env: {
|
|
44
|
+
...(plugin.env || {}),
|
|
45
|
+
MAGIC_SHELL_CLI_PATH: cliPath,
|
|
46
|
+
MAGIC_SHELL_PRIMARY_EXTENSION_DIR: extensionDir,
|
|
47
|
+
MAGIC_SHELL_PRIMARY_EXTENSION_PACKAGE_DIR: installedExtensionDir,
|
|
48
|
+
},
|
|
19
49
|
};
|
|
20
50
|
}
|
|
21
51
|
export function buildPrimaryPrompt(text, context) {
|