pi-subagents 0.9.2 → 0.11.0

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/settings.ts CHANGED
@@ -213,7 +213,7 @@ export function resolveStepBehavior(
213
213
  * Resolve a file path: absolute paths pass through, relative paths get chainDir prepended.
214
214
  */
215
215
  function resolveChainPath(filePath: string, chainDir: string): string {
216
- return path.isAbsolute(filePath) ? filePath : `${chainDir}/${filePath}`;
216
+ return path.isAbsolute(filePath) ? filePath : path.join(chainDir, filePath);
217
217
  }
218
218
 
219
219
  /**
@@ -243,7 +243,7 @@ export function buildChainInstructions(
243
243
 
244
244
  // Progress instructions in suffix (less critical)
245
245
  if (behavior.progress) {
246
- const progressPath = `${chainDir}/progress.md`;
246
+ const progressPath = path.join(chainDir, "progress.md");
247
247
  if (isFirstProgressAgent) {
248
248
  suffixParts.push(`Create and maintain progress at: ${progressPath}`);
249
249
  } else {
@@ -288,7 +288,7 @@ export function resolveParallelBehaviors(
288
288
  }
289
289
 
290
290
  // Build subdirectory path for this parallel task
291
- const subdir = `parallel-${stepIndex}/${taskIndex}-${task.agent}`;
291
+ const subdir = path.join(`parallel-${stepIndex}`, `${taskIndex}-${task.agent}`);
292
292
 
293
293
  // Output: task override > agent default (namespaced) > false
294
294
  // Absolute paths pass through unchanged; relative paths get namespaced under subdir
@@ -299,11 +299,11 @@ export function resolveParallelBehaviors(
299
299
  } else if (path.isAbsolute(task.output)) {
300
300
  output = task.output; // Absolute path: use as-is
301
301
  } else {
302
- output = `${subdir}/${task.output}`; // Relative: namespace under subdir
302
+ output = path.join(subdir, task.output); // Relative: namespace under subdir
303
303
  }
304
304
  } else if (config.output) {
305
305
  // Agent defaults are always relative, so namespace them
306
- output = `${subdir}/${config.output}`;
306
+ output = path.join(subdir, config.output);
307
307
  }
308
308
 
309
309
  // Reads: task override > agent default > false
@@ -372,15 +372,17 @@ export function aggregateParallelOutputs(results: ParallelTaskResult[]): string
372
372
  .map((r, i) => {
373
373
  const header = `=== Parallel Task ${i + 1} (${r.agent}) ===`;
374
374
  const hasTextOutput = Boolean(r.output?.trim());
375
- const status = r.exitCode !== 0
376
- ? `⚠️ FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
377
- : r.error
378
- ? `⚠️ WARNING: ${r.error}`
379
- : !hasTextOutput && r.outputTargetPath && r.outputTargetExists === false
380
- ? `⚠️ EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
381
- : !hasTextOutput && !r.outputTargetPath
382
- ? "⚠️ EMPTY OUTPUT (no textual response returned)"
383
- : "";
375
+ const status = r.exitCode === -1
376
+ ? "⏭️ SKIPPED"
377
+ : r.exitCode !== 0
378
+ ? `⚠️ FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
379
+ : r.error
380
+ ? `⚠️ WARNING: ${r.error}`
381
+ : !hasTextOutput && r.outputTargetPath && r.outputTargetExists === false
382
+ ? `⚠️ EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
383
+ : !hasTextOutput && !r.outputTargetPath
384
+ ? "⚠️ EMPTY OUTPUT (no textual response returned)"
385
+ : "";
384
386
  const body = status
385
387
  ? (hasTextOutput ? `${status}\n${r.output}` : status)
386
388
  : r.output;
package/skills.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Skill resolution and caching for subagent extension
3
3
  */
4
4
 
5
+ import { execSync } from "node:child_process";
5
6
  import * as fs from "node:fs";
6
7
  import * as os from "node:os";
7
8
  import * as path from "node:path";
@@ -89,11 +90,31 @@ function getPackageSkillPaths(packageRoot: string): string[] {
89
90
  }
90
91
  }
91
92
 
93
+ let cachedGlobalNpmRoot: string | null = null;
94
+
95
+ function getGlobalNpmRoot(): string | null {
96
+ if (cachedGlobalNpmRoot !== null) return cachedGlobalNpmRoot;
97
+ try {
98
+ cachedGlobalNpmRoot = execSync("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim();
99
+ return cachedGlobalNpmRoot;
100
+ } catch {
101
+ cachedGlobalNpmRoot = ""; // Empty string means "tried but failed"
102
+ return null;
103
+ }
104
+ }
105
+
92
106
  function collectPackageSkillPaths(cwd: string): string[] {
93
107
  const dirs = [
94
108
  path.join(cwd, CONFIG_DIR, "npm", "node_modules"),
95
109
  path.join(AGENT_DIR, "npm", "node_modules"),
96
110
  ];
111
+
112
+ // Add global npm root if available (where pi installs global packages)
113
+ const globalRoot = getGlobalNpmRoot();
114
+ if (globalRoot) {
115
+ dirs.push(globalRoot);
116
+ }
117
+
97
118
  const results: string[] = [];
98
119
 
99
120
  for (const dir of dirs) {
@@ -178,6 +199,8 @@ function inferSkillSource(rawSource: unknown, filePath: string, cwd: string): Sk
178
199
  const projectRoot = path.resolve(cwd, CONFIG_DIR);
179
200
  const isProjectScoped = isWithinPath(filePath, projectRoot);
180
201
  const isUserScoped = isWithinPath(filePath, AGENT_DIR);
202
+ const globalRoot = getGlobalNpmRoot();
203
+ const isGlobalPackage = globalRoot ? isWithinPath(filePath, globalRoot) : false;
181
204
 
182
205
  if (source === "project") return "project";
183
206
  if (source === "user") return "user";
@@ -188,7 +211,7 @@ function inferSkillSource(rawSource: unknown, filePath: string, cwd: string): Sk
188
211
  }
189
212
  if (source === "package") {
190
213
  if (isProjectScoped) return "project-package";
191
- if (isUserScoped) return "user-package";
214
+ if (isUserScoped || isGlobalPackage) return "user-package";
192
215
  return "unknown";
193
216
  }
194
217
  if (source === "extension") return "extension";
@@ -196,6 +219,7 @@ function inferSkillSource(rawSource: unknown, filePath: string, cwd: string): Sk
196
219
 
197
220
  if (isProjectScoped) return "project";
198
221
  if (isUserScoped) return "user";
222
+ if (isGlobalPackage) return "user-package";
199
223
  return "unknown";
200
224
  }
201
225