pi-subagents 0.56.0 → 0.57.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 (83) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/agents/claude-code-writer.md +15 -0
  3. package/agents/claude-code.md +15 -0
  4. package/agents/codex-exec-writer.md +15 -0
  5. package/agents/codex-exec.md +15 -0
  6. package/agents/cursor-agent-writer.md +14 -0
  7. package/agents/cursor-agent.md +14 -0
  8. package/docs/agents.md +111 -15
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +2 -2
  11. package/docs/observability.md +8 -7
  12. package/docs/tool-reference.md +11 -1
  13. package/docs/workflows.md +20 -0
  14. package/package.json +1 -1
  15. package/skills/pi-subagents/references/execution-controls.md +1 -1
  16. package/src/agents/agent-management.ts +13 -5
  17. package/src/agents/agent-refinements.ts +4 -4
  18. package/src/agents/agents.ts +219 -48
  19. package/src/agents/builtin-names.ts +6 -0
  20. package/src/agents/runtime-agent-registry.ts +8 -2
  21. package/src/api/preflight.ts +2 -2
  22. package/src/extension/config.ts +70 -0
  23. package/src/extension/doctor.ts +3 -3
  24. package/src/extension/index.ts +18 -4
  25. package/src/extension/public-execution.ts +29 -13
  26. package/src/extension/rpc.ts +20 -5
  27. package/src/extension/schemas.ts +4 -3
  28. package/src/extension/tool-description.ts +11 -9
  29. package/src/inspectors/herdr/actions.ts +2 -1
  30. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  31. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  32. package/src/integrations/herdr-status.ts +46 -3
  33. package/src/runs/background/active-async-capacity.ts +77 -10
  34. package/src/runs/background/async-execution.ts +100 -9
  35. package/src/runs/background/async-job-tracker.ts +5 -0
  36. package/src/runs/background/async-retention.ts +20 -3
  37. package/src/runs/background/async-status.ts +5 -0
  38. package/src/runs/background/chain-root-attachment.ts +15 -1
  39. package/src/runs/background/fleet-view.ts +16 -10
  40. package/src/runs/background/inspect-rpc.ts +8 -8
  41. package/src/runs/background/result-files.ts +27 -14
  42. package/src/runs/background/result-watcher.ts +9 -5
  43. package/src/runs/background/run-status.ts +33 -6
  44. package/src/runs/background/scheduled-runs.ts +7 -1
  45. package/src/runs/background/subagent-runner.ts +177 -44
  46. package/src/runs/background/wait-completions.ts +4 -0
  47. package/src/runs/foreground/execution.ts +72 -10
  48. package/src/runs/foreground/foreground-control.ts +6 -0
  49. package/src/runs/foreground/foreground-history.ts +22 -1
  50. package/src/runs/foreground/subagent-executor.ts +270 -81
  51. package/src/runs/foreground/workflow-detach-reconcile.ts +16 -1
  52. package/src/runs/shared/child-protocol.ts +21 -7
  53. package/src/runs/shared/claude-code-adapter.ts +129 -0
  54. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  55. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  56. package/src/runs/shared/external-cli-contract.ts +167 -0
  57. package/src/runs/shared/external-cli-preflight.ts +122 -0
  58. package/src/runs/shared/external-cli-runner.ts +348 -55
  59. package/src/runs/shared/model-exclusions.ts +57 -7
  60. package/src/runs/shared/model-fallback.ts +23 -1
  61. package/src/runs/shared/mutation-evidence.ts +7 -2
  62. package/src/runs/shared/nested-events.ts +3 -1
  63. package/src/runs/shared/nested-render.ts +2 -2
  64. package/src/runs/shared/process-signal.ts +13 -0
  65. package/src/runs/shared/run-history.ts +21 -1
  66. package/src/shared/fork-context.ts +21 -0
  67. package/src/shared/formatters.ts +7 -1
  68. package/src/shared/pruned-fork.ts +450 -0
  69. package/src/shared/session-file-trust.ts +19 -0
  70. package/src/shared/session-tokens.ts +14 -3
  71. package/src/shared/settings.ts +10 -2
  72. package/src/shared/shortcuts.ts +17 -0
  73. package/src/shared/types.ts +118 -9
  74. package/src/shared/workflow-child-permit.ts +116 -0
  75. package/src/slash/delegation-adapters.ts +0 -1
  76. package/src/slash/slash-commands.ts +8 -6
  77. package/src/tui/fleet-status.ts +27 -10
  78. package/src/tui/fleet-transcript.ts +11 -5
  79. package/src/tui/fleet.ts +28 -13
  80. package/src/tui/render.ts +47 -21
  81. package/src/workflows/scripted-workflow.ts +248 -27
  82. package/src/workflows/workflow-child-summary.ts +117 -0
  83. package/src/workflows/workflow-receipt.ts +119 -4
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
6
6
  import { existsSync, unlinkSync } from "node:fs";
