pi-open-agents 0.1.27 → 0.1.29
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/README.md +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/subagent/executor.ts +120 -6
package/README.md
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -261,8 +261,8 @@ export { matchPattern, isWildcardPattern } from "./permission/matcher.ts";
|
|
|
261
261
|
export { evaluate, toolToPermission, getDisabledTools, filterDisabledTools } from "./permission/evaluator.ts";
|
|
262
262
|
|
|
263
263
|
// Subagent re-exports
|
|
264
|
-
export { runSubagent, resolveSubagentSession, resolvePiEntryPoint } from "./subagent/executor.ts";
|
|
265
|
-
export type { AgentProgress, AgentResult, RunSubagentOptions, SubagentSessionInfo, SubagentSessionMode } from "./subagent/executor.ts";
|
|
264
|
+
export { runSubagent, resolveSubagentSession, resolvePiEntryPoint, isVirtualBunPath, resolveCompiledCommand } from "./subagent/executor.ts";
|
|
265
|
+
export type { AgentProgress, AgentResult, RunSubagentOptions, SubagentSessionInfo, SubagentSessionMode, HostProcessProbe } from "./subagent/executor.ts";
|
|
266
266
|
export { resolveSkills } from "./subagent/skills.ts";
|
|
267
267
|
export type { ResolvedSkill } from "./subagent/skills.ts";
|
|
268
268
|
export { buildSubagentPrompt } from "./subagent/prompt.ts";
|
package/src/subagent/executor.ts
CHANGED
|
@@ -227,9 +227,95 @@ export function subagentSessionDir(
|
|
|
227
227
|
return path.join(agentDir, "sessions", safeProject, "subagents");
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Detect Bun's in-memory virtual paths (issue #28).
|
|
232
|
+
*
|
|
233
|
+
* Inside a `bun build --compile` single-file binary, `process.argv[1]` (and on
|
|
234
|
+
* some platforms `process.execPath`) resolves to a compile-time virtual entry
|
|
235
|
+
* such as `/$bunfs/root/pi`. That path does not exist on disk — any
|
|
236
|
+
* `realpathSync`/`lstat` on it throws ENOENT. A real on-disk executable path
|
|
237
|
+
* never contains these markers, so the check has no false positives.
|
|
238
|
+
*/
|
|
239
|
+
export function isVirtualBunPath(candidate: string | undefined | null): boolean {
|
|
240
|
+
if (!candidate) return false;
|
|
241
|
+
return (
|
|
242
|
+
candidate.includes("$bunfs") || candidate.includes("~BUN") || candidate.includes("%7EBUN")
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Injectable host-process values for {@link resolvePiEntryPoint}.
|
|
248
|
+
*
|
|
249
|
+
* The production default reads `process.execPath` / `process.argv` /
|
|
250
|
+
* `/proc/self/exe` directly. Tests inject exact strings (including the
|
|
251
|
+
* virtual `/$bunfs/...` paths a compiled host reports) without spawning
|
|
252
|
+
* a real compiled binary.
|
|
253
|
+
*/
|
|
254
|
+
export interface HostProcessProbe {
|
|
255
|
+
execPath?: string;
|
|
256
|
+
argv0?: string;
|
|
257
|
+
argv1?: string;
|
|
258
|
+
readProcSelfExe?: () => string;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Pick the real executable for a compiled single-file host (issue #28).
|
|
263
|
+
*
|
|
264
|
+
* Priority order (most reliable first):
|
|
265
|
+
* 1. `/proc/self/exe` (kernel truth on Linux),
|
|
266
|
+
* 2. `process.execPath` (real in every observed compiled binary),
|
|
267
|
+
* 3. `process.argv[0]` — ONLY when absolute. A compiled Bun binary reports
|
|
268
|
+
* the bare word `"bun"` here, which must never become the spawn command
|
|
269
|
+
* (it would resolve via PATH to the Bun runtime instead of pi).
|
|
270
|
+
*
|
|
271
|
+
* Returns `undefined` when every candidate is missing or virtual — the caller
|
|
272
|
+
* turns that into a descriptive error instead of spawning a `$bunfs` path.
|
|
273
|
+
*/
|
|
274
|
+
export function resolveCompiledCommand(probe: {
|
|
275
|
+
execPath?: string;
|
|
276
|
+
argv0?: string;
|
|
277
|
+
procSelfExe?: string | undefined;
|
|
278
|
+
}): string | undefined {
|
|
279
|
+
for (const candidate of [probe.procSelfExe, probe.execPath]) {
|
|
280
|
+
if (candidate && !isVirtualBunPath(candidate)) return candidate;
|
|
281
|
+
}
|
|
282
|
+
const argv0 = probe.argv0;
|
|
283
|
+
if (argv0 && path.isAbsolute(argv0) && !isVirtualBunPath(argv0)) return argv0;
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
230
287
|
export function resolvePiEntryPoint(
|
|
231
288
|
resolveSelf: (specifier: string) => string = (specifier) => import.meta.resolve(specifier),
|
|
289
|
+
hostProbe?: HostProcessProbe,
|
|
232
290
|
): PiResolution {
|
|
291
|
+
const execPath = hostProbe?.execPath ?? process.execPath;
|
|
292
|
+
const argv0 = hostProbe?.argv0 ?? process.argv[0];
|
|
293
|
+
const argv1 = hostProbe?.argv1 ?? process.argv[1];
|
|
294
|
+
|
|
295
|
+
// Compiled single-file host (`bun build --compile`): the entry "file" is a
|
|
296
|
+
// virtual `$bunfs` path that does not exist on disk. The binary IS the CLI,
|
|
297
|
+
// so resolve the real executable and return an empty entry point — the args
|
|
298
|
+
// builder omits it and the child receives only real CLI flags.
|
|
299
|
+
if (isVirtualBunPath(execPath) || isVirtualBunPath(argv1)) {
|
|
300
|
+
let procSelfExe: string | undefined;
|
|
301
|
+
try {
|
|
302
|
+
procSelfExe = hostProbe?.readProcSelfExe
|
|
303
|
+
? hostProbe.readProcSelfExe()
|
|
304
|
+
: realpathSync("/proc/self/exe");
|
|
305
|
+
} catch {
|
|
306
|
+
procSelfExe = undefined;
|
|
307
|
+
}
|
|
308
|
+
const command = resolveCompiledCommand({ execPath, argv0, procSelfExe });
|
|
309
|
+
if (!command) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
"pi-open-agents: running inside a compiled single-file pi binary but the real " +
|
|
312
|
+
"executable path could not be determined (only virtual paths available). " +
|
|
313
|
+
"Subagent delegation is unavailable in this environment.",
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return { command, entryPoint: "" };
|
|
317
|
+
}
|
|
318
|
+
|
|
233
319
|
let packageEntryPoint: string;
|
|
234
320
|
try {
|
|
235
321
|
packageEntryPoint = fileURLToPath(resolveSelf("@earendil-works/pi-coding-agent"));
|
|
@@ -239,14 +325,21 @@ export function resolvePiEntryPoint(
|
|
|
239
325
|
// resolution which cannot see the host package from the plugin's install
|
|
240
326
|
// tree (peer dependencies are never installed next to it). Fall back to
|
|
241
327
|
// the running pi entry itself — the child must run the same pi anyway.
|
|
328
|
+
if (!argv1) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
"pi-open-agents: cannot resolve the pi CLI entry point (host package " +
|
|
331
|
+
"self-resolution failed and process.argv[1] is missing). " +
|
|
332
|
+
"Subagent delegation is unavailable in this environment.",
|
|
333
|
+
);
|
|
334
|
+
}
|
|
242
335
|
return {
|
|
243
|
-
command:
|
|
244
|
-
entryPoint: realpathSync(
|
|
336
|
+
command: execPath,
|
|
337
|
+
entryPoint: realpathSync(argv1),
|
|
245
338
|
};
|
|
246
339
|
}
|
|
247
340
|
const packageRoot = path.dirname(path.dirname(packageEntryPoint));
|
|
248
341
|
return {
|
|
249
|
-
command:
|
|
342
|
+
command: execPath,
|
|
250
343
|
entryPoint: path.join(packageRoot, "dist", "cli.js"),
|
|
251
344
|
};
|
|
252
345
|
}
|
|
@@ -347,10 +440,28 @@ function usageFromMessage(
|
|
|
347
440
|
};
|
|
348
441
|
}
|
|
349
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Extract the display model string from an assistant message (issue #27).
|
|
445
|
+
*
|
|
446
|
+
* `AssistantMessage` always carries both `provider` and `model`, so prefix the
|
|
447
|
+
* bare id (`provider/model`) to match the `model:` frontmatter format and the
|
|
448
|
+
* per-call override (`parseModelRef` splits on the first slash, so a
|
|
449
|
+
* namespaced id such as `anthropic/claude-opus-5.5` under `openrouter`
|
|
450
|
+
* round-trips exactly). No `provider` → bare id, as before.
|
|
451
|
+
*
|
|
452
|
+
* The `startsWith` guard prevents `provider/provider/...` when a host echoes
|
|
453
|
+
* an already-qualified id. Deliberately NOT `responseModel`: that is the
|
|
454
|
+
* provider-native answering snapshot (alias → dated version), a different
|
|
455
|
+
* granularity from "which provider ran the agent".
|
|
456
|
+
*/
|
|
350
457
|
function modelFromMessage(message: unknown): string | undefined {
|
|
351
458
|
if (!message || typeof message !== "object") return undefined;
|
|
352
|
-
const
|
|
353
|
-
|
|
459
|
+
const data = message as { provider?: unknown; model?: unknown };
|
|
460
|
+
if (typeof data.model !== "string") return undefined;
|
|
461
|
+
if (typeof data.provider === "string" && !data.model.startsWith(`${data.provider}/`)) {
|
|
462
|
+
return `${data.provider}/${data.model}`;
|
|
463
|
+
}
|
|
464
|
+
return data.model;
|
|
354
465
|
}
|
|
355
466
|
|
|
356
467
|
function updateUsage(target: AgentUsage, update: Partial<AgentUsage> | undefined) {
|
|
@@ -542,8 +653,11 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
542
653
|
const modelRegistry = buildModelRegistry(options.agentDir);
|
|
543
654
|
|
|
544
655
|
// ── Build CLI args ──
|
|
656
|
+
// A compiled single-file host IS the CLI: its PiResolution carries an
|
|
657
|
+
// empty entryPoint (issue #28) and the child receives only real CLI
|
|
658
|
+
// flags. Non-compiled hosts keep runtime + script + flags, unchanged.
|
|
545
659
|
const args = [
|
|
546
|
-
pi.entryPoint,
|
|
660
|
+
...(pi.entryPoint ? [pi.entryPoint] : []),
|
|
547
661
|
"--mode", "json",
|
|
548
662
|
"-p",
|
|
549
663
|
"--no-skills",
|