pi-subagents 0.57.0 → 0.59.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 (115) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/docs/agents.md +18 -9
  3. package/docs/configuration.md +4 -4
  4. package/docs/extension-api.md +40 -1
  5. package/docs/models.md +27 -4
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +92 -6
  8. package/docs/workflows.md +115 -0
  9. package/package.json +3 -1
  10. package/prompts/review-loop.md +2 -2
  11. package/skills/council-mode/SKILL.md +1 -1
  12. package/skills/pi-subagents/SKILL.md +3 -1
  13. package/skills/pi-subagents/references/execution-controls.md +6 -2
  14. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  15. package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  16. package/src/agents/agent-management.ts +28 -17
  17. package/src/agents/agent-serializer.ts +9 -2
  18. package/src/agents/agents.ts +103 -27
  19. package/src/agents/runtime-agent-events.ts +70 -0
  20. package/src/agents/runtime-agent-registry.ts +19 -18
  21. package/src/api/agents.ts +10 -5
  22. package/src/api/background-work.ts +5 -1
  23. package/src/api/delegation.ts +0 -7
  24. package/src/api/preflight.ts +32 -10
  25. package/src/extension/fanout-child.ts +5 -3
  26. package/src/extension/index.ts +66 -27
  27. package/src/extension/public-execution.ts +15 -2
  28. package/src/extension/rpc.ts +35 -14
  29. package/src/extension/schemas.ts +37 -12
  30. package/src/extension/tool-description.ts +25 -6
  31. package/src/integrations/herdr-status.ts +5 -0
  32. package/src/intercom/result-intercom.ts +2 -0
  33. package/src/profiles/profiles.ts +5 -6
  34. package/src/runs/background/active-async-capacity.ts +2 -2
  35. package/src/runs/background/async-execution.ts +78 -46
  36. package/src/runs/background/async-job-tracker.ts +14 -12
  37. package/src/runs/background/async-resume.ts +29 -27
  38. package/src/runs/background/async-status-snapshot.ts +23 -261
  39. package/src/runs/background/async-status.ts +68 -7
  40. package/src/runs/background/chain-append.ts +8 -3
  41. package/src/runs/background/chain-root-attachment.ts +60 -8
  42. package/src/runs/background/fleet-view.ts +21 -11
  43. package/src/runs/background/notify.ts +184 -9
  44. package/src/runs/background/result-delivery-ownership.ts +45 -0
  45. package/src/runs/background/result-files.ts +2 -1
  46. package/src/runs/background/result-watcher.ts +29 -10
  47. package/src/runs/background/resume-guidance.ts +1 -1
  48. package/src/runs/background/retained-children.ts +1 -1
  49. package/src/runs/background/run-status.ts +18 -8
  50. package/src/runs/background/scheduled-runs.ts +86 -7
  51. package/src/runs/background/stale-run-reconciler.ts +10 -4
  52. package/src/runs/background/steering.ts +4 -14
  53. package/src/runs/background/subagent-runner.ts +389 -350
  54. package/src/runs/background/subagent-wait.ts +58 -10
  55. package/src/runs/background/terminal-run-index.ts +1 -1
  56. package/src/runs/background/wait-completions.ts +22 -1
  57. package/src/runs/background/wait-config.ts +23 -9
  58. package/src/runs/background/wait-tool.ts +9 -2
  59. package/src/runs/foreground/async-steering-action.ts +2 -2
  60. package/src/runs/foreground/execution.ts +134 -121
  61. package/src/runs/foreground/foreground-control.ts +3 -0
  62. package/src/runs/foreground/foreground-history.ts +1 -0
  63. package/src/runs/foreground/subagent-executor.ts +527 -257
  64. package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
  65. package/src/runs/shared/abort-recovery.ts +119 -0
  66. package/src/runs/shared/async-status-projection.ts +463 -0
  67. package/src/runs/shared/child-identity.ts +19 -4
  68. package/src/runs/shared/child-launch-plan.ts +151 -0
  69. package/src/runs/shared/completion-evidence.ts +89 -0
  70. package/src/runs/shared/completion-guard.ts +5 -4
  71. package/src/runs/shared/dynamic-fanout.ts +3 -3
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/host-step-status.ts +230 -0
  74. package/src/runs/shared/lane-metadata.ts +105 -0
  75. package/src/runs/shared/launch-cwd.ts +16 -0
  76. package/src/runs/shared/long-running-guard.ts +2 -1
  77. package/src/runs/shared/mcp-config-sources.ts +422 -0
  78. package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  79. package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  80. package/src/runs/shared/model-exclusions.ts +12 -0
  81. package/src/runs/shared/model-fallback.ts +25 -6
  82. package/src/runs/shared/nested-events.ts +6 -2
  83. package/src/runs/shared/nested-render.ts +7 -3
  84. package/src/runs/shared/parallel-handoff.ts +419 -7
  85. package/src/runs/shared/parallel-utils.ts +15 -1
  86. package/src/runs/shared/pi-args.ts +81 -8
  87. package/src/runs/shared/single-output.ts +44 -4
  88. package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  89. package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  90. package/src/runs/shared/worktree.ts +18 -0
  91. package/src/shared/child-session-name.ts +46 -0
  92. package/src/shared/extension-context.ts +24 -0
  93. package/src/shared/formatters.ts +11 -2
  94. package/src/shared/launch-contract.ts +5 -1
  95. package/src/shared/settings.ts +9 -103
  96. package/src/shared/types.ts +240 -30
  97. package/src/shared/utils.ts +41 -84
  98. package/src/slash/delegation-adapters.ts +1 -8
  99. package/src/slash/delegation-request.ts +0 -4
  100. package/src/slash/slash-bridge.ts +1 -2
  101. package/src/slash/slash-commands.ts +369 -90
  102. package/src/slash/slash-live-state.ts +22 -11
  103. package/src/slash/subagents-admin.ts +3 -0
  104. package/src/tui/fleet-status.ts +125 -74
  105. package/src/tui/fleet.ts +11 -5
  106. package/src/tui/render.ts +330 -33
  107. package/src/watchdog/turn-delta.ts +1 -1
  108. package/src/workflows/chat-progress.ts +6 -3
  109. package/src/workflows/host-command.ts +230 -0
  110. package/src/workflows/scripted-workflow.ts +496 -35
  111. package/src/workflows/workflow-child-summary.ts +9 -5
  112. package/src/workflows/workflow-preflight.ts +270 -0
  113. package/src/workflows/workflow-receipt.ts +79 -5
  114. package/src/workflows/workflow-settlement.ts +246 -0
  115. package/src/runs/shared/turn-budget.ts +0 -98
