pi-subagents 0.35.0 → 0.36.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 (76) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +132 -24
  3. package/agents/advisor.md +73 -0
  4. package/package.json +8 -12
  5. package/skills/pi-subagents/SKILL.md +22 -9
  6. package/src/agents/agents.ts +22 -5
  7. package/src/api/delegation.ts +125 -0
  8. package/src/extension/config.ts +7 -1
  9. package/src/extension/index.ts +50 -38
  10. package/src/extension/rpc.ts +27 -2
  11. package/src/extension/schemas.ts +22 -2
  12. package/src/extension/tool-description.ts +2 -2
  13. package/src/intercom/intercom-bridge.ts +1 -1
  14. package/src/intercom/native-supervisor-channel.ts +45 -6
  15. package/src/intercom/result-intercom.ts +7 -0
  16. package/src/runs/background/async-execution.ts +34 -4
  17. package/src/runs/background/async-job-tracker.ts +4 -0
  18. package/src/runs/background/async-resume.ts +27 -5
  19. package/src/runs/background/async-status.ts +76 -3
  20. package/src/runs/background/chain-append.ts +2 -0
  21. package/src/runs/background/completion-batcher.ts +6 -4
  22. package/src/runs/background/completion-dedupe.ts +2 -11
  23. package/src/runs/background/fleet-view.ts +9 -4
  24. package/src/runs/background/notify.ts +132 -120
  25. package/src/runs/background/result-watcher.ts +138 -78
  26. package/src/runs/background/run-status.ts +3 -1
  27. package/src/runs/background/subagent-runner.ts +225 -43
  28. package/src/runs/background/subagent-wait.ts +130 -4
  29. package/src/runs/background/wait-tool.ts +2 -2
  30. package/src/runs/foreground/chain-execution.ts +176 -111
  31. package/src/runs/foreground/execution.ts +90 -36
  32. package/src/runs/foreground/foreground-control.ts +90 -0
  33. package/src/runs/foreground/subagent-executor.ts +394 -163
  34. package/src/runs/shared/acceptance.ts +55 -13
  35. package/src/runs/shared/agent-contract.ts +38 -0
  36. package/src/runs/shared/child-protocol.ts +1 -1
  37. package/src/runs/shared/completion-guard.ts +36 -5
  38. package/src/runs/shared/context-mode.ts +44 -0
  39. package/src/runs/shared/dynamic-fanout.ts +4 -4
  40. package/src/runs/shared/long-running-guard.ts +4 -0
  41. package/src/runs/shared/nested-events.ts +27 -2
  42. package/src/runs/shared/parallel-handoff.ts +154 -0
  43. package/src/runs/shared/parallel-utils.ts +6 -0
  44. package/src/runs/shared/pi-args.ts +23 -14
  45. package/src/runs/shared/run-history.ts +90 -5
  46. package/src/runs/shared/structured-output.ts +112 -7
  47. package/src/runs/shared/subagent-control.ts +4 -0
  48. package/src/runs/shared/subagent-prompt-runtime.ts +17 -18
  49. package/src/runs/shared/task-intent.ts +10 -5
  50. package/src/runs/shared/tool-availability.ts +3 -1
  51. package/src/runs/shared/tool-budget.ts +11 -5
  52. package/src/runs/shared/turn-budget.ts +2 -1
  53. package/src/runs/shared/worktree.ts +63 -14
  54. package/src/shared/accessible-dir.ts +25 -0
  55. package/src/shared/artifacts.ts +37 -7
  56. package/src/shared/atomic-json.ts +14 -42
  57. package/src/shared/child-transcript.ts +52 -0
  58. package/src/shared/file-system-retry.ts +47 -0
  59. package/src/shared/settings.ts +9 -1
  60. package/src/shared/types.ts +211 -22
  61. package/src/slash/delegation-adapters.ts +152 -5
  62. package/src/slash/delegation-json.ts +108 -0
  63. package/src/slash/delegation-request.ts +182 -36
  64. package/src/slash/prompt-template-bridge.ts +222 -37
  65. package/src/slash/selector.ts +147 -0
  66. package/src/slash/slash-commands.ts +14 -5
  67. package/src/slash/slash-live-state.ts +2 -2
  68. package/src/slash/subagents-admin.ts +42 -42
  69. package/src/tui/fleet-status.ts +362 -0
  70. package/src/tui/fleet-transcript.ts +472 -0
  71. package/src/tui/fleet.ts +318 -59
  72. package/src/tui/render.ts +25 -15
  73. package/src/watchdog/change-signature.ts +105 -12
  74. package/src/watchdog/review.ts +7 -2
  75. package/src/watchdog/runtime.ts +5 -3
  76. package/src/slash/subagents-editor.ts +0 -86
@@ -5,6 +5,7 @@
5
5
  import * as os from "node:os";
6
6
  import * as path from "node:path";
