pi-subagents 0.66.0 → 0.68.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 (172) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/reviewer.md +3 -2
  5. package/docs/agents.md +43 -15
  6. package/docs/configuration.md +67 -23
  7. package/docs/extension-api.md +38 -19
  8. package/docs/missions.md +10 -2
  9. package/docs/models.md +11 -79
  10. package/docs/observability.md +22 -12
  11. package/docs/standalone-background.md +59 -0
  12. package/docs/tool-reference.md +33 -20
  13. package/docs/watchdog.md +39 -10
  14. package/docs/workflows.md +37 -13
  15. package/index.ts +5 -2
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +4 -2
  18. package/prompts/parallel-review.md +1 -1
  19. package/runner-peer-loader.mjs +24 -0
  20. package/runner-peer-preload.mjs +32 -0
  21. package/skills/pi-subagents/SKILL.md +32 -21
  22. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  23. package/skills/pi-subagents/references/execution-controls.md +11 -9
  24. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  25. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  26. package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
  27. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  28. package/src/agents/advertised-agent-prompt.ts +34 -3
  29. package/src/agents/agent-management.ts +57 -58
  30. package/src/agents/agent-serializer.ts +4 -3
  31. package/src/agents/agents.ts +190 -71
  32. package/src/agents/builtin-names.ts +1 -0
  33. package/src/agents/chain-serializer.ts +5 -0
  34. package/src/agents/runtime-agent-registry.ts +7 -6
  35. package/src/api/delegation.ts +4 -0
  36. package/src/api/preflight.ts +94 -59
  37. package/src/api/required-child-extensions.ts +6 -0
  38. package/src/api/shared-types.ts +2 -0
  39. package/src/extension/config.ts +10 -37
  40. package/src/extension/fanout-child.ts +66 -4
  41. package/src/extension/herdr-pi-bridge.ts +160 -0
  42. package/src/extension/index.ts +62 -39
  43. package/src/extension/public-execution.ts +7 -5
  44. package/src/extension/rpc.ts +4 -0
  45. package/src/extension/schemas.ts +81 -81
  46. package/src/extension/tool-description.ts +30 -82
  47. package/src/inspectors/actions.ts +148 -0
  48. package/src/inspectors/ghostty/actions.ts +74 -0
  49. package/src/inspectors/ghostty/plugin.ts +17 -0
  50. package/src/inspectors/herdr/actions.ts +99 -179
  51. package/src/inspectors/herdr/plugin.ts +20 -0
  52. package/src/inspectors/herdr/project-panes.ts +1 -1
  53. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  54. package/src/inspectors/plugins.ts +8 -0
  55. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  56. package/src/inspectors/types.ts +51 -0
  57. package/src/intercom/intercom-bridge.ts +50 -8
  58. package/src/intercom/native-supervisor-channel.ts +44 -31
  59. package/src/policy/authority.ts +4 -0
  60. package/src/profiles/profiles.ts +12 -6
  61. package/src/runs/background/active-async-capacity.ts +4 -0
  62. package/src/runs/background/active-run-index.ts +17 -1
  63. package/src/runs/background/async-execution.ts +348 -176
  64. package/src/runs/background/async-job-tracker.ts +8 -6
  65. package/src/runs/background/async-resume.ts +17 -12
  66. package/src/runs/background/async-status.ts +15 -4
  67. package/src/runs/background/auto-drain.ts +23 -10
  68. package/src/runs/background/binary-bootstrap.ts +38 -0
  69. package/src/runs/background/chain-append.ts +1 -1
  70. package/src/runs/background/chain-root-attachment.ts +14 -33
  71. package/src/runs/background/fleet-view.ts +30 -2
  72. package/src/runs/background/notify.ts +105 -7
  73. package/src/runs/background/owned-process-tree.ts +29 -2
  74. package/src/runs/background/result-files.ts +8 -4
  75. package/src/runs/background/result-watcher.ts +19 -2
  76. package/src/runs/background/run-child-session.ts +81 -33
  77. package/src/runs/background/run-status.ts +3 -0
  78. package/src/runs/background/runner-aliases.ts +12 -33
  79. package/src/runs/background/runner-child-launch.ts +6 -1
  80. package/src/runs/background/runner-child-sessions.ts +5 -4
  81. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  82. package/src/runs/background/scheduled-runs.ts +51 -18
  83. package/src/runs/background/stale-run-reconciler.ts +35 -11
  84. package/src/runs/background/steering.ts +20 -2
  85. package/src/runs/background/subagent-runner.ts +441 -304
  86. package/src/runs/background/subagent-wait.ts +176 -28
  87. package/src/runs/background/wait-completions.ts +75 -27
  88. package/src/runs/background/wait-subscriptions.ts +9 -3
  89. package/src/runs/background/wait-tool.ts +5 -3
  90. package/src/runs/foreground/async-steering-action.ts +18 -7
  91. package/src/runs/foreground/async-stop-action.ts +93 -3
  92. package/src/runs/foreground/execution.ts +134 -248
  93. package/src/runs/foreground/foreground-history.ts +2 -1
  94. package/src/runs/foreground/prompt-audit.ts +3 -1
  95. package/src/runs/foreground/subagent-executor.ts +374 -178
  96. package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
  97. package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  98. package/src/runs/shared/acceptance.ts +38 -11
  99. package/src/runs/shared/async-status-projection.ts +127 -33
  100. package/src/runs/shared/capability-ceiling.ts +2 -0
  101. package/src/runs/shared/child-hooks.ts +25 -10
  102. package/src/runs/shared/child-launch-plan.ts +15 -3
  103. package/src/runs/shared/child-launch.ts +28 -5
  104. package/src/runs/shared/child-lifecycle.ts +6 -3
  105. package/src/runs/shared/child-runtime-config.ts +8 -1
  106. package/src/runs/shared/child-session.ts +127 -52
  107. package/src/runs/shared/child-tool-plan.ts +142 -11
  108. package/src/runs/shared/completion-guard.ts +5 -3
  109. package/src/runs/shared/dynamic-fanout.ts +2 -2
  110. package/src/runs/shared/effective-system-prompt.ts +33 -0
  111. package/src/runs/shared/external-cli-contract.ts +11 -1
  112. package/src/runs/shared/external-cli-preflight.ts +6 -2
  113. package/src/runs/shared/external-cli-runner.ts +9 -7
  114. package/src/runs/shared/herdr-connection.ts +134 -0
  115. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  116. package/src/runs/shared/herdr-machine.ts +279 -0
  117. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  118. package/src/runs/shared/herdr-placed-run.ts +263 -0
  119. package/src/runs/shared/llm-intent-arbiter.ts +12 -3
  120. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  121. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
  122. package/src/runs/shared/model-scope.ts +1 -1
  123. package/src/runs/shared/nested-events.ts +11 -2
  124. package/src/runs/shared/orca-progress-tabs.ts +1 -1
  125. package/src/runs/shared/parallel-utils.ts +7 -2
  126. package/src/runs/shared/pi-spawn.ts +10 -0
  127. package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
  128. package/src/runs/shared/task-intent.ts +46 -13
  129. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  130. package/src/runs/shared/worktree-setup-command.ts +27 -4
  131. package/src/runs/shared/worktree.ts +45 -15
  132. package/src/shared/child-cache-retention.ts +43 -0
  133. package/src/shared/fork-context.ts +15 -72
  134. package/src/shared/launch-contract.ts +68 -8
  135. package/src/shared/opencode-session-headers.ts +30 -0
  136. package/src/shared/pruned-fork.ts +1 -1
  137. package/src/shared/required-child-extensions.ts +81 -0
  138. package/src/shared/settings.ts +5 -2
  139. package/src/shared/shortcuts.ts +0 -4
  140. package/src/shared/types.ts +74 -30
  141. package/src/slash/delegation-adapters.ts +3 -1
  142. package/src/slash/delegation-request.ts +14 -0
  143. package/src/slash/slash-commands.ts +2 -7
  144. package/src/slash/subagents-admin.ts +24 -13
  145. package/src/tui/fleet-status.ts +164 -19
  146. package/src/tui/fleet.ts +16 -14
  147. package/src/tui/render.ts +168 -37
  148. package/src/watchdog/child-status.ts +28 -28
  149. package/src/watchdog/lsp-diagnostics.ts +1 -1
  150. package/src/watchdog/model-selection.ts +21 -1
  151. package/src/watchdog/permission-arbiter.ts +3 -1
  152. package/src/watchdog/register-child.ts +10 -2
  153. package/src/watchdog/register-main.ts +39 -35
  154. package/src/watchdog/render.ts +1 -1
  155. package/src/watchdog/review.ts +123 -74
  156. package/src/watchdog/rules.ts +1 -1
  157. package/src/watchdog/runtime.ts +100 -27
  158. package/src/watchdog/scope.ts +1 -1
  159. package/src/watchdog/settings.ts +3 -0
  160. package/src/watchdog/tool-actions.ts +13 -12
  161. package/src/watchdog/turn-delta.ts +23 -0
  162. package/src/watchdog/types.ts +5 -3
  163. package/src/watchdog/warning-format.ts +1 -1
  164. package/src/workflows/scripted-workflow.ts +279 -10
  165. package/src/workflows/workflow-checklist.ts +2 -2
  166. package/src/workflows/workflow-receipt.ts +21 -3
  167. package/src/workflows/workflow-resources.ts +13 -2
  168. package/runner-server-preload.mjs +0 -13
  169. package/src/runs/shared/model-exclusions.ts +0 -374
  170. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  171. package/src/runs/shared/readonly-session-evidence.ts +0 -307
  172. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -3,13 +3,18 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
