@ferris1225/pi-subagents 4.2.4 → 4.2.7

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/src/monitor.ts CHANGED
@@ -242,7 +242,7 @@ export const RUN_LABEL_MAX = 32;
242
242
  * distinguishing fragment so concurrent same-agent runs are told apart by WHAT
243
243
  * they do, not just their run id. A file/directory path outranks other
244
244
  * fragment kinds (a kebab-case word like "edge-case" never beats
245
- * "tests/config.test.ts"); a long path keeps its tail (the filename is the
245
+ * "src/config.ts"); a long path keeps its tail (the filename is the
246
246
  * recognisable part); a task with no recognizable fragment falls back to a
247
247
  * head slice of its prose. Grapheme-safe.
248
248
  */
package/src/prompt.ts CHANGED
@@ -1,69 +1,69 @@
1
- /**
2
- * Builds the delegation directive injected into the parent model's
3
- * system prompt via `before_agent_start`. It is paid on every turn, so it
4
- * stays a lean routing contract — when a child context pays for itself,
5
- * how wide to fan out, and how results come back. Tool metadata stays
6
- * intentionally minimal so role/process guidance is not paid for twice.
7
- */
8
-
9
- import type { AgentConfig } from "./agents.ts";
10
- import { formatCatalogEntry } from "./agents.ts";
11
-
12
- function bullets(lines: readonly string[]): string {
13
- return lines.map((line) => `- ${line}`).join("\n");
14
- }
15
-
16
- export function buildDelegationDirective(
17
- agents: AgentConfig[],
18
- ): string {
19
- if (agents.length === 0) return "";
20
-
21
- const catalog = agents.map(formatCatalogEntry).join("\n");
22
- const hasExplorer = agents.some((agent) => agent.name === "explorer");
23
- const hasExecutor = agents.some((agent) => agent.name === "executor");
24
-
25
- const dispatchRules = [
26
- `Delegate aggressively: child contexts are cheap, yours is scarce. Inline only trivial work a lookup, a single focused edit, an answer already in context${hasExecutor ? "; default every non-trivial delegated task (implementation, fix, refactor, test, cleanup, docs sync, result merging) to `executor`" : ""}.`,
27
- "A single artifact you must fully absorb yourself (one issue, one spec) is inline work — delegation saves search, not that read.",
28
- ...(hasExplorer
29
- ? [
30
- "`explorer`: split a broad question into parallel explorers with disjoint scopes. Its findings are leads, never proof — re-read the cited line ranges before acting on them (a child you brief re-verifies).",
31
- ]
32
- : []),
33
- ...(hasExecutor
34
- ? [
35
- "`executor`: brief it as the edit authorization. For cleanup, name the scope (uncommitted diff, Git range, directory) — every safe proven cut applies without per-item approval; finding no safe cut is a valid result. After a wide fan-out, pass the result-artifact paths to one executor and read its merged brief instead of every result yourself.",
36
- ]
37
- : []),
38
- "Parallelize by default: map the todo list onto ONE `tasks` dispatch. One child owns one deliverable and its files; only genuinely dependent work waits for its prerequisite.",
39
- "Brief each child completely — goal, exact paths, constraints, expected output; it has no conversation memory and cannot delegate. Resume parked threads with `subagent_control resume`.",
40
- ];
41
-
42
- const handoffRules = [
43
- "Dispatch never blocks or ends your turn — keep working; each completion resumes you automatically. Never sleep or poll for it.",
44
- "Results are already shown; add only your conclusion or next action, never a restatement.",
45
- "Never declare the overall task done while a dispatched run is still active.",
46
- ];
47
-
48
- const verificationRules = [
49
- "Never report an unrun check as passed; surface unavailable checks and pre-existing failures, and inspect the actual diff before reporting completion.",
50
- "Commit or push only when explicitly requested and applicable checks pass.",
51
- ];
52
-
53
- return `
54
- ## Sub-agent delegation (pi-subagents)
55
-
56
- \`subagent\` runs isolated leaf Pi child processes in the background.
57
-
58
- Agents:
59
- ${catalog}
60
-
61
- Dispatch:
62
- ${bullets(dispatchRules)}
63
-
64
- Result handoff:
65
- ${bullets(handoffRules)}
66
-
67
- Verification:
68
- ${bullets(verificationRules)}`;
69
- }
1
+ /**
2
+ * Builds the delegation directive injected into the parent model's
3
+ * system prompt via `before_agent_start`. It is paid on every turn, so it
4
+ * stays a lean routing contract — when a child context pays for itself,
5
+ * how wide to fan out, and how results come back. Tool metadata stays
6
+ * intentionally minimal so role/process guidance is not paid for twice.
7
+ */
8
+
9
+ import type { AgentConfig } from "./agents.ts";
10
+ import { formatCatalogEntry } from "./agents.ts";
11
+
12
+ function bullets(lines: readonly string[]): string {
13
+ return lines.map((line) => `- ${line}`).join("\n");
14
+ }
15
+
16
+ export function buildDelegationDirective(
17
+ agents: AgentConfig[],
18
+ ): string {
19
+ if (agents.length === 0) return "";
20
+
21
+ const catalog = agents.map(formatCatalogEntry).join("\n");
22
+ const hasExplorer = agents.some((agent) => agent.name === "explorer");
23
+ const hasExecutor = agents.some((agent) => agent.name === "executor");
24
+
25
+ const dispatchRules = [
26
+ `Delegate aggressively: child contexts are cheap, yours is scarce. A unit is delegable when it can proceed independently and return a compact result${hasExecutor ? "; when both hold, default it to `executor`" : ""}.`,
27
+ "Keep inline what fails either test — a lookup, a single focused edit, an answer already in context, or a single artifact you must absorb yourself (one issue, one spec), where delegation saves search, not that read. Cluster related questions into one brief instead of firing many small dispatches; a child loses context between runs.",
28
+ ...(hasExplorer
29
+ ? [
30
+ "`explorer`: split a broad question into parallel explorers with disjoint scopes. Its findings are leads, never proof — re-read the cited line ranges before acting on them (a child you brief re-verifies).",
31
+ ]
32
+ : []),
33
+ ...(hasExecutor
34
+ ? [
35
+ "`executor`: brief it as the edit authorization. For cleanup, name the scope (uncommitted diff, Git range, directory) — every safe proven cut applies without per-item approval; finding no safe cut is a valid result. After a wide fan-out, pass the result-artifact paths to one executor and read its merged brief instead of every result yourself.",
36
+ ]
37
+ : []),
38
+ "Parallelize by default: map the todo list onto ONE `tasks` dispatch. One child owns one deliverable and its files; only genuinely dependent work waits for its prerequisite.",
39
+ "Brief each child completely — goal, exact paths, constraints, expected output; it has no conversation memory and cannot delegate. Resume parked threads with `subagent_control resume`.",
40
+ ];
41
+
42
+ const handoffRules = [
43
+ "Dispatch never blocks or ends your turn — keep working; each completion resumes you automatically. Never sleep or poll for it.",
44
+ "Results are already shown; add only your conclusion or next action, never a restatement.",
45
+ "Never declare the overall task done while a dispatched run is still active.",
46
+ ];
47
+
48
+ const verificationRules = [
49
+ "Never report an unrun check as passed; surface unavailable checks and pre-existing failures, and inspect the actual diff before reporting completion.",
50
+ "Commit or push only when explicitly requested and applicable checks pass.",
51
+ ];
52
+
53
+ return `
54
+ ## Sub-agent delegation (pi-subagents)
55
+
56
+ \`subagent\` runs isolated leaf Pi child processes in the background.
57
+
58
+ Agents:
59
+ ${catalog}
60
+
61
+ Dispatch:
62
+ ${bullets(dispatchRules)}
63
+
64
+ Result handoff:
65
+ ${bullets(handoffRules)}
66
+
67
+ Verification:
68
+ ${bullets(verificationRules)}`;
69
+ }
package/src/rpc-run.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { spawn, type ChildProcess } from "node:child_process";
11
- import { existsSync, readdirSync, unlinkSync, rmdirSync } from "node:fs";
11
+ import { existsSync, readdirSync } from "node:fs";
12
12
  import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
