@cydm/magic-shell-agent-node 0.1.3 → 0.1.5

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.
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node-pty";
2
- import { dirname } from "path";
2
+ import { delimiter, dirname, join } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  const __filename = fileURLToPath(import.meta.url);
5
5
  const __dirname = dirname(__filename);
@@ -16,13 +16,26 @@ export class PtyAdapter {
16
16
  }
17
17
  async start(config) {
18
18
  const agentId = `pty-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
19
- const pty = spawn(config.command, config.args || [], {
20
- name: "xterm-256color",
21
- cols: 80,
22
- rows: 24,
23
- cwd: config.cwd || process.cwd(),
24
- env: { ...process.env, ...config.env },
25
- });
19
+ const spawnCwd = config.cwd || process.cwd();
20
+ const spawnEnv = { ...process.env, ...config.env };
21
+ console.log(`[PtyAdapter] Starting ${agentId}: command=${JSON.stringify(config.command)} args=${JSON.stringify(config.args || [])} cwd=${JSON.stringify(spawnCwd)} platform=${process.platform}`);
22
+ if (process.platform === "win32" && !isAbsoluteOrQualifiedCommand(config.command)) {
23
+ console.log(`[PtyAdapter] Windows bare command probe for ${agentId}: ${JSON.stringify(findWindowsCommandCandidates(config.command, spawnEnv.PATH))}`);
24
+ }
25
+ let pty;
26
+ try {
27
+ pty = spawn(config.command, config.args || [], {
28
+ name: "xterm-256color",
29
+ cols: 80,
30
+ rows: 24,
31
+ cwd: spawnCwd,
32
+ env: spawnEnv,
33
+ });
34
+ }
35
+ catch (error) {
36
+ console.error(`[PtyAdapter] Failed to start ${agentId}:`, error instanceof Error ? error.stack || error.message : error);
37
+ throw error;
38
+ }
26
39
  const agent = {
27
40
  id: agentId,
28
41
  pty,
@@ -97,3 +110,21 @@ export class PtyAdapter {
97
110
  this.exitCallbacks.push(callback);
98
111
  }
99
112
  }
113
+ function isAbsoluteOrQualifiedCommand(command) {
114
+ return command.includes("/") || command.includes("\\") || /\.(exe|cmd|bat|com|ps1)$/i.test(command);
115
+ }
116
+ function findWindowsCommandCandidates(command, pathValue) {
117
+ if (!pathValue)
118
+ return [];
119
+ const pathext = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD;.PS1")
120
+ .split(";")
121
+ .map((value) => value.trim())
122
+ .filter(Boolean);
123
+ const results = [];
124
+ for (const dir of pathValue.split(delimiter).filter(Boolean)) {
125
+ for (const ext of ["", ...pathext]) {
126
+ results.push(join(dir, ext ? `${command}${ext}` : command));
127
+ }
128
+ }
129
+ return results.slice(0, 20);
130
+ }
package/dist/node.js CHANGED
@@ -641,11 +641,13 @@ export class AgentNode {
641
641
  const cwd = this.readString(payload.cwd) || message.cwd;
642
642
  const displayName = this.readString(payload.displayName) || message.displayName;
643
643
  const sessionId = this.readString(payload.sessionId) || message.target?.sessionId || generateSessionId();
644
+ console.log(`[AgentNode] control spawn_worker start: session=${sessionId} plugin=${pluginName} cwd=${JSON.stringify(cwd || "")} displayName=${JSON.stringify(displayName || "")} task=${JSON.stringify(taskSummary || "")}`);
644
645
  try {
645
646
  await this.spawnWorker({ sessionId, pluginName, cwd, displayName, taskSummary });
646
647
  if (taskSummary) {
647
648
  this.rememberNodeSpawn(taskSummary, sessionId);
648
649
  }
650
+ console.log(`[AgentNode] control spawn_worker success: session=${sessionId} plugin=${pluginName}`);
649
651
  this.sendControlResult(source, message.requestId, name, true, {
650
652
  sessionId,
651
653
  pluginName,
@@ -654,6 +656,7 @@ export class AgentNode {
654
656
  });
655
657
  }
656
658
  catch (err) {
659
+ console.error(`[AgentNode] control spawn_worker failed: session=${sessionId} plugin=${pluginName}`, err instanceof Error ? err.stack || err.message : err);
657
660
  this.sendControlResult(source, message.requestId, name, false, {
658
661
  error: err instanceof Error ? err.message : String(err),
659
662
  });
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, readdirSync, existsSync } from "fs";
2
2
  import { join, extname, dirname, isAbsolute, resolve } from "path";
3
3
  import { fileURLToPath } from "url";
4
+ import { createRequire } from "module";
4
5
  /**
5
6
  * 加载单个插件配置
6
7
  */
@@ -29,12 +30,13 @@ export function loadPlugin(path) {
29
30
  if (!["stdio", "pty", "rpc"].includes(config.type)) {
30
31
  throw new Error(`Plugin ${path} invalid type: ${config.type}`);
31
32
  }
32
- return {
33
+ const normalized = {
33
34
  ...config,
34
35
  command: resolveCommandPath(config.command, configDir, repoRoot),
35
36
  args: resolveArgPaths(config.args, configDir, repoRoot),
36
37
  cwd: resolveOptionalPath(config.cwd, configDir, repoRoot),
37
38
  };
39
+ return maybeResolveBundledPieCommand(normalized, configDir, repoRoot);
38
40
  }
39
41
  function resolveOptionalPath(value, configDir, repoRoot) {
40
42
  if (!value)
@@ -82,6 +84,62 @@ function resolveArgPaths(args, configDir, repoRoot) {
82
84
  return arg;
83
85
  });
84
86
  }
87
+ function maybeResolveBundledPieCommand(config, configDir, repoRoot) {
88
+ if (config.name !== "pie" || config.command !== "pie") {
89
+ return config;
90
+ }
91
+ const pieEntry = resolveInstalledPackageBin("@cydm/pie", configDir, repoRoot);
92
+ if (!pieEntry) {
93
+ return config;
94
+ }
95
+ return {
96
+ ...config,
97
+ command: process.execPath,
98
+ args: [pieEntry, ...(config.args || [])],
99
+ };
100
+ }
101
+ function resolveInstalledPackageBin(packageName, configDir, repoRoot) {
102
+ const requireFromHere = createRequire(import.meta.url);
103
+ try {
104
+ const packageJsonPath = requireFromHere.resolve(`${packageName}/package.json`);
105
+ const direct = readPackageBin(packageJsonPath);
106
+ if (direct)
107
+ return direct;
108
+ }
109
+ catch {
110
+ // Fall through to alternate roots below.
111
+ }
112
+ const searchRoots = [
113
+ configDir,
114
+ repoRoot,
115
+ process.cwd(),
116
+ dirname(fileURLToPath(import.meta.url)),
117
+ ];
118
+ for (const root of searchRoots) {
119
+ try {
120
+ const packageJsonPath = requireFromHere.resolve(`${packageName}/package.json`, { paths: [resolve(root)] });
121
+ const resolved = readPackageBin(packageJsonPath);
122
+ if (resolved)
123
+ return resolved;
124
+ }
125
+ catch {
126
+ continue;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ function readPackageBin(packageJsonPath) {
132
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
133
+ const packageRoot = dirname(packageJsonPath);
134
+ const binField = packageJson.bin;
135
+ const relativeBin = typeof binField === "string"
136
+ ? binField
137
+ : (binField && Object.values(binField)[0]) || null;
138
+ if (!relativeBin)
139
+ return null;
140
+ const binPath = resolve(packageRoot, relativeBin);
141
+ return existsSync(binPath) ? binPath : null;
142
+ }
85
143
  /**
86
144
  * 扫描目录加载所有插件
87
145
  */
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "claude-code",
3
+ "type": "pty",
4
+ "command": "claude",
5
+ "args": ["--dangerously-skip-permissions"],
6
+ "capabilities": ["terminal", "bash", "file-edit", "vim", "tmux", "ansi256", "mouse"]
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "codex",
3
+ "type": "pty",
4
+ "command": "codex",
5
+ "args": ["--yolo", "--no-alt-screen"],
6
+ "capabilities": ["terminal", "bash", "file-edit", "ansi256", "mouse"]
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "echo",
3
+ "type": "stdio",
4
+ "command": "node",
5
+ "args": ["test/mocks/echo-agent.js"],
6
+ "capabilities": ["terminal", "test"]
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "pie-unity",
3
+ "type": "stdio",
4
+ "command": "pie-unity",
5
+ "args": ["--interactive"],
6
+ "capabilities": ["terminal", "file-edit", "web-search"],
7
+ "env": { "PIE_UNITY_MODE": "terminal" }
8
+ }
@@ -2591,6 +2591,17 @@
2591
2591
  }
2592
2592
  }
2593
2593
 
2594
+ function setSpawnCwd(cwd, { remember = false } = {}) {
2595
+ const next = (cwd || '').trim();
2596
+ const input = document.getElementById('spawnCwd');
2597
+ input.value = next;
2598
+ defaultSpawnCwd = next;
2599
+ localStorage.setItem('spawnCwd', defaultSpawnCwd);
2600
+ if (remember && next) {
2601
+ rememberCwd(next);
2602
+ }
2603
+ }
2604
+
2594
2605
  const searchParams = new URLSearchParams(window.location.search);
2595
2606
  const inferredRelayUrl = inferDefaultRelayUrl();
2596
2607
  document.getElementById('relayUrl').value = inferredRelayUrl;
@@ -3658,6 +3669,10 @@
3658
3669
  pushNodeTranscript,
3659
3670
  renderWorkers,
3660
3671
  syncWorkerPluginPreference,
3672
+ getSpawnCwd() {
3673
+ return (document.getElementById('spawnCwd').value || '').trim();
3674
+ },
3675
+ setSpawnCwd,
3661
3676
  setSessionDisplay(value) {
3662
3677
  document.getElementById('session-display').textContent = value;
3663
3678
  },
@@ -105,6 +105,18 @@
105
105
  }
106
106
 
107
107
  function handleControlMessage(msg, ctx) {
108
+ if (msg.controlKind === "result" && msg.controlName === "spawn_worker") {
109
+ if (msg.ok === false) {
110
+ ctx.pendingSpawnSessionId = null;
111
+ ctx.pendingSpawnAutoAttach = true;
112
+ ctx.resetSpawnButton();
113
+ ctx.writeSystemNotice(`SPAWN FAILED: ${msg.payload?.error || msg.error || "Unknown error"}`);
114
+ ctx.renderWorkers();
115
+ return true;
116
+ }
117
+ return false;
118
+ }
119
+
108
120
  if (msg.controlKind === "result" && msg.controlName === "get_primary_agent" && msg.payload && msg.payload.primary) {
109
121
  ctx.primaryAgent = msg.payload.primary || null;
110
122
  ctx.renderNodeHome();
@@ -145,6 +157,9 @@
145
157
  ctx.dirBrowserParentPath = msg.payload.parentPath || null;
146
158
  ctx.dirBrowserRepoRoot = msg.payload.repoRoot || null;
147
159
  ctx.dirBrowserEntries = msg.payload.entries || [];
160
+ if (!ctx.getSpawnCwd?.() && msg.payload.path) {
161
+ ctx.setSpawnCwd?.(msg.payload.path);
162
+ }
148
163
  ctx.renderDirBrowser();
149
164
  return true;
150
165
  }
@@ -165,6 +180,9 @@
165
180
  ctx.dirBrowserParentPath = msg.payload.parentPath || null;
166
181
  ctx.dirBrowserRepoRoot = msg.payload.repoRoot || null;
167
182
  ctx.dirBrowserEntries = msg.payload.entries || [];
183
+ if (!ctx.getSpawnCwd?.() && msg.payload.path) {
184
+ ctx.setSpawnCwd?.(msg.payload.path);
185
+ }
168
186
  ctx.renderDirBrowser();
169
187
  return true;
170
188
  }
@@ -6,6 +6,9 @@
6
6
  ctx.sessionId = null;
7
7
  ctx.setSessionDisplay("----");
8
8
  ctx.syncWorkerPluginPreference?.();
9
+ if (!ctx.getSpawnCwd?.()) {
10
+ ctx.requestDirBrowse?.();
11
+ }
9
12
  ctx.requestWorkerList();
10
13
  return true;
11
14
 
@@ -16,6 +16,7 @@ function normalizeWorkerCwd(cwd, pluginCwd) {
16
16
  return path.resolve(pluginCwd || process.cwd(), value);
17
17
  }
18
18
  export async function spawnManagedWorker(options, deps) {
19
+ console.log(`[WorkerRuntime] spawnManagedWorker start: session=${options.sessionId} plugin=${options.pluginName} cwd=${JSON.stringify(options.cwd || "")} displayName=${JSON.stringify(options.displayName || "")}`);
19
20
  const plugin = deps.plugins.get(options.pluginName);
20
21
  if (!plugin) {
21
22
  throw new Error(`Plugin not found: ${options.pluginName}`);
@@ -27,7 +28,9 @@ export async function spawnManagedWorker(options, deps) {
27
28
  : plugin.args,
28
29
  cwd: normalizeWorkerCwd(options.cwd, plugin.cwd),
29
30
  };
31
+ console.log(`[WorkerRuntime] resolved plugin: session=${options.sessionId} command=${JSON.stringify(runtimePlugin.command)} args=${JSON.stringify(runtimePlugin.args || [])} cwd=${JSON.stringify(runtimePlugin.cwd || process.cwd())}`);
30
32
  await deps.sessionManager.createSession(options.sessionId, runtimePlugin);
33
+ console.log(`[WorkerRuntime] session created: ${options.sessionId}`);
31
34
  const session = deps.sessionManager.getSession(options.sessionId);
32
35
  if (!session) {
33
36
  throw new Error(`Session failed to start: ${options.sessionId}`);
@@ -60,6 +63,7 @@ export async function spawnManagedWorker(options, deps) {
60
63
  deps.workerRegistry.updateWorkerStatus(session.agentId, "running", {
61
64
  startedAt: Date.now(),
62
65
  });
66
+ console.log(`[WorkerRuntime] worker record ready: session=${options.sessionId} agent=${session.agentId}`);
63
67
  return {
64
68
  type: "session",
65
69
  sessionId: options.sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/magic-shell-agent-node",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Magic Shell Agent Node - Local agent connector",
5
5
  "homepage": "https://magicshell.ai",
6
6
  "keywords": [