5
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
6
- import type { AsyncStatus, Details, SubagentState, ToolBudgetConfig } from "../../shared/types.ts";
6
+ import type { AsyncStatus, Details, SteerActionResult, SubagentState, ToolBudgetConfig } from "../../shared/types.ts";
7
7
  import { readStatus } from "../../shared/utils.ts";
8
8
  import { 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
11
  import { actionResultFromSteeringStatus, claimSteeringRecovery, createSteeringStatus, recordSteeringRequest, remainingSteeringRecoveryLimits, steeringReceipt, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
12
12
 
13
+ function steerTargetsAccepted(result: Pick<SteerActionResult, "targets"> | undefined): boolean {
14
+ const targets = result?.targets ?? [];
15
+ return targets.length > 0 && targets.every((target) => target.state === "queued" || target.state === "delivered");
16
+ }
17
+
13
18
  export function canQueueRetainedAsyncFollowUp(status: AsyncStatus, index?: number): boolean {
14
19
  const steps = status.steps ?? [];
15
20
  return status.state === "complete"
@@ -170,9 +175,11 @@ export async function steerAsyncRun(input: {
170
175
  }
171
176
  const finalStatus = readStatus(asyncDir);
172
177
  const finalResult = finalStatus?.steering ? actionResultFromSteeringStatus(finalStatus.steering, status.runId, requestId) : undefined;
173
- if (finalResult?.state === "delivered") {
174
- return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering delivered for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: finalResult } };
175
- }
178
+ const acceptedReply = (accepted: SteerActionResult): AgentToolResult<Details> => {
179
+ const acceptedState = accepted.state === "delivered" ? "delivered" : "queued";
180
+ return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering ${acceptedState} for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: accepted } };
181
+ };
182
+ if (finalResult && steerTargetsAccepted(finalResult)) return acceptedReply(finalResult);
176
183
  const running = (finalStatus?.steps ?? status.steps ?? []).filter((step) => step.status === "running");
