pi-subagents 0.54.0 → 0.55.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 (61) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/agents/reviewer.md +12 -2
  3. package/docs/agents.md +4 -4
  4. package/docs/configuration.md +4 -4
  5. package/docs/extension-api.md +14 -4
  6. package/docs/models.md +24 -3
  7. package/docs/observability.md +14 -3
  8. package/docs/tool-reference.md +21 -6
  9. package/docs/workflows.md +7 -1
  10. package/package.json +1 -1
  11. package/prompts/council.md +9 -0
  12. package/prompts/parallel-review.md +5 -1
  13. package/prompts/review-loop.md +9 -5
  14. package/skills/council-mode/SKILL.md +33 -10
  15. package/skills/pi-subagents/references/constraints-and-recipes.md +16 -1
  16. package/skills/pi-subagents/references/execution-controls.md +11 -4
  17. package/skills/pi-subagents/references/multi-lane-orchestration.md +3 -3
  18. package/skills/pi-subagents/references/prompting-and-roles.md +8 -8
  19. package/src/agents/agent-management.ts +30 -9
  20. package/src/agents/agent-serializer.ts +2 -2
  21. package/src/agents/agents.ts +146 -41
  22. package/src/api/external-job-provider.ts +10 -1
  23. package/src/api/preflight.ts +26 -17
  24. package/src/api/project-panes.ts +2 -0
  25. package/src/extension/index.ts +20 -1
  26. package/src/extension/public-execution.ts +12 -9
  27. package/src/extension/rpc.ts +77 -3
  28. package/src/extension/schemas.ts +2 -1
  29. package/src/extension/tool-description.ts +9 -6
  30. package/src/inspectors/herdr/focus.ts +55 -0
  31. package/src/inspectors/herdr/project-panes.ts +228 -44
  32. package/src/integrations/herdr-status.ts +26 -4
  33. package/src/runs/background/async-execution.ts +103 -9
  34. package/src/runs/background/async-job-tracker.ts +33 -14
  35. package/src/runs/background/async-resume.ts +20 -2
  36. package/src/runs/background/async-status.ts +10 -0
  37. package/src/runs/background/control-channel.ts +98 -10
  38. package/src/runs/background/notify.ts +62 -1
  39. package/src/runs/background/run-status.ts +15 -1
  40. package/src/runs/background/subagent-runner.ts +226 -37
  41. package/src/runs/foreground/async-stop-action.ts +23 -2
  42. package/src/runs/foreground/execution.ts +81 -7
  43. package/src/runs/foreground/subagent-executor.ts +348 -40
  44. package/src/runs/foreground/workflow-detach-reconcile.ts +3 -0
  45. package/src/runs/shared/acceptance.ts +1 -0
  46. package/src/runs/shared/child-identity.ts +36 -0
  47. package/src/runs/shared/completion-guard.ts +50 -1
  48. package/src/runs/shared/external-job-bridge.ts +53 -37
  49. package/src/runs/shared/external-job-runner.ts +126 -23
  50. package/src/runs/shared/model-fallback.ts +10 -0
  51. package/src/runs/shared/orca-progress-tabs.ts +71 -9
  52. package/src/runs/shared/parallel-utils.ts +12 -0
  53. package/src/runs/shared/pi-args.ts +9 -12
  54. package/src/runs/shared/subagent-prompt-runtime.ts +3 -1
  55. package/src/runs/shared/tool-availability.ts +1 -3
  56. package/src/shared/launch-contract.ts +6 -5
  57. package/src/shared/thinking-ceiling.ts +52 -0
  58. package/src/shared/types.ts +59 -2
  59. package/src/tui/fleet-status.ts +66 -13
  60. package/src/tui/render.ts +5 -4
  61. package/src/workflows/scripted-workflow.ts +107 -10
