pi-subagents 0.36.0 → 0.37.1

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 (54) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +111 -16
  3. package/agents/delegate.md +2 -0
  4. package/agents/planner.md +2 -1
  5. package/agents/worker.md +2 -0
  6. package/package.json +6 -4
  7. package/skills/pi-subagents/SKILL.md +18 -973
  8. package/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
  9. package/skills/pi-subagents/references/execution-controls.md +411 -0
  10. package/skills/pi-subagents/references/management-authoring-rpc.md +140 -0
  11. package/skills/pi-subagents/references/prompting-and-roles.md +268 -0
  12. package/src/agents/agent-management.ts +7 -1
  13. package/src/agents/agents.ts +99 -12
  14. package/src/api/capability-ceiling.ts +17 -0
  15. package/src/api/delegation.ts +6 -1
  16. package/src/api/preflight.ts +399 -0
  17. package/src/extension/index.ts +16 -7
  18. package/src/extension/rpc.ts +29 -2
  19. package/src/extension/schemas.ts +10 -4
  20. package/src/extension/tool-description.ts +4 -2
  21. package/src/intercom/intercom-bridge.ts +5 -2
  22. package/src/runs/background/async-execution.ts +125 -14
  23. package/src/runs/background/async-job-tracker.ts +19 -12
  24. package/src/runs/background/async-resume.ts +27 -9
  25. package/src/runs/background/async-status.ts +18 -0
  26. package/src/runs/background/notify.ts +3 -0
  27. package/src/runs/background/process-terminal.ts +280 -0
  28. package/src/runs/background/result-watcher.ts +9 -3
  29. package/src/runs/background/run-status.ts +7 -1
  30. package/src/runs/background/scheduled-runs.ts +6 -1
  31. package/src/runs/background/stale-run-reconciler.ts +6 -0
  32. package/src/runs/background/subagent-runner.ts +182 -12
  33. package/src/runs/foreground/chain-execution.ts +5 -0
  34. package/src/runs/foreground/execution.ts +59 -13
  35. package/src/runs/foreground/subagent-executor.ts +142 -47
  36. package/src/runs/shared/acceptance.ts +41 -30
  37. package/src/runs/shared/capability-ceiling.ts +177 -0
  38. package/src/runs/shared/dynamic-fanout.ts +1 -1
  39. package/src/runs/shared/mcp-direct-tool-allowlist.ts +56 -17
  40. package/src/runs/shared/model-fallback.ts +8 -0
  41. package/src/runs/shared/nested-events.ts +8 -1
  42. package/src/runs/shared/parallel-utils.ts +5 -0
  43. package/src/runs/shared/pi-args.ts +141 -58
  44. package/src/runs/shared/session-lease.ts +25 -5
  45. package/src/runs/shared/subagent-prompt-runtime.ts +14 -1
  46. package/src/runs/shared/task-intent.ts +1 -1
  47. package/src/runs/shared/tool-availability.ts +18 -2
  48. package/src/shared/launch-contract.ts +123 -0
  49. package/src/shared/types.ts +118 -8
  50. package/src/shared/utils.ts +17 -42
  51. package/src/slash/delegation-adapters.ts +6 -1
  52. package/src/slash/slash-commands.ts +1 -1
  53. package/src/tui/fleet-status.ts +62 -16
  54. package/src/tui/fleet.ts +1 -1
@@ -42,6 +42,7 @@ import {
42
42
  getFinalOutput,
43
43
  findLatestSessionFile,
44
44
  detectSubagentError,
45
+ hasEmptyTerminalAssistantResponse,
45
46
  extractToolArgsPreview,
46
47
  extractTextFromContent,
47
48
  } from "../../shared/utils.ts";