13
13
  import { basename, join } from "node:path";
14
14
  import { StringDecoder } from "node:string_decoder";
@@ -364,6 +364,11 @@ export async function writeChildRetryPolicyExtension(
364
364
  }
365
365
  }
366
366
 
367
+ /** Hand the agent body to the child as a file rather than inline argv text.
368
+ * `--append-system-prompt` takes either, but a system prompt is unbounded — a
369
+ * custom agent can carry a whole spec — while Windows caps a command line at
370
+ * 32767 characters, so inlining turns a long agent into an opaque spawn failure.
371
+ * The file lives in the run's scratch dir and is removed when the attempt ends. */
367
372
  async function writePromptToTempFile(
368
373
  scratchRoot: string,
369
374
  agentName: string,
@@ -974,20 +979,9 @@ export async function runRpcAgentAttempt(options: RunRpcAttemptOptions): Promise
974
979
  return result;
975
980
  } finally {
976
981
  if (attemptToken !== undefined) control?.detach(attemptToken);
977
- if (tmpPromptPath) {
978
- try {
979
- unlinkSync(tmpPromptPath);
980
- } catch {
981
- /* ignore */
982
- }
983
- }
984
- if (tmpPromptDir) {
985
- try {
986
- rmdirSync(tmpPromptDir);
987
- } catch {
988
- /* ignore */
989
- }
990
- }
982
+ // Recursive removal: the directory also holds the temp-hygiene owner marker,
983
+ // so an empty-directory removal always fails and leaks one dir per dispatch.
984
+ if (tmpPromptDir) await rm(tmpPromptDir, { recursive: true, force: true }).catch(() => undefined);
991
985
  await rm(retryPolicy.dir, { recursive: true, force: true }).catch(() => undefined);
992
986
  }
993
987
  }