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
@@ -48,7 +48,7 @@ import {
48
48
  encodeChildWatchdogConfig,
49
49
  type ChildWatchdogConfig,
50
50
  } from "../../watchdog/child-status.ts";
51
- import { WAIT_TOOL_ENABLED_ENV } from "../background/wait-config.ts";
51
+ import { WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV, WAIT_TOOL_ENABLED_ENV } from "../background/wait-config.ts";
52
52
  import {
53
53
  PI_CODING_AGENT_PACKAGE_ROOT_ENV,
54
54
  getAgentDir,
@@ -170,6 +170,11 @@ export interface BuildPiArgsInput {
170
170
  cwd?: string;
171
171
  promptFileStem?: string;
172
172
  intercomSessionName?: string;
173
+ /** Human-readable display name for the child session (agent + task excerpt,
174
+ * derived by the caller). Passed to the child as PI_SUBAGENT_SESSION_NAME;
175
+ * the prompt runtime applies it via pi.setSessionName unless an intercom
176
+ * target takes precedence. */
177
+ sessionName?: string;
173
178
  orchestratorIntercomTarget?: string;
174
179
  runId?: string;
175
180
  childAgentName?: string;
@@ -206,6 +211,8 @@ export interface BuildPiArgsInput {
206
211
  */
207
212
  taskDelivery?: SubagentTaskDelivery;
208
213
  waitToolEnabled?: boolean;
214
+ waitToolDefaultTimeoutMs?: number;
215
+ allowNestedSubagents?: boolean;
209
216
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
210
217
  thinkingCeiling?: import("../../shared/model-info.ts").ThinkingLevel;
211
218
  extensionBindings?: ExtensionBindings;
@@ -283,6 +290,7 @@ function resolveFastModeExtension(input: Pick<ResolvePiLaunchToolPlanInput, "fas
283
290
 
284
291
  export interface ResolvePiLaunchToolPlanInput {
285
292
  tools?: string[];
293
+ allowNestedSubagents?: boolean;
286
294
  extensions?: string[];
287
295
  subagentOnlyExtensions?: string[];
288
296
  mcpDirectTools?: string[];
@@ -481,7 +489,10 @@ export function resolvePiLaunchToolPlan(
481
489
  ? ["read", ...requestedBuiltinTools]
482
490
  : requestedBuiltinTools
483
491
  ).filter((tool) => !allowedToolSet || allowedToolSet.has(tool));
484
- const fanoutAuthorized = declaredBuiltinTools.includes("subagent");
492
+ const fanoutAuthorized = declaredBuiltinTools.includes("subagent") || (
493
+ input.allowNestedSubagents === true &&
494
+ (!allowedToolSet || allowedToolSet.has("subagent"))
495
+ );
485
496
  const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions
486
497
  ? []
487
498
  : (input.tools ?? []).filter(
@@ -684,6 +695,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
684
695
 
685
696
  const toolPlan = resolvePiLaunchToolPlan({
686
697
  tools: input.tools,
698
+ allowNestedSubagents: input.allowNestedSubagents,
687
699
  extensions: input.extensions,
688
700
  subagentOnlyExtensions: input.subagentOnlyExtensions,
689
701
  mcpDirectTools: input.mcpDirectTools,
@@ -795,6 +807,9 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
795
807
  if (input.waitToolEnabled !== undefined) {
796
808
  env[WAIT_TOOL_ENABLED_ENV] = input.waitToolEnabled ? "true" : "false";
797
809
  }
810
+ if (input.waitToolDefaultTimeoutMs !== undefined) {
811
+ env[WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV] = String(input.waitToolDefaultTimeoutMs);
812
+ }
798
813
  const inheritedNestedRoute = Boolean(
799
814
  process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] &&
800
815
  process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] &&
@@ -878,6 +893,9 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
878
893
  if (input.intercomSessionName) {
879
894
  env.PI_SUBAGENT_INTERCOM_SESSION_NAME = input.intercomSessionName;
880
895
  }
896
+ if (input.sessionName?.trim()) {
897
+ env.PI_SUBAGENT_SESSION_NAME = input.sessionName.trim();
898
+ }
881
899
  if (input.orchestratorIntercomTarget) {
882
900
  env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = input.orchestratorIntercomTarget;
883
901
  }
@@ -161,6 +161,23 @@ export function validateFileOnlyOutputMode(outputMode: OutputMode | undefined, o
161
161
  return undefined;
162
162
  }
163
163
 
164
+ export function resolveSingleOutputClaimPath(outputPath: string): string {
165
+ let existing = path.resolve(outputPath);
166
+ const missingSegments: string[] = [];
167
+ while (!fs.existsSync(existing)) {
168
+ missingSegments.unshift(path.basename(existing));
169
+ const parent = path.dirname(existing);
170
+ if (parent === existing) break;
171
+ existing = parent;
172
+ }
173
+ return path.join(fs.realpathSync(existing), ...missingSegments);
174
+ }
175
+
176
+ function outputClaimError(outputPath: string, expectedClaimPath: string | undefined): string | undefined {
177
+ if (expectedClaimPath && resolveSingleOutputClaimPath(outputPath) !== expectedClaimPath) return "Output path changed after it was claimed.";
178
+ return undefined;
179
+ }
180
+
164
181
  export function captureSingleOutputSnapshot(outputPath: string | undefined): SingleOutputSnapshot | undefined {
165
182
  if (!outputPath) return undefined;
166
183
  try {
@@ -175,9 +192,12 @@ export function captureSingleOutputSnapshot(outputPath: string | undefined): Sin
175
192
  function persistSingleOutput(
176
193
  outputPath: string | undefined,
177
194
  fullOutput: string,
178
- ): { savedPath?: string; error?: string } {
195
+ expectedClaimPath?: string,
196
+ ): { savedPath?: string; error?: string; fatalError?: boolean } {
179
197
  if (!outputPath) return {};
180
198
  try {
199
+ const claimError = outputClaimError(outputPath, expectedClaimPath);
200
+ if (claimError) return { error: claimError, fatalError: true };
181
201
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
182
202
  fs.writeFileSync(outputPath, fullOutput, "utf-8");
183
203
  return { savedPath: outputPath };
@@ -190,8 +210,11 @@ export function resolveSingleOutput(
190
210
  outputPath: string | undefined,
191
211
  fallbackOutput: string,
192
212
  beforeRun: SingleOutputSnapshot | undefined,
193
- ): { fullOutput: string; savedPath?: string; saveError?: string } {
213
+ expectedClaimPath?: string,
214
+ ): { fullOutput: string; savedPath?: string; saveError?: string; fatalError?: boolean } {
194
215
  if (!outputPath) return { fullOutput: fallbackOutput };
216
+ const claimError = outputClaimError(outputPath, expectedClaimPath);
217
+ if (claimError) return { fullOutput: fallbackOutput, saveError: claimError, fatalError: true };
195
218
 
196
219
  let changedSinceStart = false;
197
220
  try {
@@ -220,9 +243,9 @@ export function resolveSingleOutput(
220
243
  }
221
244
  }
222
245
 
223
- const save = persistSingleOutput(outputPath, fallbackOutput);
246
+ const save = persistSingleOutput(outputPath, fallbackOutput, expectedClaimPath);
224
247
  if (save.savedPath) return { fullOutput: fallbackOutput, savedPath: save.savedPath };
225
- return { fullOutput: fallbackOutput, saveError: save.error };
248
+ return { fullOutput: fallbackOutput, saveError: save.error, ...(save.fatalError ? { fatalError: true } : {}) };
226
249
  }
227
250
 
228
251
  export function finalizeSingleOutput(params: {
@@ -32,6 +32,9 @@ import { drainOutstandingWork } from "../background/auto-drain.ts";
32
32
  const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = "PI_SUBAGENT_INHERIT_PROJECT_CONTEXT";
33
33
  const SUBAGENT_INHERIT_SKILLS_ENV = "PI_SUBAGENT_INHERIT_SKILLS";
34
34
  export const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "PI_SUBAGENT_INTERCOM_SESSION_NAME";
35
+ /** Human-readable child display name (agent + task excerpt) set by the parent
36
+ * at launch; applied via pi.setSessionName when no intercom target exists. */
37
+ export const SUBAGENT_SESSION_NAME_ENV = "PI_SUBAGENT_SESSION_NAME";
35
38
  const STEERING_LEGACY_SETTLE_FALLBACK_MS = 1000;
36
39
  const STEERING_SAFETY_POLL_INTERVAL_MS = 5000;
37
40
 
@@ -288,6 +291,7 @@ const PORTABLE_TOOL_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
288
291
  const MAX_PORTABLE_TOOL_ID_LENGTH = 64;
289
292
  const COMPOSITE_TOOL_ID_APIS = new Set([
290
293
  "azure-openai-responses",
294
+ "cursor-native",
291
295
  "openai-completions",
292
296
  "openai-responses",
293
297
  ]);
@@ -671,7 +675,7 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
671
675
  registerPermissionGate(pi);
672
676
  registerToolBudget(pi, decodeToolBudgetEnv(process.env[TOOL_BUDGET_ENV], { allowZero: process.env[TOOL_BUDGET_ZERO_AUTH_ENV] === "1" }));
673
677
  registerChildWatchdog(pi);
674
- const waitToolEnabled = resolveWaitToolConfig().enabled;
678
+ const waitToolConfig = resolveWaitToolConfig();
675
679
  const waitState = {
676
680
  baseCwd: "",
677
681
  currentSessionId: null,
@@ -686,7 +690,7 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
686
690
  watcherRestartTimer: null,
687
691
  resultFileCoalescer: { schedule: () => false, clear: () => {} },
688
692
  } as unknown as SubagentState;
689
- if (typeof pi.registerTool === "function") registerWaitTool(pi, waitState, waitToolEnabled);
693
+ if (typeof pi.registerTool === "function") registerWaitTool(pi, waitState, waitToolConfig.enabled, undefined, waitToolConfig.defaultTimeoutMs);
690
694
  let nativeSupervisorClientRegistered = false;
691
695
  const registerNativeSupervisorClientOnce = (): void => {
692
696
  if (nativeSupervisorClientRegistered) return;
@@ -756,9 +760,14 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
756
760
  onRuntimeEvent("before_agent_start", async (event: unknown) => {
757
761
  if (!event || typeof event !== "object" || !("systemPrompt" in event) || typeof event.systemPrompt !== "string") return undefined;
758
762
  registerNativeSupervisorClientOnce();
763
+ // The intercom target is a routing address and always wins; the display
764
+ // name (agent + task excerpt, computed by the parent at launch) only
765
+ // applies when the bridge is not addressing this child.
759
766
  const intercomSessionName = process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV]?.trim();
760
- if (intercomSessionName && typeof pi.setSessionName === "function") {
761
- pi.setSessionName(intercomSessionName);
767
+ const displaySessionName = process.env[SUBAGENT_SESSION_NAME_ENV]?.trim();
768
+ const childSessionName = intercomSessionName || displaySessionName;
769
+ if (childSessionName && typeof pi.setSessionName === "function") {
770
+ pi.setSessionName(childSessionName);
762
771
  }
763
772
 
764
773
  const inheritProjectContext = readBooleanEnv(SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV);