pi-subagents 0.59.0 → 0.60.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 (43) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/docs/tool-reference.md +4 -1
  3. package/docs/workflows.md +2 -2
  4. package/package.json +1 -1
  5. package/skills/council-mode/SKILL.md +48 -243
  6. package/skills/council-mode/references/pass-contracts.md +150 -0
  7. package/skills/pi-subagents/SKILL.md +87 -37
  8. package/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
  9. package/skills/pi-subagents/references/execution-controls.md +47 -6
  10. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  11. package/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
  12. package/skills/pi-subagents/references/prompting-and-roles.md +34 -27
  13. package/skills/pi-subagents/references/review-and-validation.md +73 -0
  14. package/src/agents/agent-management.ts +154 -25
  15. package/src/api/shared-types.ts +2 -0
  16. package/src/extension/public-execution.ts +1 -0
  17. package/src/extension/schemas.ts +1 -0
  18. package/src/extension/tool-description.ts +8 -4
  19. package/src/runs/background/async-execution.ts +2 -2
  20. package/src/runs/background/async-job-tracker.ts +3 -0
  21. package/src/runs/background/async-status.ts +45 -2
  22. package/src/runs/background/control-channel.ts +3 -2
  23. package/src/runs/background/run-status.ts +13 -2
  24. package/src/runs/background/subagent-runner.ts +5 -1
  25. package/src/runs/background/subagent-wait.ts +10 -2
  26. package/src/runs/background/wait-completions.ts +3 -0
  27. package/src/runs/foreground/execution.ts +11 -2
  28. package/src/runs/foreground/subagent-executor.ts +98 -1
  29. package/src/runs/shared/async-status-projection.ts +138 -4
  30. package/src/runs/shared/background-process-options.ts +9 -0
  31. package/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
  32. package/src/runs/shared/mutation-evidence.ts +52 -3
  33. package/src/runs/shared/pi-args.ts +47 -1
  34. package/src/runs/shared/single-output.ts +45 -18
  35. package/src/runs/shared/subagent-prompt-runtime.ts +20 -2
  36. package/src/runs/shared/workflow-graph.ts +15 -0
  37. package/src/shared/types.ts +34 -1
  38. package/src/tui/fleet-status.ts +11 -3
  39. package/src/tui/render-helpers.ts +31 -0
  40. package/src/tui/render.ts +597 -112
  41. package/src/watchdog/change-signature.ts +40 -1
  42. package/src/workflows/host-command.ts +6 -1
  43. package/src/workflows/scripted-workflow.ts +53 -2