177
184
  const recoveryAllowed = (input.mode ?? "steer") === "steer" && status.mode === "single" && status.isNested !== true && running.length === 1 && Boolean(finalStatus?.steering) && (input.index === undefined || input.index === 0);
178
185
  if (recoveryAllowed && finalResult?.state !== "scheduled" && input.recover) {
@@ -186,17 +193,21 @@ export async function steerAsyncRun(input: {
186
193
  try {
187
194
  const latest = readStatus(asyncDir);
188
195
  const latestResult = latest?.steering ? actionResultFromSteeringStatus(latest.steering, status.runId, requestId) : undefined;
189
- if (latestResult?.state === "delivered") return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering delivered for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: latestResult } };
196
+ if (latestResult && steerTargetsAccepted(latestResult)) return acceptedReply(latestResult);
190
197
  const committedAt = Date.now();
191
198
  input.onBeforeRecoveryClaim?.(requestId, committedAt);
192
199
  const { claimPath, markerPath } = claimSteeringRecovery(asyncDir, { requestId, sourceRunId: status.runId, committedAt });
193
200
  input.onRecoveryCommitted?.(requestId, committedAt);
194
201
  const preCommitStatus = readStatus(asyncDir);
195
202
  const preCommitResult = preCommitStatus?.steering ? actionResultFromSteeringStatus(preCommitStatus.steering, status.runId, requestId) : undefined;
196
- if (preCommitResult?.state === "delivered" && preCommitResult.targets.every((target) => target.deliveredAt !== undefined && target.deliveredAt <= committedAt)) {
203
+ if (
204
+ preCommitResult
205
+ && preCommitResult.targets.length > 0
206
+ && preCommitResult.targets.every((target) => target.state === "queued" || (target.deliveredAt !== undefined && target.deliveredAt <= committedAt))
207
+ ) {
197
208
  fs.rmSync(markerPath, { force: true });
198
209
  fs.rmSync(claimPath, { force: true });
199
- return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering delivered for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: preCommitResult } };
210
+ return acceptedReply(preCommitResult);
200
211
  }
201
212
  try {
202
213
  deliverInterruptRequest({ asyncDir, source: "steering-recovery" });
@@ -1,7 +1,12 @@
1
+ import * as fs from "node:fs";
1
2
  import * as path from "node:path";
2
3
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
3
- import type { Details, SubagentState } from "../../shared/types.ts";
4
+ import { writeAtomicJson } from "../../shared/atomic-json.ts";
5
+ import { DIRS, type AsyncStatus, type Details, type SubagentState } from "../../shared/types.ts";
6
+ import { updateActiveRunIndex } from "../background/active-run-index.ts";
4
7
  import { deliverStopRequest } from "../background/control-channel.ts";
8
+ import { readProcessTerminal } from "../background/process-terminal.ts";
9
+ import { resultFilePath, resultPayloadPathForSessionRun, writeAsyncResultFile } from "../background/result-files.ts";
5
10
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
6
11
  import { isStoppableAsyncStatusStep, resolveAsyncStatusChild, type ResolvedAsyncStatusChild } from "../shared/child-identity.ts";
7
12
 
@@ -21,6 +26,80 @@ function getAsyncStopTarget(
21
26
  return direct ? { asyncId: direct.asyncId, asyncDir: direct.asyncDir } : undefined;
22
27
  }
23
28
 
29
+ const STOP_MESSAGE = "Subagent stopped by user.";
30
+
31
+ function sealPausedRun(asyncDir: string, status: AsyncStatus): string | undefined {
32
+ const runnerProcessInstanceId = status.processTerminal?.runnerProcessInstanceId;
33
+ if (!runnerProcessInstanceId) return "runner process identity is missing";
34
+ const proof = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId });
35
+ if (proof?.state !== "observed" || proof.runId !== status.runId || proof.runnerProcessInstanceId !== runnerProcessInstanceId) {
36
+ return `process-terminal proof is ${proof?.state === "unknown" ? `unknown (${proof.reason})` : proof?.state ?? "missing"}`;
37
+ }
38
+ if (!status.sessionId) return "session identity is missing";
39
+ const existingResultPath = resultPayloadPathForSessionRun(DIRS.results, status.sessionId, status.runId);
40
+ if (!existingResultPath) return "paused result is missing";
41
+ let existingResult: Record<string, unknown>;
42
+ try {
43
+ const parsed: unknown = JSON.parse(fs.readFileSync(existingResultPath, "utf-8"));
44
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("result is not an object");
45
+ existingResult = parsed as Record<string, unknown>;
46
+ } catch (error) {
47
+ return `paused result is unavailable: ${error instanceof Error ? error.message : String(error)}`;
48
+ }
49
+ const resultRunId = typeof existingResult.runId === "string" ? existingResult.runId : existingResult.id;
50
+ if (resultRunId !== status.runId || existingResult.sessionId !== status.sessionId) {
51
+ return "paused result identity does not match the run";
52
+ }
53
+
54
+ const now = Date.now();
55
+ const steps = (status.steps ?? []).map((step) => {
56
+ if (step.status !== "pending" && step.status !== "running" && step.status !== "paused") return step;
57
+ const { activityState: _activityState, ...rest } = step;
58
+ return {
59
+ ...rest,
60
+ status: "stopped" as const,
61
+ error: STOP_MESSAGE,
62
+ exitCode: 1,
63
+ stopped: true,
64
+ endedAt: now,
65
+ durationMs: step.startedAt ? now - step.startedAt : 0,
66
+ lastActivityAt: now,
67
+ };
68
+ });
69
+ const results = Array.isArray(existingResult.results)
70
+ ? existingResult.results.map((entry, index) => {
71
+ const stepState = status.steps?.[index]?.status;
72
+ if ((stepState !== "pending" && stepState !== "running" && stepState !== "paused") || !entry || typeof entry !== "object" || Array.isArray(entry)) return entry;
73
+ const { interrupted: _interrupted, ...result } = entry as Record<string, unknown>;
74
+ return { ...result, output: STOP_MESSAGE, error: STOP_MESSAGE, success: false, exitCode: 1, stopped: true };
75
+ })
76
+ : existingResult.results;
77
+ const stoppedResult = {
78
+ ...existingResult,
79
+ success: false,
80
+ state: "stopped",
81
+ summary: STOP_MESSAGE,
82
+ error: STOP_MESSAGE,
83
+ stopped: true,
84
+ exitCode: 1,
85
+ timestamp: now,
86
+ ...(Array.isArray(existingResult.results) ? { results } : {}),
87
+ };
88
+ const stoppedStatus: AsyncStatus = {
89
+ ...status,
90
+ state: "stopped",
91
+ stopped: true,
92
+ error: STOP_MESSAGE,
93
+ endedAt: now,
94
+ lastUpdate: now,
95
+ steps,
96
+ };
97
+ delete stoppedStatus.activityState;
98
+ writeAsyncResultFile(resultFilePath(DIRS.results, status.runId), stoppedResult);
99
+ writeAtomicJson(path.join(asyncDir, "status.json"), stoppedStatus);
100
+ updateActiveRunIndex(asyncDir, "stopped", status.toolCallId, { retryCapacityErrors: true, terminalIndexBeforeRelease: true });
101
+ }
102
+
24
103
  export function stopAsyncRun(
25
104
  state: SubagentState,
26
105
  runId: string | undefined,
@@ -38,7 +117,8 @@ export function stopAsyncRun(
38
117
  details: { mode: "management", results: [] },
39
118
  };
40
119
  }
