pi-subagents 0.59.0 → 0.61.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 (72) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/docs/agents.md +2 -2
  3. package/docs/configuration.md +9 -5
  4. package/docs/extension-api.md +14 -7
  5. package/docs/models.md +1 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +15 -3
  8. package/docs/workflows.md +15 -14
  9. package/install.mjs +2 -1
  10. package/package.json +1 -1
  11. package/skills/council-mode/SKILL.md +48 -243
  12. package/skills/council-mode/references/pass-contracts.md +150 -0
  13. package/skills/pi-subagents/SKILL.md +89 -37
  14. package/skills/pi-subagents/references/constraints-and-recipes.md +30 -234
  15. package/skills/pi-subagents/references/execution-controls.md +79 -7
  16. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  17. package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
  18. package/skills/pi-subagents/references/prompting-and-roles.md +35 -28
  19. package/skills/pi-subagents/references/review-and-validation.md +73 -0
  20. package/src/agents/agent-management.ts +256 -88
  21. package/src/agents/agents.ts +527 -221
  22. package/src/api/background-work.ts +7 -2
  23. package/src/api/external-runs.ts +67 -4
  24. package/src/api/preflight.ts +13 -8
  25. package/src/api/shared-types.ts +3 -0
  26. package/src/extension/index.ts +7 -4
  27. package/src/extension/public-execution.ts +48 -4
  28. package/src/extension/rpc.ts +62 -4
  29. package/src/extension/schemas.ts +12 -7
  30. package/src/extension/tool-description.ts +16 -16
  31. package/src/runs/background/async-execution.ts +53 -37
  32. package/src/runs/background/async-job-tracker.ts +65 -3
  33. package/src/runs/background/async-resume.ts +3 -1
  34. package/src/runs/background/async-status.ts +104 -11
  35. package/src/runs/background/auto-drain.ts +1 -1
  36. package/src/runs/background/control-channel.ts +3 -2
  37. package/src/runs/background/fleet-view.ts +1 -1
  38. package/src/runs/background/result-watcher.ts +1 -1
  39. package/src/runs/background/resume-guidance.ts +1 -1
  40. package/src/runs/background/run-status.ts +15 -4
  41. package/src/runs/background/subagent-runner.ts +6 -3
  42. package/src/runs/background/subagent-wait.ts +30 -23
  43. package/src/runs/background/wait-completions.ts +4 -1
  44. package/src/runs/background/wait-tool.ts +24 -18
  45. package/src/runs/foreground/execution.ts +72 -4
  46. package/src/runs/foreground/subagent-executor.ts +276 -59
  47. package/src/runs/shared/acceptance.ts +43 -18
  48. package/src/runs/shared/async-status-projection.ts +138 -4
  49. package/src/runs/shared/background-process-options.ts +9 -0
  50. package/src/runs/shared/host-step-status.ts +1 -0
  51. package/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
  52. package/src/runs/shared/model-fallback.ts +61 -17
  53. package/src/runs/shared/mutation-evidence.ts +52 -3
  54. package/src/runs/shared/permissions.ts +1 -1
  55. package/src/runs/shared/pi-args.ts +47 -1
  56. package/src/runs/shared/single-output.ts +45 -18
  57. package/src/runs/shared/subagent-prompt-runtime.ts +20 -2
  58. package/src/runs/shared/tool-timeout.ts +1 -1
  59. package/src/runs/shared/workflow-graph.ts +16 -0
  60. package/src/shared/types.ts +74 -4
  61. package/src/shared/workflow-child-permit.ts +91 -0
  62. package/src/slash/prompt-template-bridge.ts +37 -1
  63. package/src/slash/slash-commands.ts +18 -26
  64. package/src/tui/fleet-status.ts +11 -3
  65. package/src/tui/render-helpers.ts +31 -0
  66. package/src/tui/render.ts +673 -145
  67. package/src/watchdog/change-signature.ts +40 -1
  68. package/src/workflows/host-command.ts +6 -1
  69. package/src/workflows/scripted-workflow.ts +206 -6
  70. package/src/workflows/workflow-child-summary.ts +1 -1
  71. package/src/workflows/workflow-receipt.ts +41 -4
  72. package/src/workflows/workflow-resources.ts +150 -0