7
7
  import type { Message } from "@earendil-works/pi-ai";
8
+ import type { AgentConfig } from "../agents/agents.ts";
8
9
  import type { FSWatcher } from "node:fs";
9
10
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
10
11
  import type { ModelScopeConfig } from "../runs/shared/model-scope.ts";
@@ -201,7 +202,7 @@ export interface ControlEvent {
201
202
  nestedRunId?: string;
202
203
  nestingPath?: NestedRunAddress["path"];
203
204
  message: string;
204
- reason?: "idle" | "completion_guard" | "active_long_running" | "tool_failures" | "time_threshold" | "turn_threshold" | "token_threshold";
205
+ reason?: "idle" | "completion_guard" | "active_long_running" | "tool_failures" | "supervisor_request" | "time_threshold" | "turn_threshold" | "token_threshold";
205
206
  turns?: number;
206
207
  tokens?: number;
207
208
  toolCount?: number;
@@ -214,6 +215,108 @@ export interface ControlEvent {
214
215
 
215
216
  export type SubagentResultStatus = "completed" | "failed" | "paused" | "stopped" | "detached";
216
217
  export type SubagentRunMode = "single" | "parallel" | "chain";
218
+
219
+ export interface ParallelHandoffPatch {
220
+ path: string;
221
+ branch: string;
222
+ changed: boolean;
223
+ diffStat: string;
224
+ filesChanged: number;
225
+ insertions: number;
226
+ deletions: number;
227
+ error?: string;
228
+ }
229
+
230
+ export interface ParallelHandoffChild {
231
+ index: number;
232
+ taskIndex: number;
233
+ agent: string;
234
+ status: SubagentResultStatus;
235
+ summary: string;
236
+ outputPath?: string;
237
+ structuredOutput?: unknown;
238
+ structuredOutputPath?: string;
239
+ sessionPath?: string;
240
+ patch: ParallelHandoffPatch;
241
+ }
242
+
243
+ export interface ParallelHandoffCleanupTask {
244
+ index: number;
245
+ path: string;
246
+ branch: string;
247
+ worktreeRemoved: boolean;
248
+ branchRemoved: boolean;
249
+ errors?: string[];
250
+ }
251
+
252
+ export interface ParallelHandoffGroup {
253
+ stepIndex: number;
254
+ baseCommit: string;
255
+ repoRoot: string;
256
+ children: ParallelHandoffChild[];
257
+ cleanup: {
258
+ state: "complete" | "partial";
259
+ tasks: ParallelHandoffCleanupTask[];
260
+ pruned: boolean;
261
+ errors?: string[];
262
+ };
263
+ }
264
+
265
+ export interface ParallelHandoffManifest {
266
+ version: 1;
267
+ runId: string;
268
+ mode: "parallel" | "chain";
269
+ source: "foreground" | "async";
270
+ cwd: string;
271
+ createdAt: number;
272
+ updatedAt: number;
273
+ groups: ParallelHandoffGroup[];
274
+ }
275
+
276
+ export interface ParallelHandoffReference {
277
+ version: 1;
278
+ path: string;
279
+ groupCount: number;
280
+ childCount: number;
281
+ changedPatches: number;
282
+ cleanupState: "complete" | "partial";
283
+ }
284
+
285
+ export interface AgentContract {
286
+ version: 1;
287
+ }
288
+
289
+ export type ChainGateLayer = "execution" | "acceptance";
290
+
291
+ export type ExecutionProjectionStatus = "completed" | "failed" | "paused" | "stopped" | "detached";
292
+
293
+ export interface ExecutionProjection {
294
+ status: ExecutionProjectionStatus;
295
+ success: boolean;
296
+ exitCode: number;
297
+ error?: string;
298
+ interrupted?: boolean;
299
+ timedOut?: boolean;
300
+ stopped?: boolean;
301
+ detached?: boolean;
302
+ }
303
+
304
+ export interface ReviewProjection {
305
+ status: "not-requested" | "no-blockers" | "blockers" | "needs-parent-decision";
306
+ findings?: AcceptanceReviewResult["findings"];
307
+ }
308
+
309
+ export interface FileMutationEffect {
310
+ status: "not-requested" | "not-applicable" | "observed" | "missing";
311
+ expected: boolean;
312
+ attempted: boolean;
313
+ message?: string;
314
+ }
315
+
316
+ export interface EffectsProjection {
317
+ fileMutation?: FileMutationEffect;
318
+ }
319
+
217
320
  export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 2;
218
321
  export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
219
322
 
@@ -282,6 +385,7 @@ export interface SteeringNotice {
282
385
  export interface SteeringRecoveryDescriptor {
283
386
  version: 1;
284
387
  sourceRunId: string;
388
+ agentContract?: AgentContract;
285
389
  agent: string;
286
390
  sessionFile?: string;
287
391
  cwd: string;
@@ -303,6 +407,7 @@ export interface SteeringRecoveryDescriptor {
303
407
  memory?: { scope: "project" | "user"; path: string };
304
408
  outputPath?: string;
305
409
  outputMode: "inline" | "file-only";
410
+ structuredOutputSchema?: JsonSchemaObject;
306
411
  acceptance?: AcceptanceInput;
307
412
  controlConfig?: ResolvedControlConfig;
308
413
  absoluteDeadlineAt?: number;
@@ -365,6 +470,7 @@ export interface SubagentResultIntercomPayload {
365
470
  index?: number;
366
471
  artifactPath?: string;
367
472
  sessionPath?: string;
473
+ parallelHandoff?: ParallelHandoffReference;
368
474
  }
369
475
 
370
476
  // ============================================================================
@@ -585,6 +691,8 @@ export interface ProtocolOutputLimit {
585
691
  export interface SingleResult {
586
692
  agent: string;
587
693
  task: string;
694
+ /** Resolved launch context for this child. */
695
+ context?: "fresh" | "fork";
588
696
  exitCode: number;
589
697
  detached?: boolean;
590
698
  detachedReason?: string;
@@ -599,6 +707,8 @@ export interface SingleResult {
599
707
  messages?: Message[];
600
708
  usage: Usage;
601
709
  model?: string;
710
+ /** Effective thinking level used by this foreground child, when known. */
711
+ thinking?: string;
602
712
  attemptedModels?: string[];
603
713
  modelAttempts?: ModelAttempt[];
604
714
  controlEvents?: ControlEvent[];
@@ -618,9 +728,14 @@ export interface SingleResult {
618
728
  outputReference?: SavedOutputReference;
619
729
  outputSaveError?: string;
620
730
  structuredOutput?: unknown;
731
+ structuredOutputFailed?: boolean;
621
732
  structuredOutputPath?: string;
622
733
  structuredOutputSchemaPath?: string;
623
734
  acceptance?: AcceptanceLedger;
735
+ agentContract?: AgentContract;
736
+ execution?: ExecutionProjection;
737
+ review?: ReviewProjection;
738
+ effects?: EffectsProjection;
624
739
  transcriptPath?: string;
625
740
  transcriptError?: string;
626
741
  children?: NestedRunSummary[];
@@ -648,7 +763,8 @@ export interface SpawnBudgetSnapshot {
648
763
  export interface Details {
649
764
  mode: SubagentRunMode | "management";
650
765
  runId?: string;
651
- context?: "fresh" | "fork";
766
+ /** Run-level context summary. "mixed" when children resolved to different modes. */
767
+ context?: "fresh" | "fork" | "mixed";
652
768
  results: SingleResult[];
653
769
  controlEvents?: ControlEvent[];
654
770
  steering?: SteerActionResult;
@@ -683,6 +799,7 @@ export interface Details {
683
799
  // Aggregated cost across all agents in the run
684
800
  totalCost?: CostSummary;
685
801
  spawnBudget?: SpawnBudgetSnapshot;
802
+ parallelHandoff?: ParallelHandoffReference;
686
803
  }
687
804
 
688
805
  // ============================================================================
@@ -697,8 +814,11 @@ export interface ArtifactPaths {
697
814
  metadataPath: string;
698
815
  }
699
816
 
817
+ export type ArtifactDirPreference = "project" | "session" | "temp";
818
+
700
819
  export interface ArtifactConfig {
701
820
  enabled: boolean;
821
+ dir?: ArtifactDirPreference;
702
822
  includeInput: boolean;
703
823
  includeOutput: boolean;
704
824
  includeJsonl: boolean;
@@ -811,6 +931,8 @@ export interface AsyncStartedEvent {
811
931
  lifecycleArtifactVersion?: SubagentLifecycleArtifactVersion;
812
932
  id?: string;
813
933
  asyncDir?: string;
934
+ /** Parent-resolved launch directory, used as a trusted artifact root while this session is live. */
935
+ cwd?: string;
814
936
  pid?: number;
815
937
  sessionId?: string;
816
938
  mode?: SubagentRunMode;
@@ -867,6 +989,8 @@ export interface AsyncStatus {
867
989
  workflowGraph?: WorkflowGraphSnapshot;
868
990
  steps?: Array<{
869
991
  agent: string;
992
+ /** Resolved launch context for this child step. */
993
+ context?: "fresh" | "fork";
870
994
  phase?: string;
871
995
  label?: string;
872
996
  outputName?: string;
@@ -910,6 +1034,10 @@ export interface AsyncStatus {
910
1034
  structuredOutputPath?: string;
911
1035
  structuredOutputSchemaPath?: string;
912
1036
  acceptance?: AcceptanceLedger;
1037
+ agentContract?: AgentContract;
1038
+ execution?: ExecutionProjection;
1039
+ review?: ReviewProjection;
1040
+ effects?: EffectsProjection;
913
1041
  watchdog?: ChildWatchdogProgress;
914
1042
  }>;
915
1043
  sessionDir?: string;
@@ -918,6 +1046,7 @@ export interface AsyncStatus {
918
1046
  totalCost?: CostSummary;
919
1047
  sessionFile?: string;
920
1048
  outputs?: ChainOutputMap;
1049
+ parallelHandoff?: ParallelHandoffReference;
921
1050
  }
922
1051
 
923
1052
  export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
@@ -927,7 +1056,11 @@ export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
927
1056
  export interface AsyncJobState {
928
1057
  asyncId: string;
929
1058
  asyncDir: string;
1059
+ /** Parent-resolved launch directory retained for trusted live artifact lookup. */
1060
+ cwd?: string;
930
1061
  status: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
1062
+ /** Short caller-facing task/goal shown in fleet surfaces when available. */
1063
+ description?: string;
931
1064
  pid?: number;
932
1065
  sessionId?: string;
933
1066
  activityState?: ActivityState;
@@ -939,6 +1072,8 @@ export interface AsyncJobState {
939
1072
  toolCount?: number;
940
1073
  steering?: SteeringStatus;
941
1074
  mode?: SubagentRunMode;
1075
+ /** Run-level context summary derived from step contexts. */
1076
+ context?: "fresh" | "fork" | "mixed";
942
1077
  agents?: string[];
943
1078
  currentStep?: number;
944
1079
  chainStepCount?: number;
@@ -972,8 +1107,17 @@ export interface AsyncJobState {
972
1107
  export interface ForegroundResumeChild {
973
1108
  agent: string;
974
1109
  index: number;
1110
+ context?: "fresh" | "fork";
975
1111
  sessionFile?: string;
976
1112
  status: SubagentResultStatus;
1113
+ activityState?: ActivityState;
1114
+ lastActivityAt?: number;
1115
+ currentTool?: string;
1116
+ currentToolStartedAt?: number;
1117
+ currentPath?: string;
1118
+ turnCount?: number;
1119
+ tokens?: number;
1120
+ toolCount?: number;
977
1121
  exitCode?: number;
978
1122
  error?: string;
979
1123
  finalOutput?: string;
@@ -985,6 +1129,10 @@ export interface ForegroundResumeChild {
985
1129
  transcriptError?: string;
986
1130
  detachedReason?: string;
987
1131
  acceptance?: AcceptanceLedger;
1132
+ agentContract?: AgentContract;
1133
+ execution?: ExecutionProjection;
1134
+ review?: ReviewProjection;
1135
+ effects?: EffectsProjection;
988
1136
  updatedAt?: number;
989
1137
  }
990
1138
 
@@ -998,9 +1146,55 @@ export interface ForegroundResumeRun {
998
1146
  children: ForegroundResumeChild[];
999
1147
  }
1000
1148
 
1149
+ export interface ForegroundChildControl {
1150
+ index: number;
1151
+ agent: string;
1152
+ description?: string;
1153
+ startedAt: number;
1154
+ updatedAt: number;
1155
+ currentActivityState?: ActivityState;
1156
+ lastActivityAt?: number;
1157
+ currentTool?: string;
1158
+ currentToolStartedAt?: number;
1159
+ currentPath?: string;
1160
+ turnCount?: number;
1161
+ tokens?: number;
1162
+ toolCount?: number;
1163
+ interrupt?: () => boolean;
1164
+ }
1165
+
1166
+ export interface ForegroundRunControl {
1167
+ runId: string;
1168
+ mode: SubagentRunMode;
1169
+ startedAt: number;
1170
+ updatedAt: number;
1171
+ /** Effective working directory used to resolve live transcript artifacts. */
1172
+ cwd?: string;
1173
+ currentAgent?: string;
1174
+ currentIndex?: number;
1175
+ /** Short caller-facing task/goal shown in fleet surfaces when available. */
1176
+ description?: string;
1177
+ currentActivityState?: ActivityState;
1178
+ lastActivityAt?: number;
1179
+ currentTool?: string;
1180
+ currentToolStartedAt?: number;
1181
+ currentPath?: string;
1182
+ turnCount?: number;
1183
+ tokens?: number;
1184
+ toolCount?: number;
1185
+ /** Independently tracked children for foreground parallel work and fleet inspection. */
1186
+ activeChildren?: Map<number, ForegroundChildControl>;
1187
+ nestedRoute?: NestedRouteInfo;
1188
+ nestedChildren?: NestedRunSummary[];
1189
+ interrupt?: () => boolean;
1190
+ }
1191
+
1001
1192
  export interface SubagentState {
1002
1193
  baseCwd: string;
1003
1194
  currentSessionId: string | null;
1195
+ /** Runtime-owned artifact resolution inputs used by Fleet transcript targeting. */
1196
+ artifactDirPreference?: ArtifactDirPreference;
1197
+ parentSessionFile?: string | null;
1004
1198
  subagentInProgress?: boolean;
1005
1199
  subagentSpawns?: {
1006
1200
  sessionId: string | null;
@@ -1012,26 +1206,10 @@ export interface SubagentState {
1012
1206
  asyncJobs: Map<string, AsyncJobState>;
1013
1207
  /** Current-session active and recent async runs for the native fleet inspector. */
1014
1208
  fleetJobs?: Map<string, AsyncJobState>;
1209
+ /** Suppress dynamic status widgets while the fleet overlay owns the viewport. */
1210
+ fleetInspectorOpen?: boolean;
1015
1211
  foregroundRuns?: Map<string, ForegroundResumeRun>;
1016
- foregroundControls: Map<string, {
1017
- runId: string;
1018
- mode: SubagentRunMode;
1019
- startedAt: number;
1020
- updatedAt: number;
1021
- currentAgent?: string;
1022
- currentIndex?: number;
1023
- currentActivityState?: ActivityState;
1024
- lastActivityAt?: number;
1025
- currentTool?: string;
1026
- currentToolStartedAt?: number;
1027
- currentPath?: string;
1028
- turnCount?: number;
1029
- tokens?: number;
1030
- toolCount?: number;
1031
- nestedRoute?: NestedRouteInfo;
1032
- nestedChildren?: NestedRunSummary[];
1033
- interrupt?: () => boolean;
1034
- }>;
1212
+ foregroundControls: Map<string, ForegroundRunControl>;
1035
1213
  lastForegroundControlId: string | null;
1036
1214
  pendingForegroundControlNotices?: Map<string, ReturnType<typeof setTimeout>>;
1037
1215
  cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;
@@ -1088,13 +1266,18 @@ export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom
1088
1266
  export interface RunSyncOptions {
1089
1267
  /** Session id of the direct parent session for permission-system ask forwarding. */
1090
1268
  parentSessionId?: string;
1269
+ /** Resolved launch context for this child. */
1270
+ context?: "fresh" | "fork";
1091
1271
  cwd?: string;
1092
1272
  signal?: AbortSignal;
1093
1273
  interruptSignal?: AbortSignal;
1094
1274
  timeoutMs?: number;
1095
1275
  deadlineAt?: number;
1096
1276
  turnBudget?: ResolvedTurnBudget;
1277
+ /** Enforce maxTurns + graceTurns as a hard model-turn boundary. */
1278
+ enforceHardTurnLimit?: boolean;
1097
1279
  toolBudget?: ResolvedToolBudget;
1280
+ allowZeroToolBudget?: boolean;
1098
1281
  allowIntercomDetach?: boolean;
1099
1282
  intercomEvents?: IntercomEventBus;
1100
1283
  onUpdate?: (r: import("@earendil-works/pi-agent-core").AgentToolResult<Details>) => void;
@@ -1134,6 +1317,7 @@ export interface RunSyncOptions {
1134
1317
  schemaPath: string;
1135
1318
  outputPath: string;
1136
1319
  };
1320
+ agentContract?: AgentContract;
1137
1321
  acceptance?: AcceptanceInput;
1138
1322
  acceptanceContext?: {
1139
1323
  mode?: SubagentRunMode;
@@ -1178,7 +1362,9 @@ export interface ScheduledRunsConfig {
1178
1362
 
1179
1363
  export interface ExtensionConfig {
1180
1364
  asyncByDefault?: boolean;
1181
- /** Show the above-editor async runs widget. Defaults to true. */
1365
+ /** Show the Claude Code-style navigable fleet below the editor. Defaults to true. */
1366
+ fleetView?: boolean;
1367
+ /** Show the legacy above-editor async runs widget. Defaults to true only when fleetView is disabled. */
1182
1368
  asyncWidget?: boolean;
1183
1369
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
1184
1370
  toolDescriptionMode?: ToolDescriptionMode;
@@ -1200,6 +1386,8 @@ export interface ExtensionConfig {
1200
1386
  worktreeSetupHook?: string;
1201
1387
  worktreeSetupHookTimeoutMs?: number;
1202
1388
  worktreeBaseDir?: string;
1389
+ /** Where to store subagent artifact files. Defaults to "project" (cwd/.pi-subagents). Set to "session" for pi session dir, or "temp" for OS temp. */
1390
+ artifactDir?: ArtifactDirPreference;
1203
1391
  intercomBridge?: IntercomBridgeConfig;
1204
1392
  proactiveSkillSubagents?: ProactiveSkillSubagentsConfig | false;
1205
1393
  scheduledRuns?: ScheduledRunsConfig;
@@ -1216,6 +1404,7 @@ export const DEFAULT_MAX_OUTPUT: Required<MaxOutputConfig> = {
1216
1404
 
1217
1405
  export const DEFAULT_ARTIFACT_CONFIG: ArtifactConfig = {
1218
1406
  enabled: true,
1407
+ dir: "project",
1219
1408
  includeInput: true,
1220
1409
  includeOutput: true,
1221
1410
  includeJsonl: false,
@@ -1,12 +1,23 @@
1
1
  import {
2
2
  SUBAGENT_DELEGATION_PROTOCOL_VERSION,
3
+ SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION,
3
4
  type SubagentDelegationAcceptanceResult,
5
+ type SubagentDelegationEffectsResult,
6
+ type SubagentDelegationExecutionResult,
4
7
  type SubagentDelegationRequest,
5
8
  type SubagentDelegationResponse,
9
+ type SubagentDelegationReviewResult,
6
10
  type SubagentDelegationStatus,
7
11
  type SubagentDelegationUpdate,
12
+ type SubagentDelegationV2Request,
13
+ type SubagentDelegationV2Response,
14
+ type SubagentDelegationV2Thinking,
15
+ type SubagentDelegationV2Value,
16
+ type SubagentDelegationV2Update,
8
17
  } from "../api/delegation.ts";
9
- import type { AcceptanceInput, ToolBudgetConfig, TurnBudgetConfig } from "../shared/types.ts";
18
+ import type { AcceptanceInput, AgentContract, EffectsProjection, ExecutionProjection, JsonSchemaObject, ReviewProjection, ToolBudgetConfig, TurnBudgetConfig, Usage } from "../shared/types.ts";
19
+ import { isAgentContractV1 } from "../runs/shared/agent-contract.ts";
20
+ import { cloneJsonWithinByteLimit } from "./delegation-json.ts";
10
21
 
11
22
  export interface PromptTemplateDelegationTask {
12
23
  agent: string;
@@ -86,15 +97,22 @@ export interface PromptTemplateBridgeResult {
86
97
  exitCode?: number;
87
98
  error?: string;
88
99
  model?: string;
100
+ thinking?: string;
101
+ structuredOutput?: unknown;
89
102
  interrupted?: boolean;
90
103
  timedOut?: boolean;
91
104
  stopped?: boolean;
92
105
  turnBudgetExceeded?: boolean;
93
106
  toolBudgetBlocked?: boolean;
107
+ structuredOutputFailed?: boolean;
94
108
  savedOutputPath?: string;
95
109
  sessionFile?: string;
110
+ agentContract?: AgentContract;
111
+ execution?: ExecutionProjection;
96
112
  acceptance?: SubagentDelegationAcceptanceResult;
97
- usage?: { turns?: number };
113
+ review?: ReviewProjection;
114
+ effects?: EffectsProjection;
115
+ usage?: Usage;
98
116
  progressSummary?: { toolCount?: number; durationMs?: number; tokens?: number };
99
117
  skillsWarning?: string;
100
118
  outputSaveError?: string;
@@ -125,12 +143,20 @@ export interface DelegatedSubagentExecutionParams {
125
143
  worktree?: boolean;
126
144
  timeoutMs?: number;
127
145
  turnBudget?: TurnBudgetConfig;
146
+ /** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
147
+ enforceHardTurnLimit?: boolean;
128
148
  toolBudget?: ToolBudgetConfig;
129
149
  skill?: string | string[] | boolean;
130
150
  output?: string | boolean;
131
151
  outputMode?: "inline" | "file-only";
152
+ outputSchema?: JsonSchemaObject;
153
+ agentContract?: AgentContract;
132
154
  acceptance?: AcceptanceInput;
133
155
  artifacts?: boolean;
156
+ /** Internal-only thinking override accepted by executeDelegated. */
157
+ delegatedThinkingOverride?: SubagentDelegationV2Thinking;
158
+ /** Internal-only V2 capability accepted and stripped by executeDelegated. */
159
+ delegatedAllowZeroToolBudget?: true;
134
160
  async: false;
135
161
  foregroundOnly: true;
136
162
  clarify: false;
@@ -175,8 +201,8 @@ export function parsePromptTemplateRequest(data: unknown): PromptTemplateDelegat
175
201
  const fallbackTask = tasks[0];
176
202
  return {
177
203
  requestId: value.requestId,
178
- agent: hasSingle ? value.agent : fallbackTask!.agent,
179
- task: hasSingle ? value.task : fallbackTask!.task,
204
+ agent: hasSingle ? value.agent! : fallbackTask!.agent,
205
+ task: hasSingle ? value.task! : fallbackTask!.task,
180
206
  ...(tasks.length > 0 ? { tasks } : {}),
181
207
  context: value.context,
182
208
  model: value.model,
@@ -343,10 +369,13 @@ export function toSubagentDelegationExecutionParams(request: SubagentDelegationR
343
369
  model: request.model,
344
370
  timeoutMs: request.timeoutMs,
345
371
  turnBudget: request.turnBudget,
372
+ enforceHardTurnLimit: true,
346
373
  toolBudget: request.toolBudget,
347
374
  skill: request.skill,
348
375
  output: request.output,
349
376
  outputMode: request.outputMode,
377
+ outputSchema: request.outputSchema,
378
+ agentContract: request.agentContract,
350
379
  acceptance: request.acceptance,
351
380
  artifacts: request.artifacts,
352
381
  async: false,
@@ -355,6 +384,30 @@ export function toSubagentDelegationExecutionParams(request: SubagentDelegationR
355
384
  };
356
385
  }
357
386
 
387
+ export function toSubagentDelegationV2ExecutionParams(request: SubagentDelegationV2Request): DelegatedSubagentExecutionParams {
388
+ return {
389
+ agent: request.agent,
390
+ task: request.task,
391
+ context: request.context,
392
+ cwd: request.cwd,
393
+ model: request.model,
394
+ timeoutMs: request.timeoutMs,
395
+ turnBudget: request.turnBudget,
396
+ enforceHardTurnLimit: true,
397
+ toolBudget: request.toolBudget,
398
+ skill: request.skill,
399
+ output: false,
400
+ ...(request.result.kind === "structured" ? { outputSchema: request.result.schema } : {}),
401
+ acceptance: false,
402
+ artifacts: request.artifacts,
403
+ delegatedThinkingOverride: request.thinking,
404
+ delegatedAllowZeroToolBudget: true,
405
+ async: false,
406
+ foregroundOnly: true,
407
+ clarify: false,
408
+ };
409
+ }
410
+
358
411
  export function toSubagentDelegationUpdate(requestId: string, result: PromptTemplateBridgeResult): SubagentDelegationUpdate | undefined {
359
412
  const legacy = toDelegationUpdate(requestId, result);
360
413
  if (!legacy) return undefined;
@@ -373,6 +426,29 @@ export function toSubagentDelegationUpdate(requestId: string, result: PromptTemp
373
426
  };
374
427
  }
375
428
 
429
+ export function toSubagentDelegationV2Update(
430
+ request: SubagentDelegationV2Request,
431
+ result: PromptTemplateBridgeResult,
432
+ ): SubagentDelegationV2Update | undefined {
433
+ const legacy = toDelegationUpdate(request.requestId, result);
434
+ if (!legacy) return undefined;
435
+ return {
436
+ version: SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION,
437
+ requestId: request.requestId,
438
+ ownerRunId: request.ownerRunId,
439
+ nodeId: request.nodeId,
440
+ ...(legacy.currentTool ? { currentTool: legacy.currentTool } : {}),
441
+ ...(legacy.currentToolArgs ? { currentToolArgs: legacy.currentToolArgs } : {}),
442
+ ...(legacy.recentOutput ? { recentOutput: legacy.recentOutput } : {}),
443
+ ...(legacy.recentOutputLines ? { recentOutputLines: legacy.recentOutputLines } : {}),
444
+ ...(legacy.recentTools ? { recentTools: legacy.recentTools } : {}),
445
+ ...(legacy.model ? { model: legacy.model } : {}),
446
+ ...(typeof legacy.toolCount === "number" ? { toolCount: legacy.toolCount } : {}),
447
+ ...(typeof legacy.durationMs === "number" ? { durationMs: legacy.durationMs } : {}),
448
+ ...(typeof legacy.tokens === "number" ? { tokens: legacy.tokens } : {}),
449
+ };
450
+ }
451
+
376
452
  function resolveSubagentDelegationStatus(
377
453
  result: PromptTemplateBridgeResult,
378
454
  aborted: boolean,
@@ -381,9 +457,10 @@ function resolveSubagentDelegationStatus(
381
457
  const child = result.details?.results?.[0];
382
458
  if (!child) return "failed";
383
459
  if (result.details?.timedOut || child.timedOut) return "timed_out";
460
+ if (child?.structuredOutputFailed) return "structured_output_failed";
384
461
  if (child?.turnBudgetExceeded) return "turn_budget_exhausted";
385
462
  if (child?.toolBudgetBlocked) return "tool_budget_exhausted";
386
- if (child?.acceptance?.status === "rejected" && child.acceptance.explicit) return "acceptance_failed";
463
+ if (!isAgentContractV1(child.agentContract) && child?.acceptance?.status === "rejected" && child.acceptance.explicit) return "acceptance_failed";
387
464
  if (result.details?.stopped || child?.stopped || child?.interrupted) return "interrupted";
388
465
  if (result.isError || child?.error || (typeof child?.exitCode === "number" && child.exitCode !== 0)) return "failed";
389
466
  return "completed";
@@ -410,10 +487,13 @@ export function toSubagentDelegationResponse(
410
487
  ...(child?.agent ? { agent: child.agent } : {}),
411
488
  ...(child?.model ? { model: child.model } : {}),
412
489
  ...(typeof child?.exitCode === "number" ? { exitCode: child.exitCode } : {}),
490
+ ...(child?.execution ? { execution: child.execution as SubagentDelegationExecutionResult } : {}),
413
491
  ...(child?.finalOutput ? { output: child.finalOutput } : {}),
414
492
  ...(child?.savedOutputPath ? { outputPath: child.savedOutputPath } : {}),
415
493
  ...(child?.sessionFile ? { sessionFile: child.sessionFile } : {}),
416
494
  ...(child?.acceptance ? { acceptance: { status: child.acceptance.status, explicit: child.acceptance.explicit } } : {}),
495
+ ...(child?.review ? { review: child.review as SubagentDelegationReviewResult } : {}),
496
+ ...(child?.effects ? { effects: child.effects as SubagentDelegationEffectsResult } : {}),
417
497
  ...(typeof child?.usage?.turns === "number" ? { turns: child.usage.turns } : {}),
418
498
  ...(typeof progress?.toolCount === "number" ? { toolCount: progress.toolCount } : {}),
419
499
  ...(typeof progress?.durationMs === "number" ? { durationMs: progress.durationMs } : {}),
@@ -422,6 +502,73 @@ export function toSubagentDelegationResponse(
422
502
  };
423
503
  }
424
504
 
505
+ const MAX_V2_RESULT_BYTES = 1024 * 1024;
506
+
507
+ export function toSubagentDelegationV2Response(
508
+ request: SubagentDelegationV2Request,
509
+ result: PromptTemplateBridgeResult,
510
+ aborted: boolean,
511
+ ): SubagentDelegationV2Response {
512
+ const child = result.details?.results?.[0];
513
+ const progress = child?.progressSummary ?? result.details?.progress?.[0];
514
+ let status = resolveSubagentDelegationStatus(result, aborted);
515
+ let error = child?.error ?? (status === "failed" ? firstTextContent(result.content) : undefined);
516
+ let projectedResult: SubagentDelegationV2Value | undefined;
517
+ if (status === "completed") {
518
+ if (request.result.kind === "text") {
519
+ if (typeof child?.finalOutput !== "string") {
520
+ status = "failed";
521
+ error = "Delegated subagent did not capture a text result.";
522
+ } else if (Buffer.byteLength(child.finalOutput, "utf8") > MAX_V2_RESULT_BYTES) {
523
+ status = "failed";
524
+ error = "Delegated text result exceeds 1 MiB when UTF-8 encoded.";
525
+ } else {
526
+ projectedResult = { kind: "text", text: child.finalOutput };
527
+ }
528
+ } else if (child?.structuredOutput === undefined) {
529
+ status = "failed";
530
+ error = "Delegated subagent did not capture the requested structured result.";
531
+ } else {
532
+ const inspected = cloneJsonWithinByteLimit(child.structuredOutput, MAX_V2_RESULT_BYTES);
533
+ if (!inspected.ok) {
534
+ status = "failed";
535
+ error = inspected.reason === "too_large"
536
+ ? "Delegated structured result exceeds 1 MiB when encoded."
537
+ : "Delegated structured result is not plain JSON data.";
538
+ } else {
539
+ projectedResult = { kind: "structured", value: inspected.value };
540
+ }
541
+ }
542
+ }
543
+ const usage = child?.usage;
544
+ return {
545
+ version: SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION,
546
+ requestId: request.requestId,
547
+ ownerRunId: request.ownerRunId,
548
+ nodeId: request.nodeId,
549
+ status,
550
+ ...(error ? { error } : {}),
551
+ ...(result.details?.runId ? { runId: result.details.runId } : {}),
552
+ ...(child?.agent ? { agent: child.agent } : {}),
553
+ ...(child?.model ? { model: child.model } : {}),
554
+ ...(child?.thinking ? { thinking: child.thinking } : {}),
555
+ ...(typeof child?.exitCode === "number" ? { exitCode: child.exitCode } : {}),
556
+ ...(projectedResult ? { result: projectedResult } : {}),
557
+ ...(usage ? {
558
+ usage: {
559
+ input: usage.input,
560
+ output: usage.output,
561
+ cacheRead: usage.cacheRead,
562
+ cacheWrite: usage.cacheWrite,
563
+ cost: usage.cost,
564
+ turns: usage.turns,
565
+ toolCalls: progress?.toolCount ?? 0,
566
+ durationMs: progress?.durationMs ?? 0,
567
+ },
568
+ } : {}),
569
+ };
570
+ }
571
+
425
572
  export function toPromptTemplateResponse(
426
573
  request: PromptTemplateDelegationRequest,
427
574
  result: PromptTemplateBridgeResult,