41
- if (!status || (status.state !== "running" && status.state !== "queued")) {
120
+ const pausedWholeRun = status?.state === "paused" && childId === undefined;
121
+ if (!status || (status.state !== "running" && status.state !== "queued" && !pausedWholeRun)) {
42
122
  return {
43
123
  content: [{ type: "text", text: `No running or queued async run was found for '${runId ?? "current"}'.` }],
44
124
  isError: true,
@@ -66,13 +146,23 @@ export function stopAsyncRun(
66
146
  }
67
147
  try {
68
148
  deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action", targetIndex: child?.index, childId: child?.id ?? childId });
149
+ if (pausedWholeRun) {
150
+ const failure = sealPausedRun(target.asyncDir, status);
151
+ if (failure) {
152
+ return {
153
+ content: [{ type: "text", text: `Stop request persisted for paused async run ${target.asyncId}, but terminal proof is not ready (${failure}). Retry stop after runner shutdown is observed.` }],
154
+ isError: true,
155
+ details: { mode: "management", results: [] },
156
+ };
157
+ }
158
+ }
69
159
  const tracked = state.asyncJobs.get(target.asyncId);
70
160
  if (tracked) {
71
161
  tracked.activityState = undefined;
72
162
  tracked.updatedAt = Date.now();
73
163
  }
74
164
  return {
75
- content: [{ type: "text", text: child ? `Stop requested for child ${child.id} in async run ${target.asyncId}.` : `Stop requested for async run ${target.asyncId}.` }],
165
+ content: [{ type: "text", text: pausedWholeRun ? `Stopped paused async run ${target.asyncId}.` : child ? `Stop requested for child ${child.id} in async run ${target.asyncId}.` : `Stop requested for async run ${target.asyncId}.` }],
76
166
  details: { mode: "management", results: [] },
77
167
  };
78
168
  } catch (error) {