@@ -12,32 +12,38 @@ export function registerWaitTool(
12
12
  subscriptions?: Pick<WaitSubscriptionManager, "arm">,
13
13
  defaultTimeoutMs?: number,
14
14
  ): void {
15
- const tool: ToolDefinition<typeof SubagentWaitParams, Details> = {
16
- name: "subagent_wait",
17
- label: "Subagent Wait",
18
- description: `Block until background work owned by this session changes, then return.
15
+ const description = `Wait for background, provider, or detached work that has no native completion notification, then return.
19
16
 
20
- In an interactive chat, do not call this merely to wait: return control to the user and let Pi wake the session on completion. Override that default and call it when the current request is run-to-completion — for example, the user asked you to report results back before continuing or a skill cannot return before its work finishes. Headless runs auto-drain current-session work at agent_end; call this when the current turn must receive results before it ends.
17
+ Ordinary async subagent runs already notify this session natively when they complete or need attention. In an interactive chat, return control instead of calling this merely to wait. Use this tool for provider jobs, remembered detached foreground runs, or other background work without a native notification path. Headless runs auto-drain current-session subagent work at agent_end; use this tool only when the current turn must receive non-notifying background work results.
21
18
 
22
19
  • { } — return when the first initially active async run or registered provider item finishes, or when a subagent needs attention.
23
20
  • { all: true } — wait for every async run and provider item that was active when the call began.
24
21
  • { id: "..." } — wait for one async or remembered detached foreground subagent run (id or prefix).
25
- • { id: "...", nonBlocking: true } — resolve the prefix once, persist an exact-run wake subscription, and return immediately. The originating interactive session wakes on completion, failure, attention, reconciliation failure, or timeout.
22
+ • { id: "...", nonBlocking: true } — resolve the prefix once, persist an exact-run wake subscription, and return immediately. Use this for detached work without native completion delivery; the originating interactive session wakes on completion, failure, attention, reconciliation failure, or timeout.
26
23
  • { stopOnAttention: false } — for blocking waits only, keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.
27
24
  • { timeoutMs: 600000 } — stop waiting after N ms; active work keeps running. Omitted values use waitTool.defaultTimeoutMs, then 30 minutes. Window expiry returns a non-error window_elapsed result with active work identities.
28
25
 
29
- Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`subagent_wait\` in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : "\n\nConfigured behavior: subagent_wait is disabled by config.waitTool or PI_SUBAGENT_WAIT_TOOL_ENABLED and returns immediately without blocking."}`,
26
+ Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`bg_wait\` (or the deprecated \`subagent_wait\` compatibility alias) in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : "\n\nConfigured behavior: bg_wait and its subagent_wait compatibility alias are disabled by config.waitTool or PI_SUBAGENT_WAIT_TOOL_ENABLED and return immediately without blocking."}`;
27
+ const execute: ToolDefinition<typeof SubagentWaitParams, Details>["execute"] = async (_id, params, signal, onUpdate, ctx) => finalizeToolResult(await waitForSubagents(params, signal, {
28
+ state,
29
+ events: pi.events,
30
+ enabled,
31
+ ...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
32
+ onUpdate,
33
+ ...(subscriptions && ctx?.hasUI ? { subscribe: (input) => subscriptions.arm(input) } : {}),
34
+ }));
35
+ const primaryTool: ToolDefinition<typeof SubagentWaitParams, Details> = {
36
+ name: "bg_wait",
37
+ label: "Background Wait",
38
+ description,
30
39
  parameters: SubagentWaitParams,
31
- async execute(_id, params, signal, onUpdate, ctx) {
32
- return finalizeToolResult(await waitForSubagents(params, signal, {
33
- state,
34
- events: pi.events,
35
- enabled,
36
- ...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
37
- onUpdate,
38
- ...(subscriptions && ctx?.hasUI ? { subscribe: (input) => subscriptions.arm(input) } : {}),
39
- }));
40
- },
40
+ execute,
41
41
  };
42
- pi.registerTool(tool);
42
+ pi.registerTool(primaryTool);
43
+ pi.registerTool({
44
+ ...primaryTool,
45
+ name: "subagent_wait",
46
+ label: "Subagent Wait (deprecated)",
47
+ description: "Deprecated compatibility alias for `bg_wait`. Use `bg_wait` for background, provider, or detached work without a native completion notification. It has the same parameters and behavior.",
48
+ });
43
49
  }
@@ -49,6 +49,7 @@ import {
49
49
  formatEmptyTerminalAssistantResponseError,
50
50
  extractToolArgsPreview,
51
51
  extractTextFromContent,
52
+ MAX_STREAMED_RECENT_TOOLS,
52
53
  boundStreamedRecentTools,
53
54
  boundStreamedRecentOutput,
54
55
  boundStreamedToolCalls,
@@ -66,7 +67,7 @@ import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
66
67
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
67
68
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
68
69
  import { resolvePermissionRules } from "../shared/permissions.ts";
69
- import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
70
+ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
70
71
  import { deriveChildSessionName } from "../../shared/child-session-name.ts";
71
72
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
72
73
  import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
@@ -76,7 +77,7 @@ import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructu
76
77
  import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
77
78
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
78
79
  import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
79
- import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
80
+ import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
80
81
  import {
81
82
  buildModelCandidates,
82
83
  formatSubagentModelVerificationError,
@@ -297,6 +298,55 @@ function snapshotStreamResult(result: SingleResult, progress: AgentProgress): Si
297
298
  return snapshot;
298
299
  }
299
300
 
301
+ interface StructuredDelegationProgressState {
302
+ currentTool?: string;
303
+ currentToolArgs?: string;
304
+ recentOutput: string[];
305
+ recentTools: Array<{ tool: string; args: string }>;
306
+ activityState?: AgentProgress["activityState"];
307
+ model?: string;
308
+ toolCount: number;
309
+ tokens: number;
310
+ }
311
+
312
+ function captureStructuredDelegationProgressState(progress: AgentProgress, result: SingleResult): StructuredDelegationProgressState {
313
+ return {
314
+ currentTool: progress.currentTool,
315
+ currentToolArgs: progress.currentToolArgs,
316
+ recentOutput: [...progress.recentOutput],
317
+ recentTools: progress.recentTools.slice(-MAX_STREAMED_RECENT_TOOLS).map(({ tool, args }) => ({ tool, args })),
318
+ activityState: progress.activityState,
319
+ model: progress.model ?? result.model,
320
+ toolCount: progress.toolCount,
321
+ tokens: progress.tokens,
322
+ };
323
+ }
324
+
325
+ function structuredDelegationProgressChanged(
326
+ previous: StructuredDelegationProgressState,
327
+ progress: AgentProgress,
328
+ result: SingleResult,
329
+ ): boolean {
330
+ if (previous.currentTool !== progress.currentTool
331
+ || previous.currentToolArgs !== progress.currentToolArgs
332
+ || previous.activityState !== progress.activityState
333
+ || previous.model !== (progress.model ?? result.model)
334
+ || previous.toolCount !== progress.toolCount
335
+ || previous.tokens !== progress.tokens
336
+ || previous.recentOutput.length !== progress.recentOutput.length) return true;
337
+ for (let index = 0; index < progress.recentOutput.length; index++) {
338
+ if (previous.recentOutput[index] !== progress.recentOutput[index]) return true;
339
+ }
340
+ const recentToolsStart = Math.max(0, progress.recentTools.length - MAX_STREAMED_RECENT_TOOLS);
341
+ if (previous.recentTools.length !== progress.recentTools.length - recentToolsStart) return true;
342
+ for (let index = recentToolsStart; index < progress.recentTools.length; index++) {
343
+ const previousTool = previous.recentTools[index - recentToolsStart];
344
+ const currentTool = progress.recentTools[index];
345
+ if (previousTool?.tool !== currentTool?.tool || previousTool?.args !== currentTool?.args) return true;
346
+ }
347
+ return false;
348
+ }
349
+
300
350
  const AFTER_COMPACTION_SETTLEMENT = Symbol("afterCompactionSettlement");
301
351
  type AbortRecoverySingleResult = SingleResult & { [AFTER_COMPACTION_SETTLEMENT]?: true };
302
352
 
@@ -380,6 +430,7 @@ async function runSingleAttempt(
380
430
  parentCapabilityToken: options.nestedRoute?.capabilityToken,
381
431
  runFanoutBudget: options.runFanoutBudget,
382
432
  parentSessionId: options.parentSessionId,
433
+ forkCacheKey: options.context === "fork" ? deriveForkPromptCacheKey(options.parentSessionId) : undefined,
383
434
  steerInboxDir: options.steerInboxDir,
384
435
  steerCapabilityPath: options.steerCapabilityPath,
385
436
  steerAckDir: options.steerAckDir,
@@ -935,6 +986,7 @@ async function runSingleAttempt(
935
986
  };
936
987
 
937
988
 
989
+ let lastStructuredDelegationProgressState: StructuredDelegationProgressState | undefined;
938
990
  const emitUpdateSnapshot = (text: string) => {
939
991
  if (!options.onUpdate || processClosed) return;
940
992
  const progressSnapshot = snapshotProgress(progress);
@@ -954,6 +1006,10 @@ async function runSingleAttempt(
954
1006
  const fireUpdate = () => {
955
1007
  if (!options.onUpdate || processClosed) return;
956
1008
  progress.durationMs = Date.now() - startTime;
1009
+ if (options.suppressUnchangedDelegationUpdates) {
1010
+ if (lastStructuredDelegationProgressState && !structuredDelegationProgressChanged(lastStructuredDelegationProgressState, progress, result)) return;
1011
+ lastStructuredDelegationProgressState = captureStructuredDelegationProgressState(progress, result);
1012
+ }
957
1013
  const output = result.timedOut && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages ?? []);
958
1014
  emitUpdateSnapshot(output || "(running...)");
959
1015
  };
@@ -1527,9 +1583,17 @@ async function runSingleAttempt(
1527
1583
  result.outputState = fullOutput.trim() || result.structuredOutput !== undefined ? "present" : "absent";
1528
1584
  if (result.timedOut) {
1529
1585
  const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
1586
+ let requiredOutputMissing: boolean | undefined;
1587
+ if (options.outputMode === "file-only" && options.outputPath) {
1588
+ const outputChanged = hasSingleOutputChangedSinceSnapshot(options.outputPath, shared.outputSnapshot);
1589
+ requiredOutputMissing = outputChanged === undefined ? undefined : !outputChanged;
1590
+ } else if (options.structuredOutput) {
1591
+ requiredOutputMissing = !existsSync(options.structuredOutput.outputPath);
1592
+ }
1530
1593
  result.timeoutRecovery = buildTimeoutRecoverySummary({
1531
1594
  termination: "timed-out",
1532
1595
  evidence: mutationEvidence,
1596
+ requiredOutputMissing,
1533
1597
  currentTool: progress.currentTool,
1534
1598
  currentToolArgs: progress.currentToolArgs,
1535
1599
  currentPath: progress.currentPath,
@@ -1807,7 +1871,11 @@ async function runSyncCompletionInner(
1807
1871
  agent.fallbackModels,
1808
1872
  options.availableModels,
1809
1873
  agent.modelProvider ?? options.preferredModelProvider,
1810
- { scope: options.modelScope, primaryModelFromParent: options.modelOverrideFromParent },
1874
+ {
1875
+ scope: options.modelScope,
1876
+ primaryModelFromParent: options.modelOverrideFromParent,
1877
+ origin: options.modelOrigin ?? (options.modelOverrideFromParent ? "inherited" : "configured"),
1878
+ },
1811
1879
  );
1812
1880
  if (options.workflowChildPermitLaunch && candidates.length > 1) {
1813
1881
  const error = "Workflow child permit does not support model fallback.";
@@ -2150,7 +2218,7 @@ async function runSyncCompletionInner(
2150
2218
  report: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReport,
2151
2219
  reportError: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError,
2152
2220
  fileOutput: childWrittenOutput !== undefined && options.outputPath
2153
- ? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
2221
+ ? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only", durable: result.savedOutputPath !== undefined }
2154
2222
  : undefined,
2155
2223
  cwd: options.cwd ?? runtimeCwd,
2156
2224
  reportOptional: isAgentContractV1(options.agentContract),