pi-subagents 0.28.0 → 0.30.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 (47) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +26 -62
  3. package/package.json +1 -1
  4. package/skills/pi-subagents/SKILL.md +29 -35
  5. package/src/agents/agent-management.ts +29 -22
  6. package/src/agents/agent-selection.ts +2 -0
  7. package/src/agents/agent-serializer.ts +5 -10
  8. package/src/agents/agents.ts +339 -47
  9. package/src/agents/chain-serializer.ts +4 -9
  10. package/src/agents/proactive-skills.ts +191 -0
  11. package/src/extension/doctor.ts +4 -3
  12. package/src/extension/fanout-child.ts +1 -3
  13. package/src/extension/index.ts +6 -9
  14. package/src/extension/schemas.ts +63 -26
  15. package/src/intercom/intercom-bridge.ts +11 -1
  16. package/src/intercom/result-intercom.ts +0 -5
  17. package/src/runs/background/async-execution.ts +186 -74
  18. package/src/runs/background/async-resume.ts +53 -5
  19. package/src/runs/background/async-status.ts +4 -1
  20. package/src/runs/background/chain-append.ts +282 -0
  21. package/src/runs/background/chain-root-attachment.ts +161 -0
  22. package/src/runs/background/run-status.ts +2 -7
  23. package/src/runs/background/subagent-runner.ts +160 -219
  24. package/src/runs/foreground/chain-execution.ts +62 -58
  25. package/src/runs/foreground/execution.ts +39 -343
  26. package/src/runs/foreground/subagent-executor.ts +316 -111
  27. package/src/runs/shared/acceptance.ts +605 -22
  28. package/src/runs/shared/chain-outputs.ts +23 -8
  29. package/src/runs/shared/completion-guard.ts +3 -26
  30. package/src/runs/shared/dynamic-fanout.ts +1 -1
  31. package/src/runs/shared/model-fallback.ts +38 -0
  32. package/src/runs/shared/parallel-utils.ts +13 -10
  33. package/src/runs/shared/pi-args.ts +3 -2
  34. package/src/runs/shared/subagent-control.ts +8 -11
  35. package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
  36. package/src/runs/shared/workflow-graph.ts +2 -6
  37. package/src/shared/atomic-json.ts +68 -11
  38. package/src/shared/settings.ts +1 -0
  39. package/src/shared/types.ts +20 -49
  40. package/src/shared/utils.ts +2 -8
  41. package/src/slash/slash-bridge.ts +3 -1
  42. package/src/slash/slash-commands.ts +1 -1
  43. package/src/tui/render.ts +14 -29
  44. package/src/runs/shared/acceptance-contract.ts +0 -318
  45. package/src/runs/shared/acceptance-evaluation.ts +0 -221
  46. package/src/runs/shared/acceptance-finalization.ts +0 -173
  47. package/src/runs/shared/acceptance-reports.ts +0 -127
@@ -59,12 +59,13 @@ import {
59
59
  MAX_CONCURRENCY,
60
60
  resolveChildMaxSubagentDepth,
61
61
  } from "../../shared/types.ts";
62
- import { resolveModelCandidate } from "../shared/model-fallback.ts";
62
+ import { resolveSubagentModelOverride } from "../shared/model-fallback.ts";
63
63
  import { validateFileOnlyOutputMode } from "../shared/single-output.ts";
64
64
  import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
65
65
  import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
66
66
  import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
67
67
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
68
+ import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
68
69
  import type { ChainOutputMap } from "../../shared/types.ts";
69
70
 
70
71
  interface ChainExecutionDetailsInput {
@@ -81,7 +82,7 @@ interface ChainExecutionDetailsInput {
81
82
  outputs?: ChainOutputMap;
82
83
  currentFlatIndex?: number;
83
84
  dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
84
- dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached" | "timed-out"; error?: string; acceptance?: SingleResult["acceptance"] }>;
85
+ dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string; acceptance?: SingleResult["acceptance"] }>;
85
86
  }
86
87
 
