pi-subagents 0.58.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.
- package/CHANGELOG.md +65 -0
- package/docs/agents.md +5 -3
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +1 -1
- package/docs/models.md +24 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +89 -4
- package/docs/workflows.md +93 -2
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +1 -1
- package/skills/pi-subagents/SKILL.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +5 -1
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +24 -3
- package/src/agents/agent-management.ts +5 -17
- package/src/agents/agent-serializer.ts +4 -2
- package/src/agents/agents.ts +67 -26
- package/src/agents/runtime-agent-registry.ts +9 -16
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +6 -9
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +51 -23
- package/src/extension/public-execution.ts +15 -2
- package/src/extension/schemas.ts +35 -10
- package/src/extension/tool-description.ts +18 -3
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +50 -35
- package/src/runs/background/async-job-tracker.ts +14 -12
- package/src/runs/background/async-resume.ts +23 -25
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +66 -7
- package/src/runs/background/chain-append.ts +6 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +158 -6
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +2 -0
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +18 -8
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +346 -357
- package/src/runs/background/subagent-wait.ts +58 -10
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +22 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +114 -120
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +478 -239
- package/src/runs/foreground/workflow-detach-reconcile.ts +99 -200
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +463 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/mcp-config-sources.ts +42 -6
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +93 -143
- package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/src/runs/shared/model-fallback.ts +9 -2
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +20 -2
- package/src/runs/shared/single-output.ts +27 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -4
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +5 -2
- package/src/shared/launch-contract.ts +1 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +198 -29
- package/src/shared/utils.ts +35 -55
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/tui/fleet-status.ts +125 -74
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +316 -27
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +230 -0
- package/src/workflows/scripted-workflow.ts +452 -38
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +43 -4
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
package/src/shared/utils.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type { Message } from "@earendil-works/pi-ai";
|
|
4
|
+
import type { Message, Usage as PiUsage } from "@earendil-works/pi-ai";
|
|
5
5
|
import { previewDisplayText, sanitizeDisplayText, truncateDisplayText } from "./display-text.ts";
|
|
6
6
|
import { formatToolCall } from "./formatters.ts";
|
|
7
7
|
import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, NestedRunSummary, SingleResult, ToolCallSummary, Usage } from "./types.ts";
|
|
8
|
+
import { validateAsyncStatusLaneMetadata } from "../runs/shared/lane-metadata.ts";
|
|
8
9
|
|
|
9
10
|
const DEFAULT_CONFIG_DIR_NAME = ".pi";
|
|
10
11
|
const PI_CODING_AGENT_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
|
|
@@ -183,6 +184,13 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
183
184
|
cause: error instanceof Error ? error : undefined,
|
|
184
185
|
});
|
|
185
186
|
}
|
|
187
|
+
try {
|
|
188
|
+
validateAsyncStatusLaneMetadata(status, `Invalid async status '${statusPath}'`);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
throw new Error(`Failed to validate async status file '${statusPath}': ${getErrorMessage(error)}`, {
|
|
191
|
+
cause: error instanceof Error ? error : undefined,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
186
194
|
|
|
187
195
|
statusCache.set(statusPath, {
|
|
188
196
|
mtime: stat.mtimeMs,
|
|
@@ -199,53 +207,6 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
199
207
|
return status;
|
|
200
208
|
}
|
|
201
209
|
|
|
202
|
-
const outputTailCache = new Map<string, { mtime: number; size: number; lines: string[] }>();
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Get the last N lines from an output file (with mtime/size-based caching)
|
|
206
|
-
*/
|
|
207
|
-
function getOutputTail(outputFile: string | undefined, maxLines: number = 3): string[] {
|
|
208
|
-
if (!outputFile) return [];
|
|
209
|
-
let fd: number | null = null;
|
|
210
|
-
try {
|
|
211
|
-
const stat = fs.statSync(outputFile);
|
|
212
|
-
if (stat.size === 0) return [];
|
|
213
|
-
|
|
214
|
-
const cached = outputTailCache.get(outputFile);
|
|
215
|
-
if (cached && cached.mtime === stat.mtimeMs && cached.size === stat.size) {
|
|
216
|
-
return cached.lines;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const tailBytes = 4096;
|
|
220
|
-
const start = Math.max(0, stat.size - tailBytes);
|
|
221
|
-
fd = fs.openSync(outputFile, "r");
|
|
222
|
-
const buffer = Buffer.alloc(Math.min(tailBytes, stat.size));
|
|
223
|
-
fs.readSync(fd, buffer, 0, buffer.length, start);
|
|
224
|
-
const content = buffer.toString("utf-8");
|
|
225
|
-
const allLines = content.split("\n").filter((l) => l.trim());
|
|
226
|
-
const lines = allLines.slice(-maxLines).map((l) => l.slice(0, 120) + (l.length > 120 ? "..." : ""));
|
|
227
|
-
|
|
228
|
-
outputTailCache.set(outputFile, { mtime: stat.mtimeMs, size: stat.size, lines });
|
|
229
|
-
if (outputTailCache.size > 20) {
|
|
230
|
-
const firstKey = outputTailCache.keys().next().value;
|
|
231
|
-
if (firstKey) outputTailCache.delete(firstKey);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return lines;
|
|
235
|
-
} catch {
|
|
236
|
-
// Output tails are UI-only hints; unreadable or missing files should render as no tail.
|
|
237
|
-
return [];
|
|
238
|
-
} finally {
|
|
239
|
-
if (fd !== null) {
|
|
240
|
-
try {
|
|
241
|
-
fs.closeSync(fd);
|
|
242
|
-
} catch {
|
|
243
|
-
// Closing the best-effort tail file handle should not surface over the main status view.
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
210
|
export function getLastActivity(outputFile: string | undefined): string {
|
|
250
211
|
if (!outputFile) return "";
|
|
251
212
|
try {
|
|
@@ -276,13 +237,6 @@ export function findLatestSessionFile(sessionDir: string): string | null {
|
|
|
276
237
|
return latest ? latest.path : null;
|
|
277
238
|
}
|
|
278
239
|
|
|
279
|
-
function writePrompt(agent: string, prompt: string): { dir: string; path: string } {
|
|
280
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
281
|
-
const p = path.join(dir, `${agent.replace(/[^\w.-]/g, "_")}.md`);
|
|
282
|
-
fs.writeFileSync(p, prompt, { mode: 0o600 });
|
|
283
|
-
return { dir, path: p };
|
|
284
|
-
}
|
|
285
|
-
|
|
286
240
|
export function getFinalOutput(messages: Message[]): string {
|
|
287
241
|
const validTextParts: string[] = [];
|
|
288
242
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -338,6 +292,7 @@ function compactCompletedProgress(progress: AgentProgress): AgentProgress {
|
|
|
338
292
|
return {
|
|
339
293
|
index: progress.index,
|
|
340
294
|
agent: progress.agent,
|
|
295
|
+
...(progress.sessionName ? { sessionName: progress.sessionName } : {}),
|
|
341
296
|
status: progress.status,
|
|
342
297
|
activityState: progress.activityState,
|
|
343
298
|
task: "[prompt redacted]",
|
|
@@ -384,6 +339,17 @@ export function sumResultsUsage(results: SingleResult[]): Usage {
|
|
|
384
339
|
return usage;
|
|
385
340
|
}
|
|
386
341
|
|
|
342
|
+
export function toAgentToolUsage(usage: Usage): PiUsage {
|
|
343
|
+
return {
|
|
344
|
+
input: usage.input,
|
|
345
|
+
output: usage.output,
|
|
346
|
+
cacheRead: usage.cacheRead,
|
|
347
|
+
cacheWrite: usage.cacheWrite,
|
|
348
|
+
totalTokens: usage.input + usage.output + usage.cacheRead + usage.cacheWrite,
|
|
349
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: usage.cost },
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
387
353
|
function addNestedCost(total: NonNullable<Details["totalCost"]>, children: NestedRunSummary[] | undefined): void {
|
|
388
354
|
for (const child of children ?? []) {
|
|
389
355
|
if (child.totalCost) {
|
|
@@ -482,6 +448,20 @@ export function hasEmptyTerminalAssistantResponse(messages: Message[]): boolean
|
|
|
482
448
|
&& lastAssistant.usage.output === 0;
|
|
483
449
|
}
|
|
484
450
|
|
|
451
|
+
export function formatEmptyTerminalAssistantResponseError(messages: Message[]): string {
|
|
452
|
+
const lastAssistant = messages.findLast((message) => message.role === "assistant");
|
|
453
|
+
const errorMessage = lastAssistant && "errorMessage" in lastAssistant && typeof lastAssistant.errorMessage === "string" && lastAssistant.errorMessage.trim()
|
|
454
|
+
? lastAssistant.errorMessage.trim()
|
|
455
|
+
: undefined;
|
|
456
|
+
if (errorMessage) return errorMessage;
|
|
457
|
+
const stopReason = lastAssistant && "stopReason" in lastAssistant && typeof lastAssistant.stopReason === "string" && lastAssistant.stopReason.trim()
|
|
458
|
+
? lastAssistant.stopReason.trim()
|
|
459
|
+
: undefined;
|
|
460
|
+
return stopReason && stopReason !== "stop"
|
|
461
|
+
? `Subagent produced no output after terminal assistant stopReason "${stopReason}".`
|
|
462
|
+
: "Subagent produced no output (possible model cold-start or empty response).";
|
|
463
|
+
}
|
|
464
|
+
|
|
485
465
|
/**
|
|
486
466
|
* Detect errors in subagent execution from messages (only errors with no subsequent success)
|
|
487
467
|
*/
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type SubagentDelegationUpdate,
|
|
7
7
|
type SubagentDelegationValue,
|
|
8
8
|
} from "../api/delegation.ts";
|
|
9
|
-
import type { AcceptanceInput, AgentContract, EffectsProjection, ExecutionProjection, JsonSchemaObject, ReviewProjection, ToolBudgetConfig,
|
|
9
|
+
import type { AcceptanceInput, AgentContract, EffectsProjection, ExecutionProjection, JsonSchemaObject, ReviewProjection, ToolBudgetConfig, Usage } from "../shared/types.ts";
|
|
10
10
|
import { cloneJsonWithinByteLimit } from "./delegation-json.ts";
|
|
11
11
|
|
|
12
12
|
export interface PromptTemplateDelegationRequest {
|
|
@@ -82,7 +82,6 @@ export interface PromptTemplateBridgeResult {
|
|
|
82
82
|
interrupted?: boolean;
|
|
83
83
|
timedOut?: boolean;
|
|
84
84
|
stopped?: boolean;
|
|
85
|
-
turnBudgetExceeded?: boolean;
|
|
86
85
|
toolBudgetBlocked?: boolean;
|
|
87
86
|
structuredOutputFailed?: boolean;
|
|
88
87
|
savedOutputPath?: string;
|
|
@@ -120,9 +119,6 @@ export interface DelegatedSubagentExecutionParams {
|
|
|
120
119
|
model?: string;
|
|
121
120
|
cwd: string;
|
|
122
121
|
timeoutMs?: number;
|
|
123
|
-
turnBudget?: TurnBudgetConfig;
|
|
124
|
-
/** Internal-only strict turn-boundary enforcement for structured foreground delegation. */
|
|
125
|
-
enforceHardTurnLimit?: boolean;
|
|
126
122
|
toolBudget?: ToolBudgetConfig;
|
|
127
123
|
skill?: string | string[] | boolean;
|
|
128
124
|
output?: string | boolean;
|
|
@@ -294,8 +290,6 @@ export function toSubagentDelegationExecutionParams(request: SubagentDelegationR
|
|
|
294
290
|
cwd: request.cwd,
|
|
295
291
|
model: request.model,
|
|
296
292
|
timeoutMs: request.timeoutMs,
|
|
297
|
-
turnBudget: request.turnBudget,
|
|
298
|
-
enforceHardTurnLimit: true,
|
|
299
293
|
toolBudget: request.toolBudget,
|
|
300
294
|
skill: request.skill,
|
|
301
295
|
...(request.result.kind === "structured" ? { outputSchema: request.result.schema } : {}),
|
|
@@ -341,7 +335,6 @@ function resolveSubagentDelegationStatus(
|
|
|
341
335
|
if (!child) return "failed";
|
|
342
336
|
if (result.details?.timedOut || child.timedOut) return "timed_out";
|
|
343
337
|
if (child?.structuredOutputFailed) return "structured_output_failed";
|
|
344
|
-
if (child?.turnBudgetExceeded) return "turn_budget_exhausted";
|
|
345
338
|
if (child?.toolBudgetBlocked) return "tool_budget_exhausted";
|
|
346
339
|
if (child?.acceptance?.status === "rejected" && child.acceptance.explicit) return "acceptance_failed";
|
|
347
340
|
if (result.details?.stopped || child?.stopped || child?.interrupted) return "interrupted";
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
type SubagentDelegationRequest,
|
|
3
3
|
} from "../api/delegation.ts";
|
|
4
4
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
5
|
-
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
6
5
|
import { cloneJsonWithinByteLimit } from "./delegation-json.ts";
|
|
7
6
|
|
|
8
7
|
export type SubagentDelegationParseResult =
|
|
@@ -20,7 +19,6 @@ const supportedFields = new Set([
|
|
|
20
19
|
"model",
|
|
21
20
|
"thinking",
|
|
22
21
|
"timeoutMs",
|
|
23
|
-
"turnBudget",
|
|
24
22
|
"toolBudget",
|
|
25
23
|
"skill",
|
|
26
24
|
"artifacts",
|
|
@@ -80,8 +78,6 @@ export function parseSubagentDelegationRequest(data: unknown): SubagentDelegatio
|
|
|
80
78
|
if (timeoutMs !== undefined && timeoutMs > 2_147_483_647) {
|
|
81
79
|
return { ok: false, ...identity, error: "timeoutMs must be <= 2147483647." };
|
|
82
80
|
}
|
|
83
|
-
const turnBudget = resolveTurnBudgetConfig(value.turnBudget);
|
|
84
|
-
if (turnBudget.error) return { ok: false, ...identity, error: turnBudget.error };
|
|
85
81
|
if (value.toolBudget && typeof value.toolBudget === "object" && !Array.isArray(value.toolBudget)) {
|
|
86
82
|
const unsupportedToolBudgetField = Object.keys(value.toolBudget).find((key) => key !== "soft" && key !== "hard" && key !== "block");
|
|
87
83
|
if (unsupportedToolBudgetField) {
|
|
@@ -39,7 +39,6 @@ interface EventBus {
|
|
|
39
39
|
|
|
40
40
|
interface SlashBridgeOptions {
|
|
41
41
|
events: EventBus;
|
|
42
|
-
asyncByDefault: boolean;
|
|
43
42
|
getContext: () => ExtensionContext | null;
|
|
44
43
|
execute: (
|
|
45
44
|
id: string,
|
|
@@ -80,7 +79,7 @@ export function registerSlashSubagentBridge(options: SlashBridgeOptions): {
|
|
|
80
79
|
const request = data as Partial<SlashSubagentRequest>;
|
|
81
80
|
if (typeof request.requestId !== "string" || !request.params) return;
|
|
82
81
|
const { requestId } = request as SlashSubagentRequest;
|
|
83
|
-
const normalized = normalizePublicSubagentExecution((request as SlashSubagentRequest).params
|
|
82
|
+
const normalized = normalizePublicSubagentExecution((request as SlashSubagentRequest).params);
|
|
84
83
|
if (!normalized.ok) {
|
|
85
84
|
options.events.emit(SLASH_SUBAGENT_RESPONSE_EVENT, {
|
|
86
85
|
requestId,
|