@@ -55,7 +55,7 @@ import {
55
55
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
56
56
  import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
57
57
  import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs, toolTimeoutCallKey, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
58
- import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
58
+ import { evaluateCompletionMutationGuard, validateImplementationToolContract } from "../shared/completion-guard.ts";
59
59
  import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
60
60
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
61
61
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
@@ -66,12 +66,14 @@ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolved
66
66
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
67
67
  import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
68
68
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
69
+ import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
69
70
  import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
70
71
  import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
71
72
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
72
73
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
73
74
  import {
74
75
  buildModelCandidates,
76
+ formatSubagentModelVerificationError,
75
77
  formatModelAttemptNote,
76
78
  isContextOverflow,
77
79
  isRetryableModelFailure,
@@ -310,10 +312,13 @@ async function runSingleAttempt(
310
312
  taskDelivery?: SubagentTaskDelivery;
311
313
  orcaProgressTab?: OrcaProgressTab;
312
314
  launchWarnings: { emitted: boolean };
315
+ verifyModel: boolean;
313
316
  },
314
317
  ): Promise<SingleResult> {
315
318
  const effectiveThinking = options.thinkingOverride ?? agent.thinking;
316
319
  const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
320
+ assertThinkingWithinCeiling({ model: modelArg, configThinking: effectiveThinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
321
+ const expectedModelForVerification = shared.verifyModel ? modelArg : undefined;
317
322
  const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
318
323
  const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
319
324
  const childWatchdog = watchdogConfig.ok
@@ -370,6 +375,7 @@ async function runSingleAttempt(
370
375
  childWatchdog,
371
376
  waitToolEnabled: options.waitToolEnabled,
372
377
  capabilityCeiling: options.capabilityCeiling,
378
+ thinkingCeiling: options.thinkingCeiling,
373
379
  });
374
380
  if (!shared.launchWarnings.emitted && warnings.length > 0) {
375
381
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -390,6 +396,36 @@ async function runSingleAttempt(
390
396
  agentName: agent.name,
391
397
  permissionRules,
392
398
  });
399
+ const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
400
+ const contractError = validateImplementationToolContract({
401
+ agent: agent.name,
402
+ task: shared.originalTask ?? task,
403
+ tools: contractTools,
404
+ mcpDirectTools: toolPlan.effectiveMcpTools,
405
+ configuredExtensions: toolPlan.configuredExtensions,
406
+ requestedTools: toolPlan.requestedBuiltinTools,
407
+ acceptanceRole: agent.acceptanceRole,
408
+ completionGuard: agent.completionGuard,
409
+ });
410
+ if (contractError) {
411
+ cleanupTempDir(tempDir);
412
+ return {
413
+ index: options.index ?? 0,
414
+ agent: agent.name,
415
+ task,
416
+ messages: [],
417
+ finalOutput: "",
418
+ exitCode: 1,
419
+ error: contractError,
420
+ usage: emptyUsage(),
421
+ model: modelArg,
422
+ modelAttempts: [],
423
+ attemptedModels: [],
424
+ progressSummary: { status: "failed", toolCount: 0, tokens: 0, durationMs: 0 },
425
+ ...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
426
+ ...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
427
+ };
428
+ }
393
429
  const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
394
430
  const launchContractDigest = launchBindingDigest({
395
431
  definitionDigest: agentDefinitionDigest(agent),
@@ -397,6 +433,7 @@ async function runSingleAttempt(
397
433
  ...(modelArg ? { model: modelArg } : {}),
398
434
  modelCandidates: shared.modelCandidates,
399
435
  ...(resolvedThinking ? { thinking: resolvedThinking } : {}),
436
+ ...(options.thinkingCeiling ? { thinkingCeiling: options.thinkingCeiling } : {}),
400
437
  systemPrompt: effectiveSystemPrompt,
401
438
  systemPromptMode: agent.systemPromptMode,
402
439
  inheritProjectContext: agent.inheritProjectContext,
@@ -490,6 +527,7 @@ async function runSingleAttempt(
490
527
  let observedMutationAttempt = false;
491
528
  let structuredOutputToolInvoked = false;
492
529
  let structuredOutputMessageStartIndex: number | undefined;
530
+ let toolAvailabilityError: string | undefined;
493
531
 
494
532
  const exitCode = await new Promise<number>((resolve) => {
495
533
  const spawnSpec = getPiSpawnCommand(args);
@@ -1042,6 +1080,10 @@ async function runSingleAttempt(
1042
1080
  if (evt.message.model) {
1043
1081
  progress.model = evt.message.model;
1044
1082
  if (!result.model) result.model = evt.message.model;
1083
+ if (expectedModelForVerification && !hasToolCall) {
1084
+ const modelVerificationError = formatSubagentModelVerificationError(expectedModelForVerification, evt.message.model, options.availableModels);
1085
+ if (modelVerificationError && !result.error) result.error = modelVerificationError;
1086
+ }
1045
1087
  }
1046
1088
  if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
1047
1089
  const assistantText = extractTextFromContent(evt.message.content);
@@ -1267,6 +1309,7 @@ async function runSingleAttempt(
1267
1309
  // JSONL artifact flush is best effort.
1268
1310
  });
1269
1311
  const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
1312
+ toolAvailabilityError = toolDiagnosticError;
1270
1313
  result.runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
1271
1314
  cleanupTempDir(tempDir);
1272
1315
  stdoutReader.end();
@@ -1462,16 +1505,18 @@ async function runSingleAttempt(
1462
1505
  fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
1463
1506
  }
1464
1507
  const completionGuardEnabled = isAgentContractV1(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
1465
- const completionGuard = result.exitCode === 0 && !result.error && completionGuardEnabled
1508
+ const completionGuard = ((result.exitCode === 0 && !result.error) || toolAvailabilityError) && completionGuardEnabled
1466
1509
  ? evaluateCompletionMutationGuard({
1467
1510
  agent: agent.name,
1468
1511
  task: shared.originalTask ?? task,
1469
1512
  messages: result.messages ?? [],
1470
- tools: agent.tools,
1471
- mcpDirectTools: agent.mcpDirectTools,
1513
+ tools: contractTools,
1514
+ mcpDirectTools: toolPlan.effectiveMcpTools,
1515
+ toolAvailabilityError,
1472
1516
  })
1473
1517
  : undefined;
1474
1518
  let completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
1519
+ const completionGuardBlocked = completionGuard?.blocked === true;
1475
1520
  // The classifier is deliberately narrow, so a read-only review task can
1476
1521
  // still be misread as implementation. Arbitrate BEFORE any failure side
1477
1522
  // effect is published (effects, exit code, progress, notifications,
@@ -1492,7 +1537,9 @@ async function runSingleAttempt(
1492
1537
  result.effects = {
1493
1538
  ...(result.effects ?? {}),
1494
1539
  fileMutation: {
1495
- status: completionGuard.expectedMutation
1540
+ status: completionGuardBlocked
1541
+ ? "blocked"
1542
+ : completionGuard.expectedMutation
1496
1543
  ? completionGuardTriggered
1497
1544
  ? "missing"
1498
1545
  : arbiterRescued
@@ -1500,7 +1547,8 @@ async function runSingleAttempt(
1500
1547
  : "observed"
1501
1548
  : "not-applicable",
1502
1549
  expected: completionGuard.expectedMutation,
1503
- attempted: completionGuard.attemptedMutation || observedMutationAttempt,
1550
+ attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || observedMutationAttempt,
1551
+ ...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
1504
1552
  ...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
1505
1553
  ...(arbiterRescued ? { resolvedBy: "llm-intent-arbiter" } : {}),
1506
1554
  },
@@ -1582,6 +1630,14 @@ async function runSyncCompletionInner(
1582
1630
  error: `Unknown agent: ${agentName}`,
1583
1631
  }, options.context));
1584
1632
  }
1633
+ options = {
1634
+ ...options,
1635
+ thinkingCeiling: intersectThinkingCeilings(
1636
+ options.thinkingCeiling,
1637
+ agent.maxThinking,
1638
+ decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]),
1639
+ ),
1640
+ };
1585
1641
  try {
1586
1642
  assertAgentAllowedByCapabilityCeiling(agent.name, options.capabilityCeiling);
1587
1643
  } catch (error) {
@@ -1695,9 +1751,25 @@ async function runSyncCompletionInner(
1695
1751
  options.modelOverride ?? agent.model,
1696
1752
  agent.fallbackModels,
1697
1753
  options.availableModels,
1698
- options.preferredModelProvider,
1754
+ agent.modelProvider ?? options.preferredModelProvider,
1699
1755
  { scope: options.modelScope, primaryModelFromParent: options.modelOverrideFromParent },
1700
1756
  );
1757
+ try {
1758
+ for (const candidate of candidates) {
1759
+ const model = applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
1760
+ assertThinkingWithinCeiling({ model, configThinking: options.thinkingOverride ?? agent.thinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
1761
+ }
1762
+ } catch (error) {
1763
+ return redactResultPrompt(withRunContext({
1764
+ index: options.index ?? 0,
1765
+ agent: agent.name,
1766
+ task,
1767
+ exitCode: 1,
1768
+ messages: [],
1769
+ usage: emptyUsage(),
1770
+ error: error instanceof Error ? error.message : String(error),
1771
+ }, options.context));
1772
+ }
1701
1773
  const attemptedModels: string[] = [];
1702
1774
  const modelAttempts: ModelAttempt[] = [];
1703
1775
  const aggregateUsage = emptyUsage();
@@ -1777,6 +1849,7 @@ async function runSyncCompletionInner(
1777
1849
  modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
1778
1850
  const candidate = modelsToTry[modelIndex];
1779
1851
  for (let startupAttemptIndex = 0; ; startupAttemptIndex++) {
1852
+ const verifyModel = Boolean(candidate) && !(options.modelOverrideFromParent && modelIndex === 0);
1780
1853
  const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1781
1854
  const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, attemptOptions, {
1782
1855
  sessionEnabled,
@@ -1795,6 +1868,7 @@ async function runSyncCompletionInner(
1795
1868
  taskDelivery: taskDeliveryOverride,
1796
1869
  orcaProgressTab,
1797
1870
  launchWarnings,
1871
+ verifyModel,
1798
1872
  });
1799
1873
  lastResult = result;
1800
1874
  if (startupAttemptIndex === 0) {