87
88
  interface ParallelChainRunInput {
@@ -103,8 +104,6 @@ interface ParallelChainRunInput {
103
104
  sessionFileForIndex?: (idx?: number) => string | undefined;
104
105
  shareEnabled: boolean;
105
106
  artifactConfig: ArtifactConfig;
106
- timeoutMs?: number;
107
- timeoutAt?: number;
108
107
  artifactsDir: string;
109
108
  signal?: AbortSignal;
110
109
  onUpdate?: (r: AgentToolResult<Details>) => void;
@@ -233,9 +232,12 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
233
232
  taskStr = prefix + taskStr + suffix;
234
233
 
235
234
  const taskAgentConfig = input.agents.find((agent) => agent.name === task.agent);
236
- const effectiveModel =
237
- (task.model ? resolveModelCandidate(task.model, input.availableModels, input.ctx.model?.provider) : null)
238
- ?? resolveModelCandidate(taskAgentConfig?.model, input.availableModels, input.ctx.model?.provider);
235
+ const effectiveModel = resolveSubagentModelOverride(
236
+ task.model ?? taskAgentConfig?.model,
237
+ input.ctx.model,
238
+ input.availableModels,
239
+ input.ctx.model?.provider,
240
+ );
239
241
  const maxSubagentDepth = resolveChildMaxSubagentDepth(input.maxSubagentDepth, taskAgentConfig?.maxSubagentDepth);
240
242
 
241
243
  const taskCwd = input.worktreeSetup
@@ -267,7 +269,6 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
267
269
  cwd: taskCwd,
268
270
  signal: input.signal,
269
271
  interruptSignal: interruptController.signal,
270
- ...(input.timeoutMs !== undefined && input.timeoutAt !== undefined ? { timeoutMs: input.timeoutMs, timeoutAt: input.timeoutAt } : {}),
271
272
  allowIntercomDetach: taskAgentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
272
273
  intercomEvents: input.intercomEvents,
273
274
  runId: input.runId,
@@ -280,8 +281,6 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
280
281
  outputPath,
281
282
  outputMode: behavior.outputMode,
282
283
  maxSubagentDepth,
283
- maxExecutionTimeMs: taskAgentConfig?.maxExecutionTimeMs,
284
- maxTokens: taskAgentConfig?.maxTokens,
285
284
  controlConfig: input.controlConfig,
286
285
  onControlEvent: input.onControlEvent,
287
286
  intercomSessionName: input.childIntercomTarget?.(task.agent, input.globalTaskIndex + taskIndex),
@@ -396,7 +395,6 @@ interface ChainExecutionParams {
396
395
  nestedRoute?: NestedRouteInfo;
397
396
  worktreeSetupHook?: string;
398
397
  worktreeSetupHookTimeoutMs?: number;
399
- timeoutMs?: number;
400
398
  }
401
399
 
402
400
  interface ChainExecutionResult {
@@ -586,7 +584,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
586
584
  tuiBehaviorOverrides = result.behaviorOverrides;
587
585
  }
588
586
 
589
- const timeoutAt = params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined;
590
587
  let prev = "";
591
588
  let globalTaskIndex = 0;
592
589
  let progressCreated = false;
@@ -655,7 +652,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
655
652
  shareEnabled,
656
653
  artifactConfig,
657
654
  artifactsDir,
658
- ...(params.timeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: params.timeoutMs, timeoutAt } : {}),
659
655
  signal,
660
656
  onUpdate,
661
657
  results,
@@ -682,18 +678,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
682
678
  if (result.progress) allProgress.push(result.progress);
683
679
  if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
684
680
  }
685
- const timedOutIndexInStep = parallelResults.findIndex((result) => result.timedOut);
686
- const timedOut = timedOutIndexInStep >= 0 ? parallelResults[timedOutIndexInStep] : undefined;
687
- if (timedOut) {
688
- return {
689
- content: [{ type: "text", text: `Chain timed out at step ${stepIndex + 1} (${timedOut.agent}): ${timedOut.error ?? "timeout expired"}` }],
690
- isError: true,
691
- details: buildChainExecutionDetails(makeDetailsInput({
692
- currentStepIndex: stepIndex,
693
- currentFlatIndex: globalTaskIndex - step.parallel.length + timedOutIndexInStep,
694
- })),
695
- };
696
- }
697
681
  const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
698
682
  const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
699
683
  if (interrupted) {
@@ -770,11 +754,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
770
754
  if (worktreeSetup) cleanupWorktrees(worktreeSetup);
771
755
  }
772
756
  } else if (isDynamicParallelStep(step)) {
773
- if (Object.hasOwn(step, "acceptance")) {
774
- const message = `Dynamic fanout step ${stepIndex + 1} does not support group-level acceptance; set acceptance on the child template instead.`;
775
- dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
776
- return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
777
- }
778
757
  let materialized: ReturnType<typeof materializeDynamicParallelStep>;
779
758
  try {
780
759
  materialized = materializeDynamicParallelStep(step, outputs, stepIndex, { maxItems: params.dynamicFanoutMaxItems });
@@ -808,6 +787,30 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
808
787
  stepIndex,
809
788
  };
810
789
  dynamicGroupStatuses[stepIndex] = { status: "completed" };
790
+ if (step.acceptance !== undefined) {
791
+ const effectiveGroupAcceptance = resolveEffectiveAcceptance({
792
+ explicit: step.acceptance,
793
+ agentName: step.parallel.agent,
794
+ task: step.parallel.task ?? originalTask,
795
+ mode: "chain",
796
+ dynamicGroup: true,
797
+ });
798
+ const groupAcceptance = await evaluateAcceptance({
799
+ acceptance: effectiveGroupAcceptance,
800
+ output: "",
801
+ report: aggregateAcceptanceReport({
802
+ results: [],
803
+ notes: "Dynamic fanout produced 0 results.",
804
+ }),
805
+ cwd: cwd ?? ctx.cwd,
806
+ });
807
+ dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
808
+ const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
809
+ if (groupAcceptanceFailure) {
810
+ dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
811
+ return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
812
+ }
813
+ }
811
814
  prev = "Dynamic fanout produced 0 results.";
812
815
  continue;
813
816
  }