@@ -1,6 +1,6 @@
1
1
  import { splitKnownThinkingSuffix, type ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
2
2
  import type { Usage } from "../../shared/types.ts";
3
- import { filterFallbackCandidates, parseModelKey, recordModelFailure } from "./model-exclusions.ts";
3
+ import { filterFallbackCandidates, findModelExclusion, parseModelKey, recordModelFailure } from "./model-exclusions.ts";
4
4
  import { checkModelScope, type ModelScopeCheckRule, type ModelScopeViolation, type ModelSource } from "./model-scope.ts";
5
5
  import { redactSecretValues } from "./permissions.ts";
6
6
 
@@ -24,7 +24,12 @@ export function formatSubagentModelVerificationError(expectedModel: string, obse
24
24
  const observedBase = splitThinkingSuffix(observedModel).baseModel;
25
25
  if (expectedBase === observedBase) return undefined;
26
26
  const expectedEntry = availableModels.find((entry) => entry.fullId === expectedBase);
27
- if (expectedEntry && expectedEntry.id === observedBase) return undefined;
27
+ if (expectedEntry) {
28
+ if (expectedEntry.id === observedBase) return undefined;
29
+ const expectedIdLeaf = expectedEntry.id.slice(expectedEntry.id.lastIndexOf("/") + 1);
30
+ const expectedFullIdLeaf = expectedEntry.fullId.slice(expectedEntry.fullId.lastIndexOf("/") + 1);
31
+ if (expectedIdLeaf === observedBase || expectedFullIdLeaf === observedBase) return undefined;
32
+ }
28
33
  return `model_verification_failed: child reported a different model than the launch candidate. Expected '${expectedModel}' but observed '${observedModel}'.`;
29
34
  }
30
35
 
@@ -279,6 +284,14 @@ function enforceModelScopes(
279
284
  for (const violation of violations) (onWarn ?? defaultScopeWarn)(violation);
280
285
  }
281
286
 
287
+ function throwForExplicitModelExclusion(model: string): void {
288
+ const exclusion = findModelExclusion(model);
289
+ if (!exclusion) return;
290
+ const reason = redactSecretValues((exclusion.reason ?? "runtime-failure").replace(/[\u0000-\u001f\u007f]+/g, " ")).slice(0, 240);
291
+ const expiry = Number.isFinite(exclusion.expiresAt) ? `; expires: ${new Date(exclusion.expiresAt).toISOString()}` : "";
292
+ throw new Error(`Requested subagent model '${model}' is excluded and cannot be replaced by a fallback (reason: ${reason}${expiry}).`);
293
+ }
294
+
282
295
  /**
283
296
  * Resolve the `--model` override passed to a spawned subagent.
284
297
  *
@@ -313,6 +326,9 @@ export function resolveSubagentModelOverride(
313
326
  } else {
314
327
  resolved = resolveRequiredSubagentModelCandidate(explicit, availableModels, preferredProvider);
315
328
  }
329
+ if (resolved && explicit !== undefined && options?.source === "explicit") {
330
+ throwForExplicitModelExclusion(resolved);
331
+ }
316
332
  if (resolved && options?.scope) {
317
333
  const source: ModelSource = explicit === undefined ? "inherited" : (options.source ?? "inherited");
318
334
  enforceModelScopes(resolved, options.scope, source, options.onWarn);
@@ -326,14 +342,15 @@ export function resolveEffectiveSubagentModel(
326
342
  parentModel: ParentModel | undefined,
327
343
  availableModels: AvailableModelInfo[] | undefined,
328
344
  preferredProvider?: string,
329
- options?: Omit<ResolveSubagentModelOverrideOptions, "source">,
345
+ options?: ResolveSubagentModelOverrideOptions,
330
346
  ): string | undefined {
347
+ const source = options?.source ?? (explicitModel !== undefined ? "explicit" : "inherited");
331
348
  const resolved = resolveSubagentModelOverride(
332
349
  explicitModel ?? agentModel,
333
350
  parentModel,
334
351
  availableModels,
335
352
  preferredProvider,
336
- { ...options, source: explicitModel !== undefined ? "explicit" : "inherited" },
353
+ { ...options, source },
337
354
  );
338
355
  if (resolved || explicitModel === undefined) return resolved;
339
356
  return resolveSubagentModelOverride(
@@ -341,7 +358,7 @@ export function resolveEffectiveSubagentModel(
341
358
  parentModel,
342
359
  availableModels,
343
360
  preferredProvider,
344
- { ...options, source: "inherited" },
361
+ { ...options, source: options?.source ?? "inherited" },
345
362
  );
346
363
  }
347
364
 
@@ -434,9 +451,11 @@ const RETRYABLE_MODEL_FAILURE_PATTERNS = [
434
451
  /upstream/i,
435
452
  /timed? out/i,
436
453
  /timeout/i,
454
+ /\b500\b/,
437
455
  /\b502\b/,
438
456
  /\b503\b/,
439
457
  /\b504\b/,
458
+ /internal server error/i,
440
459
  /cold.?start/i,
441
460
  /empty response/i,
442
461
  /no output/i,
@@ -467,7 +486,7 @@ function messageError(message: unknown): string | undefined {
467
486
  export function isRetryableModelFailureAttempt(input: { error: string | undefined; messages?: readonly unknown[]; toolCount?: number }): boolean {
468
487
  if (!isRetryableModelFailure(input.error)) return false;
469
488
  if ((input.toolCount ?? 0) > 0) return false;
470
- if (input.error === "Subagent produced no output (possible model cold-start or empty response).") return true;
489
+ if (input.error === "Subagent produced no output (possible model cold-start or empty response)." || /^Subagent produced no output after terminal assistant stopReason "[^"]+"\.$/.test(input.error ?? "")) return true;
471
490
  if ((input.toolCount ?? 0) === 0 && (input.messages?.length ?? 0) === 0) return true;
472
491
  const error = input.error?.trim();
473
492
  return Boolean(error && input.messages?.some((message) => messageError(message)?.trim() === error));
@@ -286,7 +286,7 @@ function sanitizeTurnBudget(value: unknown): TurnBudgetState | undefined {
286
286
  }
287
287
 
288
288
  function sanitizeState(value: unknown, fallback: NestedRunState): NestedRunState {
289
- return value === "queued" || value === "running" || value === "complete" || value === "failed" || value === "paused" || value === "stopped"
289
+ return value === "queued" || value === "running" || value === "complete" || value === "failed" || value === "partial" || value === "paused" || value === "stopped"
290
290
  ? value
291
291
  : fallback;
292
292
  }
@@ -306,6 +306,7 @@ function sanitizeStep(input: unknown, depth: number): NestedStepSummary | undefi
306
306
  status,
307
307
  ...(model ? { model } : {}),
308
308
  ...(thinking ? { thinking } : {}),
309
+ ...(stringValue(raw.sessionName, 256) ? { sessionName: stringValue(raw.sessionName, 256) } : {}),
309
310
  ...(stringValue(raw.sessionFile, 2048) ? { sessionFile: stringValue(raw.sessionFile, 2048) } : {}),
310
311
  ...(raw.activityState === "active_long_running" || raw.activityState === "needs_attention" ? { activityState: raw.activityState } : {}),
311
312
  ...(clampNumber(raw.lastActivityAt) !== undefined ? { lastActivityAt: clampNumber(raw.lastActivityAt) } : {}),
@@ -346,6 +347,7 @@ export function sanitizeSummary(input: unknown, depth = 0): NestedRunSummary | u
346
347
  depth: Math.min(Math.max(0, clampNumber(raw.depth) ?? 0), MAX_DEPTH),
347
348
  path: pathParts,
348
349
  state: sanitizeState(raw.state, "running"),
350
+ ...(stringValue(raw.sessionName, 256) ? { sessionName: stringValue(raw.sessionName, 256) } : {}),
349
351
  ...(stringValue(raw.model) ? { model: stringValue(raw.model) } : {}),
350
352
  ...(THINKING_LEVELS.find((level) => level === raw.thinking) ? { thinking: THINKING_LEVELS.find((level) => level === raw.thinking) } : {}),
351
353
  ...(stringValue(raw.asyncDir, 2048) ? { asyncDir: stringValue(raw.asyncDir, 2048) } : {}),
@@ -436,7 +438,7 @@ export function parseNestedEventRecords(content: string, route: NestedRoute): Ne
436
438
  }
437
439
 
438
440
  function terminal(state: NestedRunState): boolean {
439
- return state === "complete" || state === "failed" || state === "paused" || state === "stopped";
441
+ return state === "complete" || state === "failed" || state === "partial" || state === "paused" || state === "stopped";
440
442
  }
441
443
 
442
444
  function mergeBoundedChildren(existing: NestedRunSummary[] | undefined, incoming: NestedRunSummary[] | undefined): NestedRunSummary[] | undefined {
@@ -1005,6 +1007,7 @@ export function nestedSummaryFromAsyncStatus(status: AsyncStatus, asyncDir: stri
1005
1007
  mode: status.mode ?? fallback.mode,
1006
1008
  ...(status.steps?.length === 1 && status.steps[0]?.model ? { model: status.steps[0].model } : {}),
1007
1009
  ...(status.steps?.length === 1 && status.steps[0]?.thinking ? { thinking: status.steps[0].thinking } : {}),
1010
+ ...(status.steps?.length === 1 && status.steps[0]?.sessionName ? { sessionName: status.steps[0].sessionName } : {}),
1008
1011
  ...(status.processTerminal ? { processTerminal: sanitizeProcessTerminal(status.processTerminal, { runId: status.runId || fallback.id, runnerProcessInstanceId: status.processTerminal.runnerProcessInstanceId }, `${asyncDir}/status.json`) } : {}),
1009
1012
  ...(status.launchResolvedExtensions ? { launchResolvedExtensions: status.launchResolvedExtensions } : {}),
1010
1013
  ...runtimeAcknowledgedEntry(status.runtimeAcknowledgedExtensions),
@@ -1035,6 +1038,7 @@ export function nestedSummaryFromAsyncStatus(status: AsyncStatus, asyncDir: stri
1035
1038
  ...(status.sessionFile ? { sessionFile: status.sessionFile } : {}),
1036
1039
  ...(status.steps?.length ? { steps: status.steps.map((step, index) => ({
1037
1040
  agent: step.agent,
1041
+ ...(step.sessionName ? { sessionName: step.sessionName } : {}),
1038
1042
  status: step.status,
1039
1043
  ...(step.model ? { model: step.model } : {}),
1040
1044
  ...(step.thinking ? { thinking: step.thinking } : {}),
@@ -1,6 +1,6 @@
1
1
  import { formatDuration, formatModelThinking, formatTokenUsage, shortenPath } from "../../shared/formatters.ts";
2
2
  import { formatActivityLabel } from "../../shared/status-format.ts";
3
- import type { ActivityState, NestedRunSummary, NestedStepSummary } from "../../shared/types.ts";
3
+ import type { ActivityState, NestedRunSummary } from "../../shared/types.ts";
4
4
 
5
5
  export interface NestedRunCounts {
6
6
  total: number;
@@ -8,13 +8,14 @@ export interface NestedRunCounts {
8
8
  paused: number;
9
9
  complete: number;
10
10
  failed: number;
11
+ partial: number;
11
12
  rejected: number;
12
13
  stopped: number;
13
14
  queued: number;
14
15
  }
15
16
 
16
17
  export function countNestedRuns(children: NestedRunSummary[] | undefined): NestedRunCounts {
17
- const counts: NestedRunCounts = { total: 0, running: 0, paused: 0, complete: 0, failed: 0, rejected: 0, stopped: 0, queued: 0 };
18
+ const counts: NestedRunCounts = { total: 0, running: 0, paused: 0, complete: 0, failed: 0, partial: 0, rejected: 0, stopped: 0, queued: 0 };
18
19
  for (const child of children ?? []) {
19
20
  counts.total++;
20
21
  counts[child.state]++;
@@ -24,6 +25,7 @@ export function countNestedRuns(children: NestedRunSummary[] | undefined): Neste
24
25
  counts.paused += nested.paused;
25
26
  counts.complete += nested.complete;
26
27
  counts.failed += nested.failed;
28
+ counts.partial += nested.partial;
27
29
  counts.rejected += nested.rejected;
28
30
  counts.stopped += nested.stopped;
29
31
  counts.queued += nested.queued;
@@ -38,6 +40,7 @@ export function formatNestedAggregate(children: NestedRunSummary[] | undefined):
38
40
  counts.running > 0 ? `${counts.running} running` : "",
39
41
  counts.paused > 0 ? `${counts.paused} paused` : "",
40
42
  counts.failed > 0 ? `${counts.failed} failed` : "",
43
+ counts.partial > 0 ? `${counts.partial} partial` : "",
41
44
  counts.rejected > 0 ? `${counts.rejected} rejected` : "",
42
45
  counts.stopped > 0 ? `${counts.stopped} stopped` : "",
43
46
  counts.complete > 0 ? `${counts.complete} complete` : "",
@@ -47,6 +50,7 @@ export function formatNestedAggregate(children: NestedRunSummary[] | undefined):
47
50
  }
48
51
 
49
52
  function nestedRunLabel(run: NestedRunSummary): string {
53
+ if (run.sessionName?.trim()) return run.sessionName.trim();
50
54
  if (run.agent) return run.agent;
51
55
  if (run.agents?.length) return run.agents.length === 1 ? run.agents[0]! : `${run.agents.slice(0, 2).join(", ")}${run.agents.length > 2 ? ` +${run.agents.length - 2}` : ""}`;
52
56
  return run.id;
@@ -103,7 +107,7 @@ function formatNestedRunLines(children: NestedRunSummary[] | undefined, options:
103
107
  if (lines.length >= options.maxLines) return;
104
108
  const stepActivity = step.status === "running" ? formatNestedActivity(step) : undefined;
105
109
  const stepModelThinking = formatModelThinking(step.model, step.thinking);
106
- lines.push(`${indent} ${stepIndex + 1}. ${step.agent} ${step.status}${stepModelThinking ? ` | ${stepModelThinking}` : ""}${stepActivity ? ` | ${stepActivity}` : ""}${step.error ? ` | error: ${step.error}` : ""}`);
110
+ lines.push(`${indent} ${stepIndex + 1}. ${step.sessionName?.trim() || step.agent} ${step.status}${stepModelThinking ? ` | ${stepModelThinking}` : ""}${stepActivity ? ` | ${stepActivity}` : ""}${step.error ? ` | error: ${step.error}` : ""}`);
107
111
  append(step.children, depth + 1, `${indent} `);
108
112
  }
109
113
  append(child.children, depth + 1, `${indent} `);