@@ -559,6 +559,10 @@ export interface TimeoutRecoverySummary {
559
559
  termination: "timed-out" | "stopped";
560
560
  changedFiles: string[];
561
561
  truncated?: boolean;
562
+ /** True only when a timed-out child left tracked changes without its requested report. */
563
+ recoveryNeeded?: boolean;
564
+ reason?: "timed-out-with-dirty-worktree";
565
+ reportStatus?: "missing" | "written" | "not-requested" | "unknown";
562
566
  currentTool?: string;
563
567
  currentToolArgs?: string;
564
568
  currentPath?: string;
@@ -569,6 +573,9 @@ export interface TimeoutRecoverySummary {
569
573
  message: string;
570
574
  }
571
575
 
576
+ /** Safe parent-facing subset of a timeout recovery summary. */
577
+ export type TimeoutRecoveryProjection = Pick<TimeoutRecoverySummary, "termination" | "changedFiles" | "truncated" | "recoveryNeeded" | "reason" | "reportStatus">;
578
+
572
579
  export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 3;
573
580
  export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
574
581
 
@@ -1272,6 +1279,7 @@ export interface WaitCompletionChild {
1272
1279
  model?: string;
1273
1280
  contextOverflow?: boolean;
1274
1281
  artifactPaths?: Partial<ArtifactPaths>;
1282
+ timeoutRecovery?: TimeoutRecoveryProjection;
1275
1283
  }
1276
1284
 
1277
1285
  /**
@@ -1290,6 +1298,27 @@ export interface WaitCompletion {
1290
1298
  workflowChildren?: WorkflowChildSummaryV1;
1291
1299
  }
1292
1300
 
1301
+ export interface AgentCapabilitiesSnapshot {
1302
+ agents: AgentCapabilityRow[];
1303
+ restrictedCount: number;
1304
+ capabilityCeilingSources?: string[];
1305
+ }
1306
+
1307
+ export interface AgentCapabilityRow {
1308
+ name: string;
1309
+ description: string;
1310
+ source: "builtin" | "package" | "user" | "project" | "runtime";
1311
+ executable: boolean;
1312
+ restrictionSources?: string[];
1313
+ aliases?: string[];
1314
+ runner: { type: "pi" } | { type: "external-cli"; adapter?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
1315
+ tools: { ambient: boolean; names: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
1316
+ model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
1317
+ execution?: { defaultAsync?: boolean; timeoutMs?: number };
1318
+ output?: { path?: string; mode?: OutputMode };
1319
+ extensions?: { names?: string[]; subagentOnly?: string[]; skills?: string[] };
1320
+ }
1321
+
1293
1322
  export interface Details {
1294
1323
  mode: SubagentResultMode | "management";
1295
1324
  runId?: string;
@@ -1352,6 +1381,7 @@ export interface Details {
1352
1381
  runFanoutBudget?: RunFanoutBudgetSnapshot;
1353
1382
  runFanoutRejection?: RunFanoutRejection;
1354
1383
  activeAsyncCapacity?: ActiveAsyncCapacitySnapshot;
1384
+ agentCapabilities?: AgentCapabilitiesSnapshot;
1355
1385
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1356
1386
  capabilityAudit?: SubagentCapabilityAudit;
1357
1387
  parallelHandoff?: ParallelHandoffReference;
@@ -1852,9 +1882,10 @@ export interface AsyncStatus {
1852
1882
  parallelHandoff?: ParallelHandoffReference;
1853
1883
  }
1854
1884
 
1855
- export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
1885
+ export type AsyncJobStep = Omit<NonNullable<AsyncStatus["steps"]>[number], "timeoutRecovery"> & {
1856
1886
  index?: number;
1857
1887
  description?: string;
1888
+ timeoutRecovery?: TimeoutRecoveryProjection;
1858
1889
  };
1859
1890
 
1860
1891
  export interface AsyncJobState {
@@ -1889,6 +1920,8 @@ export interface AsyncJobState {
1889
1920
  parallelGroups?: AsyncParallelGroupStatus[];
1890
1921
  /** Bounded host-owned CI/gate nodes loaded from the workflow status graph. */
1891
1922
  hostSteps?: HostStepNodeV1[];
1923
+ /** Full bounded workflow plan, including not-yet-materialized stages. */
1924
+ workflowGraph?: WorkflowGraphSnapshot;
1892
1925
  steps?: AsyncJobStep[];
1893
1926
  preflight?: WorkflowPreflightV1;
1894
1927
  stepsTotal?: number;
@@ -4,6 +4,7 @@ import { snapshotExternalRuns } from "../api/external-runs.ts";
4
4
  import { formatModelThinking } from "../shared/formatters.ts";
5
5
  import type { AsyncJobState, AsyncJobStep, FleetViewPlacement, HerdrProjectPaneSnapshot, HostStepState, HostStepVerdict, NestedRunSummary, NestedStepSummary, SubagentState } from "../shared/types.ts";
6
6
  import { projectAsyncWorkflowRows, type AsyncStatusWorkflowRow } from "../runs/shared/async-status-projection.ts";
7
+ import { contextModeLabel } from "../runs/shared/context-mode.ts";
7
8
  import { formatWorkflowJsonPreview } from "../workflows/scripted-workflow.ts";
8
9
  import { hostStepReportName, hostStepVerdictLabel } from "../runs/shared/host-step-status.ts";
9
10
  import { isStaleExtensionContextError } from "../shared/extension-context.ts";
@@ -362,7 +363,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
362
363
  if (job.mode === "workflow") {
363
364
  const latestEmit = job.workflow?.emits?.length ? formatWorkflowJsonPreview(job.workflow.emits.at(-1), 120) : undefined;
364
365
  const workflowSteps = workflowStepsWithoutMaterializedChildren(job.steps, materializedChildrenByWorkflow.get(`async:${job.asyncId}`));
365
- const workflowRows = projectAsyncWorkflowRows(workflowSteps, job.hostSteps, job.preflight);
366
+ const workflowRows = projectAsyncWorkflowRows(workflowSteps, job.workflowGraph ?? job.hostSteps, job.preflight);
366
367
  entries.push({
367
368
  key: `async:${job.asyncId}`,
368
369
  ...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
@@ -550,7 +551,12 @@ export class SubagentFleetStatus {
550
551
  this.lastRenderKey = renderKey;
551
552
  return;
552
553
  }
553
- if (renderKey === this.lastRenderKey) return;
554
+ if (renderKey === this.lastRenderKey) {
555
+ // Repaint anyway while anything is running so the wall-clock
556
+ // spinner animates between state changes (500ms tick).
557
+ if (this.entries.some((entry) => entry.state === "running")) this.tui?.requestRender();
558
+ return;
559
+ }
554
560
  this.lastRenderKey = renderKey;
555
561
  this.tui?.requestRender();
556
562
  }
@@ -728,6 +734,7 @@ export class SubagentFleetStatus {
728
734
  const marker = last ? "└─" : "├─";
729
735
  const indent = " ";
730
736
  if (row.overflow !== undefined) return truncateToWidth(`${indent}${marker} ${theme.fg("dim", `+${row.overflow} hidden workflow steps`)}`, width);
737
+ const context = contextModeLabel(row.context);
731
738
  const modelThinking = row.modelThinking ? ` (${row.modelThinking})` : "";
732
739
  const activity = row.activity ? ` · ${row.activity}` : "";
733
740
  const kind = row.kind ? `${row.kind}: ` : "";
@@ -738,7 +745,7 @@ export class SubagentFleetStatus {
738
745
  row.preflight.expectedOutput ? `expected:${row.preflight.expectedOutput}` : undefined,
739
746
  row.preflight.independence ? `independence:${row.preflight.independence}` : undefined,
740
747
  ].filter((value): value is string => Boolean(value)).join(" · ") : "";
741
- const left = `${indent}${marker} ${this.workflowRowGlyph(row, theme)} ${theme.fg("muted", `${kind}${row.name}${modelThinking}`)} · ${this.workflowRowStateLabel(row, theme)}${activity}${hints ? ` · ${hints}` : ""}`;
748
+ const left = `${indent}${marker} ${this.workflowRowGlyph(row, theme)} ${theme.fg("muted", `${kind}${row.name}${context ? ` ${context}` : ""}${modelThinking}`)} · ${this.workflowRowStateLabel(row, theme)}${activity}${hints ? ` · ${hints}` : ""}`;
742
749
  const details = [
743
750
  row.startedAt !== undefined ? formatFleetElapsed(Date.now() - row.startedAt) : undefined,
744
751
  row.tokens !== undefined ? formatFleetTokens(row.tokens, row.window) : undefined,
@@ -806,6 +813,7 @@ export class SubagentFleetStatus {
806
813
  row.kind,
807
814
  row.name,
808
815
  row.state,
816
+ row.context,
809
817
  row.modelThinking,
810
818
  row.activity,
811
819
  row.startedAt,
@@ -30,6 +30,37 @@ export function fuzzyFilter<T extends { name: string; description: string; model
30
30
  .map((x) => x.item);
31
31
  }
32
32
 
33
+ /** Remove a repeated job name from a child label when a safe separator follows it. */
34
+ export function stripRepeatedAgentPrefix(label: string, jobName: string | undefined): string {
35
+ const value = label.trim();
36
+ const prefix = jobName?.trim();
37
+ if (!prefix || !value.startsWith(prefix)) return value;
38
+
39
+ const suffix = value.slice(prefix.length);
40
+ if (!suffix || !/^(?::|·|\s)/u.test(suffix)) return value;
41
+ return suffix.replace(/^\s*(?::|·)?\s*/u, "").trim();
42
+ }
43
+
44
+ /** Whether a status label may omit the one logical step marker. */
45
+ export function shouldSuppressSingleStep(chainStepCount?: number, stepsTotal?: number): boolean {
46
+ return (chainStepCount ?? stepsTotal) === 1;
47
+ }
48
+
49
+ /** Add an Agent fraction only when visible candidates collide. */
50
+ export function withDuplicateLabelDiscriminators<T extends { index: number; displayName: string }>(
51
+ rows: readonly T[],
52
+ total: number,
53
+ ): Array<T & { rowLabel: string }> {
54
+ const counts = new Map<string, number>();
55
+ for (const row of rows) counts.set(row.displayName, (counts.get(row.displayName) ?? 0) + 1);
56
+ return rows.map((row) => ({
57
+ ...row,
58
+ rowLabel: (counts.get(row.displayName) ?? 0) > 1
59
+ ? `Agent ${row.index + 1}/${total}: ${row.displayName}`
60
+ : row.displayName,
61
+ }));
62
+ }
63
+
33
64
  export function pad(s: string, len: number): string {
34
65
  const vis = visibleWidth(s);
35
66
  return s + " ".repeat(Math.max(0, len - vis));