pi-subagents 0.58.0 → 0.59.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/docs/agents.md +5 -3
  3. package/docs/configuration.md +4 -4
  4. package/docs/extension-api.md +1 -1
  5. package/docs/models.md +24 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +89 -4
  8. package/docs/workflows.md +93 -2
  9. package/package.json +3 -1
  10. package/prompts/review-loop.md +2 -2
  11. package/skills/council-mode/SKILL.md +1 -1
  12. package/skills/pi-subagents/SKILL.md +1 -1
  13. package/skills/pi-subagents/references/execution-controls.md +5 -1
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -2
  15. package/skills/pi-subagents/references/prompting-and-roles.md +24 -3
  16. package/src/agents/agent-management.ts +5 -17
  17. package/src/agents/agent-serializer.ts +4 -2
  18. package/src/agents/agents.ts +67 -26
  19. package/src/agents/runtime-agent-registry.ts +9 -16
  20. package/src/api/background-work.ts +5 -1
  21. package/src/api/delegation.ts +0 -7
  22. package/src/api/preflight.ts +6 -9
  23. package/src/extension/fanout-child.ts +5 -3
  24. package/src/extension/index.ts +51 -23
  25. package/src/extension/public-execution.ts +15 -2
  26. package/src/extension/schemas.ts +35 -10
  27. package/src/extension/tool-description.ts +18 -3
  28. package/src/intercom/result-intercom.ts +2 -0
  29. package/src/profiles/profiles.ts +5 -6
  30. package/src/runs/background/active-async-capacity.ts +2 -2
  31. package/src/runs/background/async-execution.ts +50 -35
  32. package/src/runs/background/async-job-tracker.ts +14 -12
  33. package/src/runs/background/async-resume.ts +23 -25
  34. package/src/runs/background/async-status-snapshot.ts +23 -261
  35. package/src/runs/background/async-status.ts +66 -7
  36. package/src/runs/background/chain-append.ts +6 -3
  37. package/src/runs/background/chain-root-attachment.ts +60 -8
  38. package/src/runs/background/fleet-view.ts +21 -11
  39. package/src/runs/background/notify.ts +158 -6
  40. package/src/runs/background/result-files.ts +2 -1
  41. package/src/runs/background/result-watcher.ts +2 -0
  42. package/src/runs/background/resume-guidance.ts +1 -1
  43. package/src/runs/background/retained-children.ts +1 -1
  44. package/src/runs/background/run-status.ts +18 -8
  45. package/src/runs/background/scheduled-runs.ts +86 -7
  46. package/src/runs/background/stale-run-reconciler.ts +10 -4
  47. package/src/runs/background/steering.ts +4 -14
  48. package/src/runs/background/subagent-runner.ts +346 -357
  49. package/src/runs/background/subagent-wait.ts +58 -10
  50. package/src/runs/background/terminal-run-index.ts +1 -1
  51. package/src/runs/background/wait-completions.ts +22 -1
  52. package/src/runs/background/wait-config.ts +23 -9
  53. package/src/runs/background/wait-tool.ts +9 -2
  54. package/src/runs/foreground/async-steering-action.ts +2 -2
  55. package/src/runs/foreground/execution.ts +114 -120
  56. package/src/runs/foreground/foreground-control.ts +3 -0
  57. package/src/runs/foreground/foreground-history.ts +1 -0
  58. package/src/runs/foreground/subagent-executor.ts +478 -239
  59. package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
  60. package/src/runs/shared/abort-recovery.ts +119 -0
  61. package/src/runs/shared/async-status-projection.ts +463 -0
  62. package/src/runs/shared/child-identity.ts +19 -4
  63. package/src/runs/shared/child-launch-plan.ts +151 -0
  64. package/src/runs/shared/completion-evidence.ts +89 -0
  65. package/src/runs/shared/completion-guard.ts +1 -1
  66. package/src/runs/shared/dynamic-fanout.ts +2 -2
  67. package/src/runs/shared/host-step-status.ts +230 -0
  68. package/src/runs/shared/lane-metadata.ts +105 -0
  69. package/src/runs/shared/mcp-config-sources.ts +42 -6
  70. package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
  71. package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  72. package/src/runs/shared/model-fallback.ts +9 -2
  73. package/src/runs/shared/nested-events.ts +6 -2
  74. package/src/runs/shared/nested-render.ts +7 -3
  75. package/src/runs/shared/parallel-handoff.ts +419 -7
  76. package/src/runs/shared/parallel-utils.ts +7 -0
  77. package/src/runs/shared/pi-args.ts +20 -2
  78. package/src/runs/shared/single-output.ts +27 -4
  79. package/src/runs/shared/subagent-prompt-runtime.ts +13 -4
  80. package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  81. package/src/runs/shared/worktree.ts +18 -0
  82. package/src/shared/child-session-name.ts +46 -0
  83. package/src/shared/extension-context.ts +24 -0
  84. package/src/shared/formatters.ts +5 -2
  85. package/src/shared/launch-contract.ts +1 -1
  86. package/src/shared/settings.ts +9 -103
  87. package/src/shared/types.ts +198 -29
  88. package/src/shared/utils.ts +35 -55
  89. package/src/slash/delegation-adapters.ts +1 -8
  90. package/src/slash/delegation-request.ts +0 -4
  91. package/src/slash/slash-bridge.ts +1 -2
  92. package/src/slash/slash-commands.ts +369 -90
  93. package/src/slash/slash-live-state.ts +22 -11
  94. package/src/tui/fleet-status.ts +125 -74
  95. package/src/tui/fleet.ts +11 -5
  96. package/src/tui/render.ts +316 -27
  97. package/src/watchdog/turn-delta.ts +1 -1
  98. package/src/workflows/chat-progress.ts +6 -3
  99. package/src/workflows/host-command.ts +230 -0
  100. package/src/workflows/scripted-workflow.ts +452 -38
  101. package/src/workflows/workflow-child-summary.ts +9 -5
  102. package/src/workflows/workflow-preflight.ts +270 -0
  103. package/src/workflows/workflow-receipt.ts +43 -4
  104. package/src/workflows/workflow-settlement.ts +246 -0
  105. package/src/runs/shared/turn-budget.ts +0 -98