@@ -855,7 +858,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
855
858
  shareEnabled,
856
859
  artifactConfig,
857
860
  artifactsDir,
858
- ...(params.timeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: params.timeoutMs, timeoutAt } : {}),
859
861
  signal,
860
862
  onUpdate,
861
863
  results,
@@ -882,19 +884,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
882
884
  if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
883
885
  }
884
886
  const collected = collectDynamicResults(step, materialized.items, parallelResults);
885
- const timedOutIndexInStep = parallelResults.findIndex((result) => result.timedOut);
886
- const timedOut = timedOutIndexInStep >= 0 ? parallelResults[timedOutIndexInStep] : undefined;
887
- if (timedOut) {
888
- dynamicGroupStatuses[stepIndex] = { status: "timed-out", error: timedOut.error };
889
- return {
890
- content: [{ type: "text", text: `Chain timed out at step ${stepIndex + 1} (${timedOut.agent}): ${timedOut.error ?? "timeout expired"}` }],
891
- isError: true,
892
- details: buildChainExecutionDetails(makeDetailsInput({
893
- currentStepIndex: stepIndex,
894
- currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + timedOutIndexInStep,
895
- })),
896
- };
897
- }
898
887
  const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
899
888
  const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
900
889
  if (interrupted) {
@@ -953,6 +942,28 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
953
942
  stepIndex,
954
943
  };
955
944
  dynamicGroupStatuses[stepIndex] = { status: "completed" };
945
+ const effectiveGroupAcceptance = resolveEffectiveAcceptance({
946
+ explicit: step.acceptance,
947
+ agentName: step.parallel.agent,
948
+ task: step.parallel.task ?? originalTask,
949
+ mode: "chain",
950
+ dynamicGroup: true,
951
+ });
952
+ const groupAcceptance = await evaluateAcceptance({
953
+ acceptance: effectiveGroupAcceptance,
954
+ output: "",
955
+ report: aggregateAcceptanceReport({
956
+ results: parallelResults,
957
+ notes: `Dynamic fanout collected ${collected.length} result(s) into ${step.collect.as}.`,
958
+ }),
959
+ cwd: cwd ?? ctx.cwd,
960
+ });
961
+ dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
962
+ const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
963
+ if (groupAcceptanceFailure) {
964
+ dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
965
+ return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }));
966
+ }
956
967
  const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => ({
957
968
  agent: result.agent,
958
969
  taskIndex: i,
@@ -1008,10 +1019,13 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1008
1019
  const cleanTask = stepTask;
1009
1020
  stepTask = prefix + stepTask + suffix;
1010
1021
 
1011
- const effectiveModel =
1012
- tuiOverride?.model
1013
- ?? (seqStep.model ? resolveModelCandidate(seqStep.model, availableModels, ctx.model?.provider) : null)
1014
- ?? resolveModelCandidate(agentConfig.model, availableModels, ctx.model?.provider);
1022
+ const effectiveModel = tuiOverride?.model
1023
+ ?? resolveSubagentModelOverride(
1024
+ seqStep.model ?? agentConfig.model,
1025
+ ctx.model,
1026
+ availableModels,
1027
+ ctx.model?.provider,
1028
+ );
1015
1029
 
1016
1030
  const outputPath = typeof behavior.output === "string"
1017
1031
  ? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
@@ -1043,7 +1057,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1043
1057
  cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
1044
1058
  signal,
1045
1059
  interruptSignal: interruptController.signal,
1046
- ...(params.timeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: params.timeoutMs, timeoutAt } : {}),
1047
1060
  allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
1048
1061
  intercomEvents,
1049
1062
  runId,
@@ -1056,8 +1069,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1056
1069
  outputPath,
1057
1070
  outputMode: behavior.outputMode,
1058
1071
  maxSubagentDepth,
1059
- maxExecutionTimeMs: agentConfig.maxExecutionTimeMs,
1060
- maxTokens: agentConfig.maxTokens,
1061
1072
  controlConfig,
1062
1073
  onControlEvent,
1063
1074
  intercomSessionName: childIntercomTarget?.(seqStep.agent, globalTaskIndex),
@@ -1125,13 +1136,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1125
1136
  if (r.progress) allProgress.push(r.progress);
1126
1137
  if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
1127
1138
 
1128
- if (r.timedOut) {
1129
- return {
1130
- content: [{ type: "text", text: `Chain timed out at step ${stepIndex + 1} (${r.agent}): ${r.error ?? "timeout expired"}` }],
1131
- isError: true,
1132
- details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - 1 })),
1133
- };
1134
- }
1135
1139
  if (r.interrupted) {
1136
1140
  return {
1137
1141
  content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${r.agent}). Waiting for explicit next action.` }],