pi-subagents 0.42.0 → 0.43.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 (55) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +3 -5
  3. package/package.json +1 -1
  4. package/skills/pi-subagents/SKILL.md +2 -2
  5. package/skills/pi-subagents/references/constraints-and-recipes.md +35 -32
  6. package/skills/pi-subagents/references/execution-controls.md +27 -43
  7. package/skills/pi-subagents/references/management-authoring-rpc.md +20 -3
  8. package/skills/pi-subagents/references/prompting-and-roles.md +16 -49
  9. package/src/agents/agent-refinements.ts +624 -0
  10. package/src/agents/agents.ts +0 -2
  11. package/src/agents/proactive-skills.ts +1 -1
  12. package/src/api/delegation.ts +1 -2
  13. package/src/extension/control-notices.ts +2 -2
  14. package/src/extension/fanout-child.ts +46 -22
  15. package/src/extension/index.ts +29 -12
  16. package/src/extension/public-execution.ts +71 -0
  17. package/src/extension/rpc.ts +7 -8
  18. package/src/extension/schemas.ts +17 -17
  19. package/src/extension/tool-description.ts +14 -14
  20. package/src/missions/actions.ts +47 -11
  21. package/src/missions/goal-driver.ts +162 -0
  22. package/src/missions/lifecycle.ts +44 -12
  23. package/src/missions/store.ts +68 -3
  24. package/src/missions/types.ts +25 -3
  25. package/src/missions/workflow-state.ts +77 -0
  26. package/src/profiles/profiles.ts +1 -3
  27. package/src/runs/background/async-execution.ts +3 -0
  28. package/src/runs/background/async-job-tracker.ts +2 -17
  29. package/src/runs/background/control-channel.ts +50 -6
  30. package/src/runs/background/retained-children.ts +68 -0
  31. package/src/runs/background/scheduled-runs.ts +17 -13
  32. package/src/runs/background/steering.ts +7 -5
  33. package/src/runs/background/subagent-runner.ts +10 -4
  34. package/src/runs/foreground/async-steering-action.ts +36 -10
  35. package/src/runs/foreground/chain-clarify.ts +13 -16
  36. package/src/runs/foreground/execution.ts +4 -0
  37. package/src/runs/foreground/subagent-executor.ts +200 -59
  38. package/src/runs/shared/acceptance.ts +154 -9
  39. package/src/runs/shared/subagent-prompt-runtime.ts +94 -25
  40. package/src/shared/types.ts +17 -3
  41. package/src/slash/delegation-adapters.ts +0 -13
  42. package/src/slash/prompt-template-bridge.ts +14 -12
  43. package/src/slash/prompt-workflows.ts +10 -4
  44. package/src/slash/slash-bridge.ts +8 -6
  45. package/src/slash/slash-commands.ts +27 -12
  46. package/src/slash/slash-live-state.ts +4 -2
  47. package/src/tui/fleet-status.ts +8 -4
  48. package/src/tui/fleet.ts +15 -5
  49. package/src/tui/render.ts +13 -31
  50. package/src/workflows/chat-progress.ts +2 -2
  51. package/src/workflows/scripted-workflow.ts +97 -10
  52. package/agents/context-builder.md +0 -46
  53. package/agents/planner.md +0 -56
  54. package/prompts/parallel-context-build.md +0 -55
  55. package/prompts/parallel-handoff-plan.md +0 -61
@@ -5,15 +5,16 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
5
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
6
6
  import type { AsyncStatus, Details, SubagentState, ToolBudgetConfig, TurnBudgetConfig } from "../../shared/types.ts";
7
7
  import { readStatus } from "../../shared/utils.ts";