@@ -77,6 +77,8 @@ interface CreateWorktreesOptions {
77
77
  agents?: string[];
78
78
  setupHook?: WorktreeSetupHookConfig;
79
79
  baseDir?: string;
80
+ /** Called with deterministic ownership metadata before any worktree is created. */
81
+ beforeCreate?: (setup: WorktreeSetup) => void;
80
82
  }
81
83
 
82
84
  interface ResolvedWorktreeSetupHook {
@@ -680,6 +682,22 @@ export function createWorktrees(cwd: string, runId: string, count: number, optio
680
682
  const repo = resolveRepoState(cwd);
681
683
  const setupHook = resolveWorktreeSetupHook(repo.toplevel, options?.setupHook);
682
684
  const baseDir = resolveWorktreeBaseDir(options?.baseDir, repo.toplevel);
685
+ const plannedSetup: WorktreeSetup = {
686
+ cwd: repo.toplevel,
687
+ baseCommit: repo.baseCommit,
688
+ worktrees: Array.from({ length: count }, (_, index) => {
689
+ const worktreePath = buildWorktreePath(baseDir, runId, index);
690
+ return {
691
+ path: worktreePath,
692
+ agentCwd: repo.cwdRelative ? path.join(worktreePath, repo.cwdRelative) : worktreePath,
693
+ branch: buildWorktreeBranch(runId, index),
694
+ index,
695
+ nodeModulesLinked: false,
696
+ syntheticPaths: [],
697
+ };
698
+ }),
699
+ };
700
+ options?.beforeCreate?.(plannedSetup);
683
701
  const worktrees: WorktreeInfo[] = [];
684
702
 
685
703
  try {
@@ -0,0 +1,46 @@
1
+ import { previewDisplayText } from "./display-text.ts";
2
+ import { PROMPT_REDACTED } from "./utils.ts";
3
+
4
+ /**
5
+ * Human-readable display name for a child session, derived at launch time from
6
+ * the agent name and its task (or workflow node label). The parent computes it
7
+ * once and threads it two ways:
8
+ *
9
+ * 1. Into the child process via `PI_SUBAGENT_SESSION_NAME`, where the prompt
10
+ * runtime calls `pi.setSessionName(...)` so the child's own session file
11
+ * is identifiable in `pi --resume` and host session browsers.
12
+ * 2. Into the `sessionName` field of result/progress payloads, so hosts
13
+ * rendering the parent stream can label each child row.
14
+ *
15
+ * The name is display-only metadata. When the intercom bridge is active the
16
+ * child keeps its machine intercom target as the session name instead — that
17
+ * name is a routing address and must win (see subagent-prompt-runtime).
18
+ */
19
+
20
+ /** Longest task excerpt kept in the name; the full string is capped below. */
21
+ const TASK_EXCERPT_MAX_CHARS = 60;
22
+ /** Hard cap on the final name so host UI rows stay one line. */
23
+ export const CHILD_SESSION_NAME_MAX_CHARS = 80;
24
+
25
+ export function deriveChildSessionName(input: {
26
+ agent?: string;
27
+ task?: string;
28
+ /** Workflow node label; preferred over the task excerpt when present. */
29
+ label?: string;
30
+ }): string | undefined {
31
+ const agent = input.agent?.trim() ?? "";
32
+ const rawLabel = input.label?.trim() ?? "";
33
+ const rawTask = input.task?.trim() ?? "";
34
+ // Never build a name from redacted text — the excerpt would be meaningless
35
+ // and the redaction marker itself carries no information.
36
+ const excerptSource =
37
+ rawLabel && rawLabel !== PROMPT_REDACTED
38
+ ? rawLabel
39
+ : rawTask && rawTask !== PROMPT_REDACTED
40
+ ? rawTask
41
+ : "";
42
+ const excerpt = excerptSource ? previewDisplayText(excerptSource, TASK_EXCERPT_MAX_CHARS) : "";
43
+ const base = agent && excerpt ? `${agent}: ${excerpt}` : agent || excerpt;
44
+ if (!base) return undefined;
45
+ return previewDisplayText(base, CHILD_SESSION_NAME_MAX_CHARS);
46
+ }
@@ -0,0 +1,24 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+
3
+ /** Pi exposes replaced extension contexts as ordinary Errors without a stable code. */
4
+ export function isStaleExtensionContextError(error: unknown): boolean {
5
+ return error instanceof Error
6
+ && /extension ctx is stale|extension context no longer active|stale after session replacement or reload/i.test(error.message);
7
+ }
8
+
9
+ /** Run a synchronous operation against a cached UI context without leaking replacement errors. */
10
+ export function withCachedUiContext<T>(
11
+ ctx: ExtensionContext | null | undefined,
12
+ onStale: () => void,
13
+ run: (ctx: ExtensionContext) => T,
14
+ ): T | undefined {
15
+ if (!ctx) return undefined;
16
+ try {
17
+ if (!ctx.hasUI) return undefined;
18
+ return run(ctx);
19
+ } catch (error) {
20
+ if (!isStaleExtensionContextError(error)) throw error;
21
+ onStale();
22
+ return undefined;
23
+ }
24
+ }
@@ -11,10 +11,13 @@ import { previewDisplayText, sanitizeDisplayText } from "./display-text.ts";
11
11
  import { splitKnownThinkingSuffix, THINKING_LEVELS } from "./model-info.ts";
12
12
 
13
13
  /**
14
- * Format token count with k suffix for large numbers
14
+ * Format token count for compact display.
15
15
  */
16
16
  export function formatTokens(n: number): string {
17
- return n < 1000 ? String(n) : n < 10000 ? `${(n / 1000).toFixed(1)}k` : `${Math.round(n / 1000)}k`;
17
+ if (n < 1000) return String(n);
18
+ if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
19
+ if (n < 999_500) return `${Math.round(n / 1000)}k`;
20
+ return `${Number((n / 1_000_000).toFixed(1))}M`;
18
21
  }
19
22
 
20
23
  export function formatTokenUsage(usage: TokenUsage, legacyLabel = "tok"): string {
@@ -51,6 +51,7 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
51
51
  fast: agent.fast,
52
52
  thinking: agent.thinking,
53
53
  tools: agent.tools,
54
+ allowNestedSubagents: agent.allowNestedSubagents,
54
55
  mcpDirectTools: agent.mcpDirectTools,
55
56
  extensions: agent.extensions,
56
57
  subagentOnlyExtensions: agent.subagentOnlyExtensions,
@@ -63,7 +64,6 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
63
64
  defaultContext: agent.defaultContext,
64
65
  defaultAsync: agent.defaultAsync,
65
66
  defaultTimeoutMs: agent.defaultTimeoutMs,
66
- defaultTurnBudget: agent.defaultTurnBudget,
67
67
  defaultAcceptance: agent.defaultAcceptance,
68
68
  acceptanceRole: agent.acceptanceRole,
69
69
  interactive: agent.interactive,
@@ -7,41 +7,19 @@ import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type AgentScope, type UnknownAgentDiagnosticContext } from "../agents/agents.ts";
9
9
  import { normalizeSkillInput } from "../agents/skills.ts";
10
+ import { normalizeOutputOverride, type OutputOverrideInput, type ResolvedStepBehavior } from "../runs/shared/child-launch-plan.ts";
10
11
  import { CHAIN_RUNS_DIR, type AcceptanceInput, type AgentContract, type ChainGateLayer, type JsonSchemaObject, type OutputMode, type ToolBudgetConfig } from "./types.ts";
11
12
  const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
12
13
  const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
13
14
 
14
- // =============================================================================
15
- // Behavior Resolution Types
16
- // =============================================================================
17
-
18
- export interface ResolvedStepBehavior {
19
- output: string | false;
20
- outputMode: OutputMode;
21
- reads: string[] | false;
22
- progress: boolean;
23
- skills: string[] | false;
24
- model?: string;
25
- fast?: boolean;
26
- }
27
-
28
- export type OutputOverrideInput = string | boolean;
29
-
30
- export interface StepOverrides {
31
- output?: OutputOverrideInput;
32
- outputMode?: OutputMode;
33
- reads?: string[] | false;
34
- progress?: boolean;
35
- skills?: string[] | false;
36
- model?: string;
37
- fast?: boolean;
38
- }
39
-
40
- function normalizeOutputOverride(output: unknown): string | false | undefined {
41
- if (output === false || output === "false") return false;
42
- if (output === true || output === "true") return undefined;
43
- return typeof output === "string" && output.length > 0 ? output : undefined;
44
- }
15
+ export {
16
+ planChildLaunch,
17
+ resolveStepBehavior,
18
+ resolveTaskTextForFileUpdatePolicy,
19
+ suppressProgressForReadOnlyTask,
20
+ taskDisallowsFileUpdates,
21
+ } from "../runs/shared/child-launch-plan.ts";
22
+ export type { ChildLaunchPlan, ChildLaunchPlanInput, OutputOverrideInput, ResolvedStepBehavior, StepOverrides } from "../runs/shared/child-launch-plan.ts";
45
23
 
46
24
  // =============================================================================
47
25
  // Chain Step Types
@@ -239,78 +217,6 @@ export function resolveChainTemplates(
239
217
  });
240
218
  }
241
219
 
242
- // =============================================================================
243
- // Behavior Resolution
244
- // =============================================================================
245
-
246
- /**
247
- * Resolve effective chain behavior per step.
248
- * Priority: step override > agent frontmatter > false (disabled)
249
- */
250
- export function resolveStepBehavior(
251
- agentConfig: AgentConfig,
252
- stepOverrides: StepOverrides,
253
- chainSkills?: string[],
254
- ): ResolvedStepBehavior {
255
- // Output: step override > frontmatter > false (no output)
256
- const stepOutput = normalizeOutputOverride(stepOverrides.output);
257
- const output =
258
- stepOutput !== undefined
259
- ? stepOutput
260
- : normalizeOutputOverride(agentConfig.output) ?? false;
261
-
262
- // Reads: step override > frontmatter defaultReads > false (no reads)
263
- const reads =
264
- stepOverrides.reads !== undefined
265
- ? stepOverrides.reads
266
- : agentConfig.defaultReads ?? false;
267
-
268
- // Progress: step override > frontmatter defaultProgress > false
269
- const progress =
270
- stepOverrides.progress !== undefined
271
- ? stepOverrides.progress
272
- : agentConfig.defaultProgress ?? false;
273
-
274
- let skills: string[] | false;
275
- if (stepOverrides.skills === false) {
276
- skills = false;
277
- } else if (stepOverrides.skills !== undefined) {
278
- skills = [...stepOverrides.skills];
279
- if (chainSkills && chainSkills.length > 0) {
280
- skills = [...new Set([...skills, ...chainSkills])];
281
- }
282
- } else {
283
- skills = agentConfig.skills ? [...agentConfig.skills] : [];
284
- if (chainSkills && chainSkills.length > 0) {
285
- skills = [...new Set([...skills, ...chainSkills])];
286
- }
287
- }
288
-
289
- const outputMode = stepOverrides.outputMode ?? agentConfig.outputMode ?? "inline";
290
- const model = stepOverrides.model ?? agentConfig.model;
291
- const fast = stepOverrides.fast ?? agentConfig.fast;
292
- return { output, outputMode, reads, progress, skills, model, fast };
293
- }
294
-
295
- export function resolveTaskTextForFileUpdatePolicy(task: string | undefined, originalTask?: string): string | undefined {
296
- if (!task) return originalTask;
297
- return originalTask ? task.replaceAll("{task}", originalTask) : task;
298
- }
299
-
300
- export function taskDisallowsFileUpdates(task: string | undefined): boolean {
301
- if (!task) return false;
302
- return /\breview[- ]only\b/i.test(task)
303
- || /\bread[- ]only\s+(?:review|audit|inspection|pass)\b/i.test(task)
304
- || /\b(?:no|without)\s+(?:file\s+)?edits?\b/i.test(task)
305
- || /\b(?:do not|don't|must not)\s+(?:edit|modify|write|touch)\b/i.test(task)
306
- || /\bleave\s+files?\s+unchanged\b/i.test(task);
307
- }
308
-
309
- export function suppressProgressForReadOnlyTask(behavior: ResolvedStepBehavior, task: string | undefined, originalTask?: string): ResolvedStepBehavior {
310
- const policyTask = resolveTaskTextForFileUpdatePolicy(task, originalTask);
311
- return behavior.progress && taskDisallowsFileUpdates(policyTask) ? { ...behavior, progress: false } : behavior;
312
- }
313
-
314
220
  // =============================================================================
315
221
  // Chain Instruction Injection
316
222
  // =============================================================================