7
7
  import * as path from "node:path";
8
8
  import type { Message } from "@earendil-works/pi-ai";
9
- import type { AgentConfig } from "../../agents/agents.ts";
9
+ import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
10
10
  import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
11
11
  import { alignForkedSessionCwd } from "../../shared/fork-context.ts";
12
12
  import {
@@ -68,7 +68,7 @@ import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling,
68
68
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
69
69
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
70
70
  import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
71
- import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
71
+ import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
72
72
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
73
73
  import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
74
74
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
@@ -77,7 +77,7 @@ import {
77
77
  formatSubagentModelVerificationError,
78
78
  formatModelAttemptNote,
79
79
  isContextOverflow,
80
- isRetryableModelFailure,
80
+ isRetryableModelFailureAttempt,
81
81
  recordRetryableModelFailure,
82
82
  } from "../shared/model-fallback.ts";
83
83
  import {
@@ -106,7 +106,8 @@ import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudget
106
106
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
107
107
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
108
108
  import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
109
- import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
109
+ import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.ts";
110
+ import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
110
111
  import {
111
112
  acceptChildWatchdogEvent,
112
113
  childWatchdogIsActive,
@@ -538,9 +539,29 @@ async function runSingleAttempt(
538
539
  let structuredOutputToolInvoked = false;
539
540
  let structuredOutputMessageStartIndex: number | undefined;
540
541
  let toolAvailabilityError: string | undefined;
542
+ let abortedBySignal = options.signal?.aborted === true;
541
543
 
544
+ const spawnSpec = getPiSpawnCommand(args);
545
+ if (options.workflowChildPermitLaunch) {
546
+ const permitError = consumeWorkflowChildPermit(options.workflowChildPermitLaunch.permit, {
547
+ workflowRunId: options.workflowChildPermitLaunch.workflowRunId,
548
+ childKey: options.workflowChildPermitLaunch.childKey,
549
+ agent: agent.name,
550
+ launchContractDigest,
551
+ context: options.context ?? "fresh",
552
+ runner: "pi",
553
+ });
554
+ if (permitError) {
555
+ cleanupTempDir(tempDir);
556
+ result.exitCode = 1;
557
+ result.error = permitError;
558
+ result.finalOutput = permitError;
559
+ progress.status = "failed";
560
+ progress.error = permitError;
561
+ return result;
562
+ }
563
+ }
542
564
  const exitCode = await new Promise<number>((resolve) => {
543
- const spawnSpec = getPiSpawnCommand(args);
544
565
  const proc = spawn(spawnSpec.command, spawnSpec.args, {
545
566
  cwd: options.cwd ?? runtimeCwd,
546
567
  env: spawnEnv,
@@ -707,6 +728,7 @@ async function runSingleAttempt(
707
728
  }
708
729
  if (action === "start-drain") startFinalDrain();
709
730
  };
731
+ const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
710
732
 
711
733
  const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
712
734
  if (!options.allowIntercomDetach || processClosed) return;
@@ -994,8 +1016,9 @@ async function runSingleAttempt(
994
1016
  }
995
1017
  shared.transcriptWriter?.writeChildEvent(evt);
996
1018
  shared.orcaProgressTab?.event(evt);
997
- if (evt.type === "agent_settled") agentSettledReceived = true;
998
- applyChildLifecycle(projectChildLifecycle(evt));
1019
+ const lifecycleAction = projectChildLifecycle(evt, false, childLifecycleState);
1020
+ if (evt.type === "agent_settled" && lifecycleAction === "start-drain") agentSettledReceived = true;
1021
+ applyChildLifecycle(lifecycleAction);
999
1022
 
1000
1023
  if (isChildWatchdogStatusEvent(evt)) {
1001
1024
  if (!childWatchdog) return;
@@ -1078,6 +1101,7 @@ async function runSingleAttempt(
1078
1101
  updateTurnBudget(result.usage.turns, terminalAssistantStop || terminalStructuredOutputCall, hasToolCall || Boolean(progress.currentTool));
1079
1102
  const u = evt.message.usage;
1080
1103
  if (u) {
1104
+ const window = (u.input || 0) + (u.cacheRead || 0);
1081
1105
  result.usage.input += u.input || 0;
1082
1106
  result.usage.output += u.output || 0;
1083
1107
  result.usage.cacheRead += u.cacheRead || 0;
@@ -1086,6 +1110,8 @@ async function runSingleAttempt(
1086
1110
  progress.tokens = result.usage.input + result.usage.output;
1087
1111
  progress.inputTokens = result.usage.input;
1088
1112
  progress.outputTokens = result.usage.output;
1113
+ progress.window = window;
1114
+ progress.windowPeak = Math.max(progress.windowPeak ?? 0, window);
1089
1115
  }
1090
1116
  if (evt.message.model) {
1091
1117
  progress.model = evt.message.model;
@@ -1102,7 +1128,11 @@ async function runSingleAttempt(
1102
1128
  if (terminalAssistantStop) {
1103
1129
  if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
1104
1130
  cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
1105
- applyChildLifecycle(projectChildLifecycle(evt, true));
1131
+ clearAllToolTimeouts();
1132
+ activeToolCalls.clear();
1133
+ activeToolKeysByName.clear();
1134
+ refreshCurrentTool();
1135
+ applyChildLifecycle(projectChildLifecycle(evt, true, childLifecycleState));
1106
1136
  }
1107
1137
  }
1108
1138
  updateActivityState(now);
@@ -1369,6 +1399,7 @@ async function runSingleAttempt(
1369
1399
  if (options.signal) {
1370
1400
  const kill = () => {
1371
1401
  if (processClosed || lifecycleFinished) return;
1402
+ abortedBySignal = true;
1372
1403
  proc.kill("SIGTERM");
1373
1404
  setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
1374
1405
  };
@@ -1431,6 +1462,22 @@ async function runSingleAttempt(
1431
1462
  };
1432
1463
  return result;
1433
1464
  }
1465
+ if (progress.currentTool
1466
+ && isOrdinaryToolForMidToolExit(progress.currentTool)
1467
+ && !abortedBySignal
1468
+ && !result.timedOut
1469
+ && !result.stopped
1470
+ && !result.turnBudgetExceeded
1471
+ && !result.protocolError
1472
+ && !toolAvailabilityError) {
1473
+ const processExitCode = result.exitCode;
1474
+ result.exitCode = 1;
1475
+ result.error = formatMidToolExitError({
1476
+ toolName: progress.currentTool,
1477
+ exitCode: processExitCode,
1478
+ processSignal: result.processSignal,
1479
+ });
1480
+ }
1434
1481
  result.error = formatSubagentExtensionConflictError(result.error, {
1435
1482
  agent: agent.name,
1436
1483
  ambientExtensionsEnabled: !launchResolvedExtensions.disableAmbientExtensions,
@@ -1647,6 +1694,8 @@ async function runSyncCompletionInner(
1647
1694
  };
1648
1695
  const agent = agents.find((a) => a.name === agentName);
1649
1696
  if (!agent) {
1697
+ const diagnosticContext = options.unknownAgentDiagnosticContext
1698
+ ?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(options.cwd ?? runtimeCwd), "both"));
1650
1699
  return redactResultPrompt(withRunContext({
1651
1700
  index: options.index ?? 0,
1652
1701
  agent: agentName,
@@ -1654,7 +1703,7 @@ async function runSyncCompletionInner(
1654
1703
  exitCode: 1,
1655
1704
  messages: [],
1656
1705
  usage: emptyUsage(),
1657
- error: `Unknown agent: ${agentName}`,
1706
+ error: formatUnknownAgentError(agentName, diagnosticContext),
1658
1707
  }, options.context));
1659
1708
  }
1660
1709
  options = {
@@ -1781,6 +1830,18 @@ async function runSyncCompletionInner(
1781
1830
  agent.modelProvider ?? options.preferredModelProvider,
1782
1831
  { scope: options.modelScope, primaryModelFromParent: options.modelOverrideFromParent },
1783
1832
  );
1833
+ if (options.workflowChildPermitLaunch && candidates.length > 1) {
1834
+ const error = "Workflow child permit does not support model fallback.";
1835
+ return redactResultPrompt(withRunContext({
1836
+ index: options.index ?? 0,
1837
+ agent: agent.name,
1838
+ task,
1839
+ exitCode: 1,
1840
+ messages: [],
1841
+ usage: emptyUsage(),
1842
+ error,
1843
+ }, options.context));
1844
+ }
1784
1845
  try {
1785
1846
  for (const candidate of candidates) {
1786
1847
  const model = applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
@@ -1914,6 +1975,7 @@ async function runSyncCompletionInner(
1914
1975
  usage: { ...result.usage },
1915
1976
  };
1916
1977
  modelAttempts.push(attempt);
1978
+ if (options.workflowChildPermitLaunch && !attemptSucceeded) break modelAttemptsLoop;
1917
1979
  // Preserve the legacy intercom handoff contract: once this logical run has
1918
1980
  // been handed to a supervisor, terminating that attempt must not launch a
1919
1981
  // startup retry or model fallback. Explicit user detach retains fallback.
@@ -1986,7 +2048,7 @@ async function runSyncCompletionInner(
1986
2048
  attempt.error = startupError;
1987
2049
  break modelAttemptsLoop;
1988
2050
  }
1989
- const retryableModelFailure = isRetryableModelFailure(result.error);
2051
+ const retryableModelFailure = isRetryableModelFailureAttempt({ error: result.error, messages: result.messages, toolCount: result.progressSummary?.toolCount });
1990
2052
  if (retryableModelFailure) recordRetryableModelFailure(result.model ?? candidate, result.error);
1991
2053
  if (isContextOverflow(result.error)) {
1992
2054
  result.contextOverflow = true;
@@ -27,6 +27,8 @@ function copyProgress(target: ForegroundChildControl, progress: AgentProgress |
27
27
  target.tokens = progress.tokens;
28
28
  target.inputTokens = progress.inputTokens;
29
29
  target.outputTokens = progress.outputTokens;
30
+ target.window = progress.window;
31
+ target.windowPeak = progress.windowPeak;
30
32
  target.model = progress.model;
31
33
  target.thinking = progress.thinking;
32
34
  target.toolCount = progress.toolCount;
@@ -45,6 +47,8 @@ function syncCurrentChild(control: ForegroundRunControl, child: ForegroundChildC
45
47
  control.tokens = child.tokens;
46
48
  control.inputTokens = child.inputTokens;
47
49
  control.outputTokens = child.outputTokens;
50
+ control.window = child.window;
51
+ control.windowPeak = child.windowPeak;
48
52
  control.model = child.model;
49
53
  control.thinking = child.thinking;
50
54
  control.toolCount = child.toolCount;
@@ -65,6 +69,8 @@ function clearCurrentChild(control: ForegroundRunControl): void {
65
69
  control.tokens = undefined;
66
70
  control.inputTokens = undefined;
67
71
  control.outputTokens = undefined;
72
+ control.window = undefined;
73
+ control.windowPeak = undefined;
68
74
  control.model = undefined;
69
75
  control.thinking = undefined;
70
76
  control.toolCount = undefined;
@@ -4,10 +4,12 @@ import type { ForegroundResumeChild, ForegroundResumeRun, SubagentState } from "
4
4
  import { DIRS } from "../../shared/types.ts";
5
5
  import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
6
6
  import { utf8Tail } from "../../shared/utf8.ts";
7
+ import { validateAcceptanceInput } from "../shared/acceptance.ts";
7
8
 
8
9
  export const MAX_REMEMBERED_FOREGROUND_RUNS = 50;
9
10
  const HISTORY_VERSION = 1;
10
11
  const MAX_INLINE_OUTPUT_BYTES = 64 * 1024;
12
+ const MAX_RESUME_CONTRACT_BYTES = 64 * 1024;
11
13
 
12
14
  interface ForegroundHistoryIndex {
13
15
  version: 1;
@@ -51,6 +53,7 @@ function compactChild(child: ForegroundResumeChild): ForegroundResumeChild {
51
53
  ...(child.transcriptPath ? { transcriptPath: child.transcriptPath } : {}),
52
54
  ...(child.transcriptError ? { transcriptError: child.transcriptError } : {}),
53
55
  ...(child.acceptance ? { acceptance: child.acceptance } : {}),
56
+ ...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
54
57
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
55
58
  ...(child.extensionBindings ? { extensionBindings: child.extensionBindings } : {}),
56
59
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
@@ -62,9 +65,26 @@ function isRestorableForegroundStatus(status: unknown): status is ForegroundResu
62
65
  return status === "completed" || status === "failed" || status === "paused" || status === "stopped";
63
66
  }
64
67
 
68
+ function isRestorableResumeContract(value: unknown): boolean {
69
+ if (value === undefined) return true;
70
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
71
+ try {
72
+ if (Buffer.byteLength(JSON.stringify(value), "utf8") > MAX_RESUME_CONTRACT_BYTES) return false;
73
+ } catch {
74
+ return false;
75
+ }
76
+ const contract = value as NonNullable<ForegroundResumeChild["resumeContract"]>;
77
+ if (Object.keys(contract).some((key) => !["outputSchema", "agentContract", "acceptance", "output", "outputMode"].includes(key))) return false;
78
+ if (contract.outputSchema !== undefined && (!contract.outputSchema || typeof contract.outputSchema !== "object" || Array.isArray(contract.outputSchema))) return false;
79
+ if (contract.agentContract !== undefined && (!contract.agentContract || typeof contract.agentContract !== "object" || Array.isArray(contract.agentContract) || contract.agentContract.version !== 1)) return false;
80
+ if (validateAcceptanceInput(contract.acceptance).length > 0) return false;
81
+ if (contract.output !== undefined && typeof contract.output !== "string" && typeof contract.output !== "boolean") return false;
82
+ return contract.outputMode === undefined || contract.outputMode === "inline" || contract.outputMode === "file-only";
83
+ }
84
+
65
85
  function compactRun(run: ForegroundResumeRun): ForegroundResumeRun | undefined {
66
86
  if (!run.sessionId) return undefined;
67
- if (run.children.length === 0 || !run.children.every((child) => isRestorableForegroundStatus(child.status))) return undefined;
87
+ if (run.children.length === 0 || !run.children.every((child) => isRestorableForegroundStatus(child.status) && isRestorableResumeContract(child.resumeContract))) return undefined;
68
88
  return {
69
89
  runId: run.runId,
70
90
  mode: run.mode,
@@ -102,6 +122,7 @@ function isRestorableRun(value: unknown): value is ForegroundResumeRun {
102
122
  && run.children.every((child) => Boolean(child && typeof child === "object" && !Array.isArray(child)
103
123
  && typeof (child as Partial<ForegroundResumeChild>).agent === "string"
104
124
  && typeof (child as Partial<ForegroundResumeChild>).index === "number"
125
+ && isRestorableResumeContract((child as Partial<ForegroundResumeChild>).resumeContract)
105
126
  && isRestorableForegroundStatus((child as Partial<ForegroundResumeChild>).status)));
106
127
  }
107
128