@@ -51,7 +52,8 @@ import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
51
52
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
52
53
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
53
54
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
54
- import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
55
+ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
56
+ import { decodeSubagentCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
55
57
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
56
58
  import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
57
59
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
@@ -77,6 +79,7 @@ import { attachContractProjections, isAgentContractV1 } from "../shared/agent-co
77
79
  import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
78
80
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
79
81
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
82
+ import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
80
83
  import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
81
84
  import {
82
85
  acceptChildWatchdogEvent,
@@ -125,6 +128,7 @@ function resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; re
125
128
  function buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {
126
129
  return {
127
130
  status: "pending",
131
+ evidenceStatus: "pending",
128
132
  explicit: acceptance.explicit,
129
133
  effectiveAcceptance: acceptance,
130
134
  inferredReason: acceptance.inferredReason,
@@ -194,6 +198,7 @@ async function runSingleAttempt(
194
198
  sessionEnabled: boolean;
195
199
  systemPrompt: string;
196
200
  resolvedSkillNames?: string[];
201
+ modelCandidates?: string[];
197
202
  skillsWarning?: string;
198
203
  jsonlPath?: string;
199
204
  artifactPaths?: ArtifactPaths;
@@ -215,7 +220,7 @@ async function runSingleAttempt(
215
220
  childIndex: options.index ?? 0,
216
221
  })
217
222
  : undefined;
218
- const { args, env: sharedEnv, tempDir, toolDiagnosticPath } = buildPiArgs({
223
+ const { args, env: sharedEnv, tempDir, toolDiagnosticPath, capabilityAudit } = buildPiArgs({
219
224
  baseArgs: ["--mode", "json", "-p"],
220
225
  task,
221
226
  sessionEnabled: shared.sessionEnabled,
@@ -249,12 +254,44 @@ async function runSingleAttempt(
249
254
  allowZeroToolBudget: options.allowZeroToolBudget,
250
255
  childWatchdog,
251
256
  waitToolEnabled: options.waitToolEnabled,
257
+ capabilityCeiling: options.capabilityCeiling,
252
258
  });
253
259
 
260
+ const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget);
261
+ const toolPlan = resolvePiLaunchToolPlan({
262
+ tools: agent.tools,
263
+ extensions: agent.extensions,
264
+ subagentOnlyExtensions: agent.subagentOnlyExtensions,
265
+ mcpDirectTools: agent.mcpDirectTools,
266
+ cwd: options.cwd ?? runtimeCwd,
267
+ requireReadTool: Boolean(shared.resolvedSkillNames?.length),
268
+ structuredOutput: Boolean(options.structuredOutput),
269
+ capabilityCeiling: options.capabilityCeiling,
270
+ inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
271
+ });
272
+ const launchContractDigest = launchBindingDigest({
273
+ definitionDigest: agentDefinitionDigest(agent),
274
+ task: shared.originalTask ?? task,
275
+ ...(modelArg ? { model: modelArg } : {}),
276
+ modelCandidates: shared.modelCandidates,
277
+ ...(resolvedThinking ? { thinking: resolvedThinking } : {}),
278
+ systemPrompt: effectiveSystemPrompt,
279
+ systemPromptMode: agent.systemPromptMode,
280
+ inheritProjectContext: agent.inheritProjectContext,
281
+ inheritSkills: agent.inheritSkills,
282
+ skills: shared.resolvedSkillNames ?? [],
283
+ tools: toolPlan.effectiveToolAllowlist,
284
+ extensions: toolPlan.extensionArgs,
285
+ mcpDirectTools: toolPlan.effectiveMcpTools,
286
+ ...(options.outputPath ? { outputPath: options.outputPath } : {}),
287
+ outputMode: options.outputMode ?? "inline",
288
+ ...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
289
+ });
254
290
  const result: SingleResult = withRunContext({
255
291
  agent: agent.name,
256
292
  task: shared.originalTask ?? task,
257
293
  ...(options.agentContract ? { agentContract: options.agentContract } : {}),
294
+ launchContractDigest,
258
295
  exitCode: 0,
259
296
  messages: [],
260
297
  usage: emptyUsage(),
@@ -266,6 +303,8 @@ async function runSingleAttempt(
266
303
  skillsWarning: shared.skillsWarning,
267
304
  ...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),
268
305
  ...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),
306
+ ...(options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}),
307
+ ...(capabilityAudit ? { capabilityAudit } : {}),
269
308
  }, options.context);
270
309
  const startTime = Date.now();
271
310
  if (options.structuredOutput) {
@@ -1041,22 +1080,21 @@ async function runSingleAttempt(
1041
1080
  result.exitCode = 1;
1042
1081
  }
1043
1082
  if (result.exitCode === 0 && !result.error) {
1044
- const errInfo = detectSubagentError(result.messages ?? []);
1045
- if (errInfo.hasError) {
1046
- result.exitCode = errInfo.exitCode ?? 1;
1047
- result.error = errInfo.details
1048
- ? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`
1049
- : `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
1050
- }
1051
- }
1052
- if (result.exitCode === 0 && !result.error) {
1053
- const finalText = getFinalOutput(result.messages ?? []);
1083
+ const messages = result.messages ?? [];
1084
+ const finalText = getFinalOutput(messages);
1054
1085
  const missingStructuredOutput = options.structuredOutput
1055
1086
  ? !existsSync(options.structuredOutput.outputPath)
1056
1087
  : false;
1057
- if (!finalText?.trim() && (!options.structuredOutput || missingStructuredOutput)) {
1088
+ const errInfo = detectSubagentError(messages);
1089
+ const missingOutput = !finalText?.trim() && (!options.structuredOutput || missingStructuredOutput);
1090
+ if (missingOutput && (!errInfo.hasError || hasEmptyTerminalAssistantResponse(messages))) {
1058
1091
  result.exitCode = 1;
1059
1092
  result.error = "Subagent produced no output (possible model cold-start or empty response).";
1093
+ } else if (errInfo.hasError) {
1094
+ result.exitCode = errInfo.exitCode ?? 1;
1095
+ result.error = errInfo.details
1096
+ ? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`
1097
+ : `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
1060
1098
  }
1061
1099
  }
1062
1100
  if (options.structuredOutput && result.exitCode === 0 && !result.error) {
@@ -1194,6 +1232,10 @@ export async function runSync(
1194
1232
  task: string,
1195
1233
  options: RunSyncOptions,
1196
1234
  ): Promise<SingleResult> {
1235
+ options = {
1236
+ ...options,
1237
+ capabilityCeiling: options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId),
1238
+ };
1197
1239
  const agent = agents.find((a) => a.name === agentName);
1198
1240
  if (!agent) {
1199
1241
  return withRunContext({
@@ -1317,8 +1359,11 @@ export async function runSync(
1317
1359
  toolCount: target.progressSummary?.toolCount,
1318
1360
  error: target.error,
1319
1361
  agentContract: target.agentContract,
1362
+ launchContractDigest: target.launchContractDigest,
1320
1363
  execution: target.execution,
1321
1364
  acceptance: target.acceptance,
1365
+ capabilityCeiling: target.capabilityCeiling,
1366
+ capabilityAudit: target.capabilityAudit,
1322
1367
  review: target.review,
1323
1368
  effects: target.effects,
1324
1369
  ...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),
@@ -1383,6 +1428,7 @@ export async function runSync(
1383
1428
  artifactPaths: artifactPathsResult,
1384
1429
  transcriptWriter,
1385
1430
  attemptNotes,
1431
+ modelCandidates: candidates.map((candidate) => applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined)),
1386
1432
  outputSnapshot,
1387
1433
  originalTask: task,
1388
1434
  });