8
- import { consumeSteerAcks, deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
8
+ import { consumeSteerAcks, deliverInterruptRequest, queueRevivalBrief, requestAsyncSteer, type SteerDeliveryMode, type SteerRequest } from "../background/control-channel.ts";
9
9
  import { resolveAsyncResumeTarget } from "../background/async-resume.ts";
10
10
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
11
- import { actionResultFromSteeringStatus, claimSteeringRecovery, remainingSteeringRecoveryLimits, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
11
+ import { actionResultFromSteeringStatus, claimSteeringRecovery, createSteeringStatus, recordSteeringRequest, remainingSteeringRecoveryLimits, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
12
12
 
13
13
  export async function steerAsyncRun(input: {
14
14
  state: SubagentState;
15
15
  runId: string;
16
16
  message: string;
17
+ mode?: SteerDeliveryMode;
17
18
  index?: number;
18
19
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
19
20
  location: { asyncDir: string | null };
@@ -41,14 +42,38 @@ export async function steerAsyncRun(input: {
41
42
  details: { mode: "management", results: [] },
42
43
  };
43
44
  }
44
- if (!status || (status.state !== "running" && status.state !== "queued")) {
45
+ if (!status) {
45
46
  return {
46
- content: [{ type: "text", text: `Async run '${input.runId}' is not running or queued and cannot be steered.` }],
47
+ content: [{ type: "text", text: `Async run '${input.runId}' was not found.` }],
47
48
  isError: true,
48
49
  details: { mode: "management", results: [] },
49
50
  };
50
51
  }
51
52
  const steps = status.steps ?? [];
53
+ if (status.state !== "running" && status.state !== "queued") {
54
+ const retained = input.mode === "follow_up" && status.state === "complete" && Boolean(status.parentWorkflowRunId) && steps.length === 1 && (steps[0]?.status === "complete" || steps[0]?.status === "completed") && Boolean(steps[0]?.sessionFile ?? status.sessionFile);
55
+ if (!retained) {
56
+ return {
57
+ content: [{ type: "text", text: `Async run '${input.runId}' is not running or queued and cannot be steered.` }],
58
+ isError: true,
59
+ details: { mode: "management", results: [] },
60
+ };
61
+ }
62
+ if (input.index !== undefined && input.index !== 0) {
63
+ return { content: [{ type: "text", text: `Retained async run '${status.runId}' has one child. Index ${input.index} is out of range.` }], isError: true, details: { mode: "management", results: [] } };
64
+ }
65
+ const request: SteerRequest = { type: "steer", id: randomUUID(), ts: Date.now(), message: input.message.trim(), mode: "follow_up", targetIndex: 0, source: "steer-action" };
66
+ try {
67
+ queueRevivalBrief(asyncDir, request);
68
+ } catch (error) {
69
+ return { content: [{ type: "text", text: `Failed to queue retained follow-up for async run ${status.runId}: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "management", results: [] } };
70
+ }
71
+ status.steering ??= createSteeringStatus();
72
+ recordSteeringRequest(status.steering, { id: request.id, requestedAt: request.ts, source: request.source, message: request.message, targets: [{ index: 0, state: "scheduled" }] });
73
+ writeAtomicJson(path.join(asyncDir, "status.json"), status);
74
+ const queued = { requestId: request.id, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: status.runId, targets: [{ index: 0, state: "scheduled" as const }] };
75
+ return { content: [{ type: "text", text: `Follow-up queued for the next resume of retained async run ${status.runId} (request ${request.id}).` }], details: { mode: "management", results: [], steering: queued } };
76
+ }
52
77
  if (input.index !== undefined) {
53
78
  if (input.index < 0 || input.index >= steps.length) {
54
79
  return {
@@ -92,6 +117,7 @@ export async function steerAsyncRun(input: {
92
117
  try {
93
118
  requestPath = requestAsyncSteer(asyncDir, {
94
119
  message: input.message,
120
+ mode: input.mode,
95
121
  ...(effectiveTargetIndex !== undefined ? { targetIndex: effectiveTargetIndex } : { targetIndexes }),
96
122
  source: "steer-action",
97
123
  id: requestId,
@@ -108,11 +134,11 @@ export async function steerAsyncRun(input: {
108
134
  if (tracked) tracked.updatedAt = Date.now();
109
135
  const targets = targetIndexes.map((index) => ({ index, state: status.steps?.[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
110
136
  if (targets.every((target) => target.state === "scheduled")) {
111
- const scheduled = { requestId, state: "scheduled" as const, sourceRunId: status.runId, targets };
137
+ const scheduled = { requestId, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: status.runId, targets };
112
138
  return { content: [{ type: "text", text: `Steering scheduled for async run ${status.runId} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
113
139
  }
114
140
  const waited = await waitForSteeringAction({ asyncDir, sourceRunId: status.runId, requestId, timeoutMs: input.ackTimeoutMs ?? 3_000, signal: input.signal });
115
- const result = waited ?? { requestId, state: "pending" as const, sourceRunId: status.runId, targets };
141
+ const result = waited ?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: status.runId, targets };
116
142
  if (input.signal?.aborted) {
117
143
  return { content: [{ type: "text", text: `Steering pending for async run ${status.runId} (request ${requestId}); caller aborted before recovery.` }], details: { mode: "management", results: [], steering: result } };
118
144
  }
@@ -122,7 +148,7 @@ export async function steerAsyncRun(input: {
122
148
  return { content: [{ type: "text", text: `Steering delivered for async run ${status.runId} (request ${requestId}).` }], details: { mode: "management", results: [], steering: finalResult } };
123
149
  }
124
150
  const running = (finalStatus?.steps ?? status.steps ?? []).filter((step) => step.status === "running");
125
- const recoveryAllowed = status.mode === "single" && status.isNested !== true && running.length === 1 && Boolean(finalStatus?.steering) && (input.index === undefined || input.index === 0);
151
+ const recoveryAllowed = (input.mode ?? "steer") === "steer" && status.mode === "single" && status.isNested !== true && running.length === 1 && Boolean(finalStatus?.steering) && (input.index === undefined || input.index === 0);
126
152
  if (recoveryAllowed && finalResult?.state !== "scheduled" && input.recover) {
127
153
  const appendSteeringNotice = (state: "failed" | "recovered", message: string): void => {
128
154
  try {
@@ -203,7 +229,7 @@ export async function steerAsyncRun(input: {
203
229
  }
204
230
  const recovered = sourceStatus?.steering ? actionResultFromSteeringStatus(sourceStatus.steering, status.runId, requestId, revived.details.asyncId) : undefined;
205
231
  appendSteeringNotice("recovered", `Steering recovered for run ${status.runId}; replacement ${revived.details.asyncId} launched.`);
206
- return { content: [{ type: "text", text: `Steering recovered for async run ${status.runId}; replacement ${revived.details.asyncId} launched after the source paused.` }], details: { mode: "management", results: [], steering: recovered ?? { requestId, state: "recovered", sourceRunId: status.runId, replacementRunId: revived.details.asyncId, targets: [{ index: input.index ?? 0, state: "recovered" }] } } };
232
+ return { content: [{ type: "text", text: `Steering recovered for async run ${status.runId}; replacement ${revived.details.asyncId} launched after the source paused.` }], details: { mode: "management", results: [], steering: recovered ?? { requestId, state: "recovered", sourceRunId: status.runId, replacementRunId: revived.details.asyncId, deliveryStatus: "delivered", targets: [{ index: input.index ?? 0, state: "recovered" }] } } };
207
233
  } catch (error) {
208
234
  const reason = error instanceof Error ? error.message : String(error);
209
235
  const failedStatus = readStatus(asyncDir);
@@ -217,10 +243,10 @@ export async function steerAsyncRun(input: {
217
243
  }
218
244
  const failed = failedStatus?.steering ? actionResultFromSteeringStatus(failedStatus.steering, status.runId, requestId) : undefined;
219
245
  appendSteeringNotice("failed", `Steering failed for run ${status.runId}: ${reason}`);
220
- return { content: [{ type: "text", text: `Steering failed for async run ${status.runId} (request ${requestId}): ${reason}` }], isError: true, details: { mode: "management", results: [], steering: failed ?? { requestId, state: "failed", sourceRunId: status.runId, targets: [{ index: input.index ?? 0, state: "failed", reason }] } } };
246
+ return { content: [{ type: "text", text: `Steering failed for async run ${status.runId} (request ${requestId}): ${reason}` }], isError: true, details: { mode: "management", results: [], steering: failed ?? { requestId, state: "failed", sourceRunId: status.runId, deliveryStatus: "queued", targets: [{ index: input.index ?? 0, state: "failed", reason }] } } };
221
247
  }
222
248
  }
223
- const stateText = result.state === "delivered" ? "delivered" : result.state === "scheduled" ? "scheduled" : result.state === "recovered" ? "recovered" : result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : "pending";
249
+ const stateText = result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : result.deliveryStatus === "queued" ? "queued" : result.state === "delivered" ? "delivered" : result.state === "scheduled" ? "scheduled" : result.state === "recovered" ? "recovered" : "pending";
224
250
  const isError = result.state === "failed" || result.state === "partial";
225
251
  return {
226
252
  content: [{ type: "text", text: `Steering ${stateText} for async run ${status.runId} (request ${requestId}).` }],
@@ -882,25 +882,22 @@ export class ChainClarifyComponent implements Component {
882
882
  }
883
883
  }
884
884
 
885
- render(_width: number): string[] {
885
+ render(width: number): string[] {
886
+ let lines: string[] = [];
886
887
  if (this.editingStep !== null) {
887
- if (this.editMode === "model") {
888
- return this.renderModelSelector();
889
- }
890
- if (this.editMode === "thinking") {
891
- return this.renderThinkingSelector();
892
- }
893
- if (this.editMode === "skills") {
894
- return this.renderSkillSelector();
888
+ if (this.editMode === "model") lines = this.renderModelSelector();
889
+ else if (this.editMode === "thinking") lines = this.renderThinkingSelector();
890
+ else if (this.editMode === "skills") lines = this.renderSkillSelector();
891
+ else lines = this.renderFullEditMode();
892
+ } else {
893
+ switch (this.mode) {
894
+ case 'single': lines = this.renderSingleMode(); break;
895
+ case 'parallel': lines = this.renderParallelMode(); break;
896
+ case 'chain': lines = this.renderChainMode(); break;
895
897
  }
896
- return this.renderFullEditMode();
897
- }
898
- // Mode-based navigation rendering
899
- switch (this.mode) {
900
- case 'single': return this.renderSingleMode();
901
- case 'parallel': return this.renderParallelMode();
902
- case 'chain': return this.renderChainMode();
903
898
  }
899
+ const renderWidth = Math.max(0, Math.min(this.width, Math.floor(width)));
900
+ return lines.map((line) => truncateToWidth(line, renderWidth));
904
901
  }
905
902
 
906
903
  /** Render the model selector view */
@@ -7,6 +7,7 @@ import { existsSync, unlinkSync } from "node:fs";
7
7
  import * as path from "node:path";
8
8
  import type { Message } from "@earendil-works/pi-ai";
9
9
  import type { AgentConfig } from "../../agents/agents.ts";
10
+ import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
10
11
  import {
11
12
  ensureArtifactsDir,
12
13
  formatOutputArtifactContent,
@@ -1438,6 +1439,7 @@ async function runSyncCompletion(
1438
1439
  if (memoryInjection) {
1439
1440
  systemPrompt = systemPrompt ? `${systemPrompt}\n\n${memoryInjection}` : memoryInjection;
1440
1441
  }
1442
+ systemPrompt = appendAgentRefinementOverlay(systemPrompt, { cwd: skillCwd, agentName });
1441
1443
  systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);
1442
1444
 
1443
1445
  const candidates = buildModelCandidates(
@@ -1699,6 +1701,8 @@ async function runSyncCompletion(
1699
1701
  : undefined,
1700
1702
  cwd: options.cwd ?? runtimeCwd,
1701
1703
  reportOptional: isAgentContractV1(options.agentContract),
1704
+ artifactsDir: options.artifactsDir,
1705
+ runId: options.runId,
1702
1706
  });
1703
1707
  }
1704
1708
  } catch (error) {