pi-teams 0.9.8 → 0.9.10

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.
@@ -15,6 +15,29 @@ import * as fs from "node:fs";
15
15
  import * as os from "node:os";
16
16
  import { spawnSync } from "node:child_process";
17
17
 
18
+ /**
19
+ * Build the command used to relaunch pi for teammate processes.
20
+ *
21
+ * In Bun-compiled pi binaries, process.argv[1] points at a virtual $bunfs path
22
+ * like /$bunfs/root/pi, which is not a real file and breaks when prefixed with
23
+ * `node`. process.execPath points at the actual executable in both compiled and
24
+ * regular environments, so prefer that when available.
25
+ */
26
+ function getPiLaunchCommand(): string {
27
+ // If we have an execPath, use it directly (works for both compiled binaries and node scripts)
28
+ if (process.execPath) {
29
+ return JSON.stringify(process.execPath);
30
+ }
31
+
32
+ // Fallback: try argv[1] with node prefix for regular node environments
33
+ if (process.argv[1]) {
34
+ return `node ${JSON.stringify(process.argv[1])}`;
35
+ }
36
+
37
+ // Last resort: just use "pi" and hope it's on PATH
38
+ return "pi";
39
+ }
40
+
18
41
  // Cache for available models
19
42
  let availableModelsCache: Array<{ provider: string; model: string }> | null = null;
20
43
  let modelsCacheTime = 0;
@@ -553,7 +576,7 @@ export default function (pi: ExtensionAPI) {
553
576
  await teams.addMember(safeTeamName, member);
554
577
  await messaging.sendPlainMessage(safeTeamName, "team-lead", safeName, params.prompt, "Initial prompt");
555
578
 
556
- const piBinary = process.argv[1] ? `node ${process.argv[1]}` : "pi";
579
+ const piBinary = getPiLaunchCommand();
557
580
  let piCmd = piBinary;
558
581
 
559
582
  if (chosenModel) {
@@ -638,7 +661,7 @@ export default function (pi: ExtensionAPI) {
638
661
 
639
662
  const teamConfig = await teams.readConfig(safeTeamName);
640
663
  const cwd = params.cwd || process.cwd();
641
- const piBinary = process.argv[1] ? `node ${process.argv[1]}` : "pi";
664
+ const piBinary = getPiLaunchCommand();
642
665
  let piCmd = piBinary;
643
666
  if (teamConfig.defaultModel) {
644
667
  // Use the combined --model provider/model format
@@ -1105,7 +1128,7 @@ export default function (pi: ExtensionAPI) {
1105
1128
  await teams.addMember(safeTeamName, member);
1106
1129
  await messaging.sendPlainMessage(safeTeamName, "team-lead", safeName, agentDef.prompt, "Initial prompt from predefined team");
1107
1130
 
1108
- const piBinary = process.argv[1] ? `node ${process.argv[1]}` : "pi";
1131
+ const piBinary = getPiLaunchCommand();
1109
1132
  let piCmd = piBinary;
1110
1133
 
1111
1134
  if (chosenModel) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-teams",
3
- "version": "0.9.8",
3
+ "version": "0.9.10",
4
4
  "description": "Agent teams for pi, ported from claude-code-teams-mcp",
5
5
  "repository": {
6
6
  "type": "git",
@@ -94,6 +94,8 @@ describe("WindowsAdapter", () => {
94
94
  Object.defineProperty(process, "platform", { value: "win32" });
95
95
  // Mock findWtBinary check
96
96
  mockExecCommand.mockReturnValueOnce({ stdout: "wt", status: 0 });
97
+ // Mock findPsBinary check (pwsh found)
98
+ mockExecCommand.mockReturnValueOnce({ stdout: "found", status: 0 });
97
99
  // Mock getPanes - no existing panes
98
100
  mockExecCommand.mockReturnValueOnce({ stdout: "[]", status: 0 });
99
101
  // Mock actual spawn
@@ -114,6 +116,8 @@ describe("WindowsAdapter", () => {
114
116
  Object.defineProperty(process, "platform", { value: "win32" });
115
117
  // Mock findWtBinary check
116
118
  mockExecCommand.mockReturnValueOnce({ stdout: "wt", status: 0 });
119
+ // Mock findPsBinary check (pwsh found)
120
+ mockExecCommand.mockReturnValueOnce({ stdout: "found", status: 0 });
117
121
  // Mock getPanes - existing panes
118
122
  mockExecCommand.mockReturnValueOnce({ stdout: '[{"window":1}]', status: 0 });
119
123
  // Mock actual spawn
@@ -169,6 +173,8 @@ describe("WindowsAdapter", () => {
169
173
  Object.defineProperty(process, "platform", { value: "win32" });
170
174
  // Mock findWtBinary check
171
175
  mockExecCommand.mockReturnValueOnce({ stdout: "wt", status: 0 });
176
+ // Mock findPsBinary check (pwsh found)
177
+ mockExecCommand.mockReturnValueOnce({ stdout: "found", status: 0 });
172
178
  // Mock actual spawn
173
179
  mockExecCommand.mockReturnValueOnce({ stdout: "", status: 0 });
174
180
 
@@ -183,6 +189,31 @@ describe("WindowsAdapter", () => {
183
189
  // Returns synthetic ID: windows_win_<timestamp>_<name>
184
190
  expect(windowId).toMatch(/^windows_win_\d+_team-lead$/);
185
191
  });
192
+
193
+ it("should fallback to powershell when pwsh is not available", () => {
194
+ Object.defineProperty(process, "platform", { value: "win32" });
195
+ // Mock findWtBinary check
196
+ mockExecCommand.mockReturnValueOnce({ stdout: "wt", status: 0 });
197
+ // Mock findPsBinary check - pwsh fails, powershell succeeds
198
+ mockExecCommand.mockReturnValueOnce({ stdout: "", status: 1 }); // pwsh not found
199
+ mockExecCommand.mockReturnValueOnce({ stdout: "found", status: 0 }); // powershell found
200
+ // Mock actual spawn
201
+ mockExecCommand.mockReturnValueOnce({ stdout: "", status: 0 });
202
+
203
+ const windowId = adapter.spawnWindow({
204
+ name: "team-lead",
205
+ cwd: "/test/path",
206
+ command: "pi",
207
+ env: {},
208
+ });
209
+
210
+ expect(windowId).toMatch(/^windows_win_\d+_team-lead$/);
211
+ // Verify that powershell was used (check the call args)
212
+ const lastCall = mockExecCommand.mock.calls[mockExecCommand.mock.calls.length - 1];
213
+ expect(lastCall[0]).toBe("wt");
214
+ // The command should use 'powershell' not 'pwsh'
215
+ expect(lastCall[1]).toContain("powershell");
216
+ });
186
217
  });
187
218
 
188
219
  describe("kill()", () => {
@@ -18,6 +18,7 @@ export class WindowsAdapter implements TerminalAdapter {
18
18
  ];
19
19
 
20
20
  private wtPath: string | null = null;
21
+ private psPath: string | null = null;
21
22
 
22
23
  private findWtBinary(): string | null {
23
24
  if (this.wtPath !== null) {
@@ -62,6 +63,42 @@ export class WindowsAdapter implements TerminalAdapter {
62
63
  return null;
63
64
  }
64
65
 
66
+ /**
67
+ * Find the PowerShell binary to use.
68
+ * Prefers PowerShell Core (pwsh) if available, falls back to Windows PowerShell (powershell).
69
+ */
70
+ private findPsBinary(): string {
71
+ if (this.psPath !== null) {
72
+ return this.psPath;
73
+ }
74
+
75
+ // Try pwsh (PowerShell Core / PowerShell 7+) first
76
+ try {
77
+ const result = execCommand("pwsh", ["-NoProfile", "-Command", "echo 'found'"]);
78
+ if (result.status === 0 && result.stdout.trim() === "found") {
79
+ this.psPath = "pwsh";
80
+ return "pwsh";
81
+ }
82
+ } catch {
83
+ // pwsh not found, try powershell
84
+ }
85
+
86
+ // Fall back to powershell (Windows PowerShell 5.1)
87
+ try {
88
+ const result = execCommand("powershell", ["-NoProfile", "-Command", "echo 'found'"]);
89
+ if (result.status === 0 && result.stdout.trim() === "found") {
90
+ this.psPath = "powershell";
91
+ return "powershell";
92
+ }
93
+ } catch {
94
+ // powershell not found either
95
+ }
96
+
97
+ // Default to powershell as it's built into Windows
98
+ this.psPath = "powershell";
99
+ return "powershell";
100
+ }
101
+
65
102
  detect(): boolean {
66
103
  // Windows only - check platform
67
104
  if (process.platform !== "win32") {
@@ -111,6 +148,7 @@ export class WindowsAdapter implements TerminalAdapter {
111
148
  throw new Error("Windows Terminal (wt) CLI binary not found.");
112
149
  }
113
150
 
151
+ const psBin = this.findPsBinary();
114
152
  const panes = this.getPanes();
115
153
 
116
154
  // Build environment variables for PowerShell
@@ -134,7 +172,7 @@ export class WindowsAdapter implements TerminalAdapter {
134
172
  "split-pane",
135
173
  "--vertical",
136
174
  "--size", "50%",
137
- "--", "pwsh", "-NoExit", "-Command", psCommand
175
+ "--", psBin, "-NoExit", "-Command", psCommand
138
176
  ];
139
177
  } else {
140
178
  // Create a new tab for subsequent panes (Windows Terminal limitation)
@@ -143,7 +181,7 @@ export class WindowsAdapter implements TerminalAdapter {
143
181
  "split-pane",
144
182
  "--horizontal",
145
183
  "--size", "50%",
146
- "--", "pwsh", "-NoExit", "-Command", psCommand
184
+ "--", psBin, "-NoExit", "-Command", psCommand
147
185
  ];
148
186
  }
149
187
 
@@ -205,6 +243,8 @@ export class WindowsAdapter implements TerminalAdapter {
205
243
  throw new Error("Windows Terminal (wt) CLI binary not found.");
206
244
  }
207
245
 
246
+ const psBin = this.findPsBinary();
247
+
208
248
  // Build environment variables for PowerShell
209
249
  const envVars = Object.entries(options.env)
210
250
  .filter(([k]) => k.startsWith("PI_"))
@@ -223,7 +263,7 @@ export class WindowsAdapter implements TerminalAdapter {
223
263
  const spawnArgs = [
224
264
  "new-window",
225
265
  "--title", windowTitle,
226
- "--", "pwsh", "-NoExit", "-Command", psCommand
266
+ "--", psBin, "-NoExit", "-Command", psCommand
227
267
  ];
228
268
 
229
269
  const result = execCommand(wtBin, spawnArgs);