pi-subagents 0.40.0 → 0.42.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 +97 -0
- package/README.md +54 -1879
- package/agents/oracle.md +1 -0
- package/inspector-runner.mjs +11 -0
- package/package.json +13 -5
- package/prompts/parallel-context-build.md +1 -1
- package/prompts/parallel-handoff-plan.md +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +9 -7
- package/skills/pi-subagents/references/constraints-and-recipes.md +24 -32
- package/skills/pi-subagents/references/execution-controls.md +99 -97
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +23 -30
- package/src/agents/agent-management.ts +155 -65
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +154 -71
- package/src/agents/chain-serializer.ts +10 -7
- package/src/agents/frontmatter.ts +5 -3
- package/src/agents/identity.ts +1 -1
- package/src/agents/proactive-skills.ts +13 -10
- package/src/agents/skills.ts +23 -6
- package/src/api/control-channel.ts +4 -0
- package/src/api/delegation.ts +26 -194
- package/src/api/external-runs.ts +129 -0
- package/src/api/intercom-bridge.ts +3 -0
- package/src/api/pi-args.ts +5 -0
- package/src/api/preflight.ts +3 -3
- package/src/api/shared-types.ts +19 -0
- package/src/extension/config.ts +10 -0
- package/src/extension/control-notices.ts +5 -39
- package/src/extension/doctor.ts +10 -9
- package/src/extension/fanout-child.ts +7 -4
- package/src/extension/index.ts +232 -68
- package/src/extension/rpc.ts +18 -12
- package/src/extension/schemas.ts +48 -37
- package/src/extension/tool-description.ts +36 -86
- package/src/inspectors/herdr/actions.ts +229 -0
- package/src/inspectors/herdr/client.ts +130 -0
- package/src/inspectors/herdr/inspector-runner.ts +141 -0
- package/src/inspectors/herdr/project-panes.ts +154 -0
- package/src/integrations/herdr-status.ts +330 -0
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/result-intercom.ts +5 -1
- package/src/missions/actions.ts +374 -0
- package/src/missions/lifecycle.ts +314 -0
- package/src/missions/store.ts +442 -0
- package/src/missions/types.ts +135 -0
- package/src/policy/authority.ts +46 -0
- package/src/profiles/profiles.ts +29 -3
- package/src/runs/background/async-execution.ts +110 -50
- package/src/runs/background/async-job-tracker.ts +27 -4
- package/src/runs/background/async-resume.ts +6 -6
- package/src/runs/background/async-status.ts +29 -1
- package/src/runs/background/auto-drain.ts +3 -3
- package/src/runs/background/chain-append.ts +3 -2
- package/src/runs/background/control-channel.ts +9 -7
- package/src/runs/background/fleet-view.ts +3 -4
- package/src/runs/background/notify.ts +2 -1
- package/src/runs/background/process-terminal.ts +5 -5
- package/src/runs/background/result-watcher.ts +13 -5
- package/src/runs/background/run-id-resolver.ts +3 -3
- package/src/runs/background/run-status.ts +35 -8
- package/src/runs/background/scheduled-runs.ts +602 -375
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +608 -445
- package/src/runs/background/subagent-wait.ts +50 -9
- package/src/runs/background/wait-subscriptions.ts +253 -0
- package/src/runs/background/wait-tool.ts +12 -4
- package/src/runs/foreground/async-steering-action.ts +3 -3
- package/src/runs/foreground/chain-clarify.ts +8 -4
- package/src/runs/foreground/chain-execution.ts +56 -30
- package/src/runs/foreground/execution.ts +34 -9
- package/src/runs/foreground/subagent-executor.ts +1036 -274
- package/src/runs/shared/acceptance.ts +28 -6
- package/src/runs/shared/child-protocol.ts +302 -22
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/external-cli-runner.ts +130 -0
- package/src/runs/shared/long-running-guard.ts +42 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
- package/src/runs/shared/nested-events.ts +59 -5
- package/src/runs/shared/nested-render.ts +9 -4
- package/src/runs/shared/parallel-handoff.ts +86 -2
- package/src/runs/shared/parallel-utils.ts +11 -2
- package/src/runs/shared/permissions.ts +95 -0
- package/src/runs/shared/pi-args.ts +468 -109
- package/src/runs/shared/pi-spawn.ts +11 -1
- package/src/runs/shared/run-history.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +36 -5
- package/src/runs/shared/subagent-startup-retry.ts +5 -2
- package/src/runs/shared/turn-budget.ts +6 -6
- package/src/runs/shared/worktree.ts +122 -12
- package/src/shared/accessible-dir.ts +29 -7
- package/src/shared/artifacts.ts +143 -1
- package/src/shared/fork-context.ts +3 -2
- package/src/shared/launch-contract.ts +1 -0
- package/src/shared/settings.ts +10 -0
- package/src/shared/types.ts +156 -14
- package/src/shared/utils.ts +8 -6
- package/src/slash/delegation-adapters.ts +32 -194
- package/src/slash/delegation-request.ts +43 -126
- package/src/slash/prompt-template-bridge.ts +158 -205
- package/src/slash/prompt-workflows.ts +21 -57
- package/src/slash/slash-bridge.ts +14 -0
- package/src/slash/slash-commands.ts +31 -632
- package/src/slash/subagents-admin.ts +18 -14
- package/src/tui/fleet-status.ts +156 -21
- package/src/tui/fleet-transcript.ts +110 -5
- package/src/tui/fleet.ts +57 -25
- package/src/tui/render.ts +368 -152
- package/src/types/pi-runtime-compat.d.ts +8 -0
- package/src/watchdog/lsp-diagnostics.ts +12 -7
- package/src/watchdog/model-selection.ts +2 -2
- package/src/watchdog/permission-arbiter.ts +145 -0
- package/src/watchdog/register-child.ts +1 -1
- package/src/watchdog/register-main.ts +1 -1
- package/src/watchdog/review.ts +4 -1
- package/src/watchdog/runtime.ts +3 -2
- package/src/workflows/chat-progress.ts +140 -0
- package/src/workflows/scripted-workflow.ts +415 -0
- package/agents/advisor.md +0 -73
- package/src/extension/chain-validation.ts +0 -181
|
@@ -70,6 +70,11 @@ function normalizePath(filePath: string): string {
|
|
|
70
70
|
return path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function isStandalonePiExecutable(execPath: string): boolean {
|
|
74
|
+
const executableName = execPath.split(/[\\/]/).pop();
|
|
75
|
+
return /^pi(?:\.exe)?$/i.test(executableName ?? "");
|
|
76
|
+
}
|
|
77
|
+
|
|
73
78
|
export function resolvePiCliScript(
|
|
74
79
|
deps: PiSpawnDeps = {},
|
|
75
80
|
): string | undefined {
|
|
@@ -141,10 +146,15 @@ export function getPiSpawnCommand(
|
|
|
141
146
|
return { command: piBinary, args };
|
|
142
147
|
}
|
|
143
148
|
|
|
149
|
+
const execPath = deps.execPath ?? process.execPath;
|
|
150
|
+
if (isStandalonePiExecutable(execPath)) {
|
|
151
|
+
return { command: execPath, args };
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
const piCliPath = resolvePiCliScript(deps);
|
|
145
155
|
if (piCliPath) {
|
|
146
156
|
return {
|
|
147
|
-
command:
|
|
157
|
+
command: execPath,
|
|
148
158
|
args: [piCliPath, ...args],
|
|
149
159
|
};
|
|
150
160
|
}
|
|
@@ -176,6 +176,6 @@ export function loadRunsForAgent(agent: string): RunEntry[] {
|
|
|
176
176
|
|
|
177
177
|
return lines
|
|
178
178
|
.map((line) => { try { return JSON.parse(line) as RunEntry; } catch { return undefined; } })
|
|
179
|
-
.filter((entry): entry is RunEntry =>
|
|
179
|
+
.filter((entry): entry is RunEntry => entry !== undefined && entry.agent === agent)
|
|
180
180
|
.reverse();
|
|
181
181
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { registerNativeSupervisorClient } from "../../intercom/native-supervisor-channel.ts";
|
|
5
|
+
import { decodePermissionRules, permissionDecision, PERMISSION_AUDIT_PATH_ENV, PERMISSION_POLICY_ENV } from "./permissions.ts";
|
|
5
6
|
import { consumeSteerRequestsFromDir, steerAckPathFromDir, writeSteerAckAt, writeSteerCapabilityAt, writeSteerRequestToDir, type SteerRequest } from "../background/control-channel.ts";
|
|
6
7
|
import { SUBAGENT_CHILD_AGENT_ENV, SUBAGENT_CHILD_INDEX_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_STEER_ACK_DIR_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_INBOX_ENV } from "./pi-args.ts";
|
|
7
8
|
import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV, isRuntimeAcknowledgedExtensionId, writeRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
|
|
@@ -18,6 +19,8 @@ import type { JsonSchemaObject, ResolvedToolBudget, SubagentState } from "../../
|
|
|
18
19
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
19
20
|
import { resolveWatchPath } from "../../shared/utils.ts";
|
|
20
21
|
import { registerChildWatchdog } from "../../watchdog/register-child.ts";
|
|
22
|
+
import { CHILD_WATCHDOG_CONFIG_ENV } from "../../watchdog/child-status.ts";
|
|
23
|
+
import { requestWatchdogPermission, type WatchdogPermissionRequest, type WatchdogPermissionResult } from "../../watchdog/permission-arbiter.ts";
|
|
21
24
|
import { SUBAGENT_WATCHDOG_WARNING_TYPE } from "../../watchdog/types.ts";
|
|
22
25
|
import { resolveWaitToolConfig } from "../background/wait-config.ts";
|
|
23
26
|
import { registerWaitTool } from "../background/wait-tool.ts";
|
|
@@ -241,6 +244,31 @@ export function formatSteerMessage(request: SteerRequest): string {
|
|
|
241
244
|
].join("\n");
|
|
242
245
|
}
|
|
243
246
|
|
|
247
|
+
export function registerPermissionGate(
|
|
248
|
+
pi: ExtensionAPI,
|
|
249
|
+
requestPermission: (request: WatchdogPermissionRequest) => Promise<WatchdogPermissionResult> = requestWatchdogPermission,
|
|
250
|
+
): void {
|
|
251
|
+
const rules = decodePermissionRules(process.env[PERMISSION_POLICY_ENV]);
|
|
252
|
+
if (!rules) return;
|
|
253
|
+
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: ExtensionContext) => unknown) => void;
|
|
254
|
+
onRuntimeEvent("tool_call", async (event, ctx) => {
|
|
255
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
256
|
+
const decision = permissionDecision(rules, toolName);
|
|
257
|
+
if (decision === "allow") return undefined;
|
|
258
|
+
if (decision === "deny") return { block: true, reason: `Blocked by pi-subagents permission rule: '${toolName}' is denied.` };
|
|
259
|
+
const result = await requestPermission({
|
|
260
|
+
ctx,
|
|
261
|
+
toolName,
|
|
262
|
+
args: event.input ?? {},
|
|
263
|
+
rawWatchdogConfig: process.env[CHILD_WATCHDOG_CONFIG_ENV],
|
|
264
|
+
auditPath: process.env[PERMISSION_AUDIT_PATH_ENV],
|
|
265
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
266
|
+
});
|
|
267
|
+
if (result.approved) return undefined;
|
|
268
|
+
return { block: true, reason: `Blocked by pi-subagents permission rule: ${result.reason}` };
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
244
272
|
function registerToolBudget(pi: ExtensionAPI, budget: ResolvedToolBudget | undefined): void {
|
|
245
273
|
if (!budget) return;
|
|
246
274
|
let toolCount = 0;
|
|
@@ -360,7 +388,7 @@ export function registerSteeringInbox(
|
|
|
360
388
|
return undefined;
|
|
361
389
|
};
|
|
362
390
|
|
|
363
|
-
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown) => unknown) => void;
|
|
391
|
+
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown, ctx?: unknown) => unknown) => void;
|
|
364
392
|
// Register input before the watcher so an accepted extension input cannot race request dispatch.
|
|
365
393
|
onRuntimeEvent("input", onInput);
|
|
366
394
|
onRuntimeEvent("session_start", () => start());
|
|
@@ -377,6 +405,7 @@ export function registerSteeringInbox(
|
|
|
377
405
|
export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
378
406
|
registerRuntimeExtensionAcknowledgements(pi);
|
|
379
407
|
registerSteeringInbox(pi);
|
|
408
|
+
registerPermissionGate(pi);
|
|
380
409
|
registerToolBudget(pi, decodeToolBudgetEnv(process.env[TOOL_BUDGET_ENV], { allowZero: process.env[TOOL_BUDGET_ZERO_AUTH_ENV] === "1" }));
|
|
381
410
|
registerChildWatchdog(pi);
|
|
382
411
|
const waitToolEnabled = resolveWaitToolConfig().enabled;
|
|
@@ -408,7 +437,7 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
408
437
|
nativeSupervisorFallbackRegistered = true;
|
|
409
438
|
registerNativeSupervisorClient(pi);
|
|
410
439
|
};
|
|
411
|
-
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown) => unknown) => void;
|
|
440
|
+
const onRuntimeEvent = pi.on as unknown as (event: string, handler: (event: unknown, ctx?: unknown) => unknown) => void;
|
|
412
441
|
onRuntimeEvent("session_start", (_event: unknown, ctx: unknown) => {
|
|
413
442
|
const sessionManager = (ctx as { sessionManager?: Parameters<typeof resolveCurrentSessionId>[0] } | undefined)?.sessionManager;
|
|
414
443
|
waitState.currentSessionId = sessionManager ? resolveCurrentSessionId(sessionManager) : null;
|
|
@@ -455,13 +484,15 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
|
455
484
|
});
|
|
456
485
|
}
|
|
457
486
|
|
|
458
|
-
onRuntimeEvent("context", (event:
|
|
487
|
+
onRuntimeEvent("context", (event: unknown) => {
|
|
488
|
+
if (!event || typeof event !== "object" || !("messages" in event) || !Array.isArray(event.messages)) return undefined;
|
|
459
489
|
const messages = stripParentOnlySubagentMessages(event.messages);
|
|
460
490
|
if (messages === event.messages) return undefined;
|
|
461
491
|
return { messages };
|
|
462
492
|
});
|
|
463
493
|
|
|
464
|
-
onRuntimeEvent("before_agent_start", async (event:
|
|
494
|
+
onRuntimeEvent("before_agent_start", async (event: unknown) => {
|
|
495
|
+
if (!event || typeof event !== "object" || !("systemPrompt" in event) || typeof event.systemPrompt !== "string") return undefined;
|
|
465
496
|
registerNativeSupervisorFallbackOnce();
|
|
466
497
|
const intercomSessionName = process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV]?.trim();
|
|
467
498
|
if (intercomSessionName && typeof pi.setSessionName === "function") {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Usage } from "../../shared/types.ts";
|
|
2
|
+
import { formatProcessSignalError } from "./process-signal.ts";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Retry delays for child processes that exit before any model or tool activity.
|
|
@@ -10,6 +11,8 @@ export const SUBAGENT_STARTUP_RETRY_DELAYS_MS = [250, 750, 1500] as const;
|
|
|
10
11
|
/** A genuine startup race should fail well before a model request can complete. */
|
|
11
12
|
export const MAX_SUBAGENT_STARTUP_FAILURE_DURATION_MS = 2000;
|
|
12
13
|
|
|
14
|
+
const SIGKILL_PROCESS_SIGNAL_ERROR = formatProcessSignalError("SIGKILL");
|
|
15
|
+
|
|
13
16
|
export interface SubagentStartupFailureEvidence {
|
|
14
17
|
exitCode: number | null | undefined;
|
|
15
18
|
error?: string;
|
|
@@ -46,7 +49,7 @@ export function isRetryableSubagentStartupFailure(evidence: SubagentStartupFailu
|
|
|
46
49
|
return evidence.exitCode !== undefined
|
|
47
50
|
&& evidence.exitCode !== null
|
|
48
51
|
&& evidence.exitCode !== 0
|
|
49
|
-
&& !evidence.error?.trim()
|
|
52
|
+
&& (!evidence.error?.trim() || evidence.error === SIGKILL_PROCESS_SIGNAL_ERROR)
|
|
50
53
|
&& !evidence.finalOutput?.trim()
|
|
51
54
|
&& evidence.messageCount === 0
|
|
52
55
|
&& evidence.toolCount === 0
|
|
@@ -54,7 +57,7 @@ export function isRetryableSubagentStartupFailure(evidence: SubagentStartupFailu
|
|
|
54
57
|
&& evidence.durationMs >= 0
|
|
55
58
|
&& evidence.durationMs <= MAX_SUBAGENT_STARTUP_FAILURE_DURATION_MS
|
|
56
59
|
&& evidence.protocolError === undefined
|
|
57
|
-
&& (evidence.processSignal === undefined || evidence.processSignal === null)
|
|
60
|
+
&& (evidence.processSignal === undefined || evidence.processSignal === null || evidence.processSignal === "SIGKILL")
|
|
58
61
|
&& evidence.observedMutationAttempt !== true
|
|
59
62
|
&& evidence.detached !== true
|
|
60
63
|
&& evidence.interrupted !== true
|
|
@@ -23,9 +23,10 @@ export function resolveTurnBudgetConfig(
|
|
|
23
23
|
return { turnBudget: { maxTurns: budget.maxTurns, graceTurns } };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function appendTurnBudgetSystemPrompt(systemPrompt: string, budget:
|
|
26
|
+
export function appendTurnBudgetSystemPrompt(systemPrompt: string, budget: { maxTurns: number; graceTurns?: number } | undefined): string {
|
|
27
27
|
if (!budget) return systemPrompt;
|
|
28
|
-
const
|
|
28
|
+
const graceTurns = budget.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS;
|
|
29
|
+
const grace = graceTurns === 1 ? "1 additional assistant turn" : `${graceTurns} additional assistant turns`;
|
|
29
30
|
const block = [
|
|
30
31
|
"## Turn budget",
|
|
31
32
|
`This child run has a soft budget of ${budget.maxTurns} assistant turn${budget.maxTurns === 1 ? "" : "s"}.`,
|
|
@@ -55,8 +56,8 @@ export function formatTurnBudgetOutput(message: string, output: string): string
|
|
|
55
56
|
: message;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
export function initialTurnBudgetState(budget:
|
|
59
|
-
return {
|
|
59
|
+
export function initialTurnBudgetState(budget: { maxTurns: number; graceTurns?: number }): TurnBudgetState {
|
|
60
|
+
return { maxTurns: budget.maxTurns, graceTurns: budget.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS, outcome: "within-budget", turnCount: 0 };
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function turnBudgetState(budget: ResolvedTurnBudget, turnCount: number, exceeded: boolean): TurnBudgetState {
|
|
@@ -91,8 +92,7 @@ export function turnBudgetDecision(
|
|
|
91
92
|
enforceHardLimit = false,
|
|
92
93
|
): "continue" | "defer" | "abort" {
|
|
93
94
|
const hardLimit = budget.maxTurns + budget.graceTurns;
|
|
94
|
-
if (turnCount < hardLimit) return "continue";
|
|
95
|
+
if (terminalAssistantStop || turnCount < hardLimit) return "continue";
|
|
95
96
|
if (toolWorkActiveOrStarting && !enforceHardLimit) return "defer";
|
|
96
|
-
if (turnCount === hardLimit && terminalAssistantStop) return "continue";
|
|
97
97
|
return "abort";
|
|
98
98
|
}
|
|
@@ -2,14 +2,16 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { resolveAuthorityDecision, type AuthorityPolicyConfig } from "../../policy/authority.ts";
|
|
5
6
|
|
|
6
7
|
export interface WorktreeSetup {
|
|
7
8
|
cwd: string;
|
|
8
9
|
worktrees: WorktreeInfo[];
|
|
9
10
|
baseCommit: string;
|
|
11
|
+
capturedDiffs?: WorktreeDiff[];
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
interface WorktreeInfo {
|
|
14
|
+
export interface WorktreeInfo {
|
|
13
15
|
path: string;
|
|
14
16
|
agentCwd: string;
|
|
15
17
|
branch: string;
|
|
@@ -36,9 +38,21 @@ export interface WorktreeCleanupTask {
|
|
|
36
38
|
branch: string;
|
|
37
39
|
worktreeRemoved: boolean;
|
|
38
40
|
branchRemoved: boolean;
|
|
41
|
+
preserved?: boolean;
|
|
42
|
+
reason?: string;
|
|
39
43
|
errors?: string[];
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
export type WorktreeCleanupIntent =
|
|
47
|
+
| { kind: "preserve"; capturedDiffs?: WorktreeDiff[]; handoffManifestPath?: string }
|
|
48
|
+
| {
|
|
49
|
+
kind: "discard";
|
|
50
|
+
authorization:
|
|
51
|
+
| { kind: "policy"; policy?: AuthorityPolicyConfig }
|
|
52
|
+
| { kind: "confirmed"; policy?: AuthorityPolicyConfig };
|
|
53
|
+
}
|
|
54
|
+
| { kind: "setup-rollback" };
|
|
55
|
+
|
|
42
56
|
export interface WorktreeCleanupReport {
|
|
43
57
|
state: "complete" | "partial";
|
|
44
58
|
tasks: WorktreeCleanupTask[];
|
|
@@ -121,7 +135,9 @@ function resolveRepoState(cwd: string): RepoState {
|
|
|
121
135
|
const cwdRelative = resolveRepoCwdRelative(cwd);
|
|
122
136
|
const toplevel = runGitChecked(cwd, ["rev-parse", "--show-toplevel"]).trim();
|
|
123
137
|
|
|
124
|
-
|
|
138
|
+
// pi-subagents writes durable runtime state under .pi-subagents/ by default;
|
|
139
|
+
// that state must not make managed isolation unusable for later runs.
|
|
140
|
+
const status = runGitChecked(toplevel, ["status", "--porcelain", "--", ":!.pi-subagents"]);
|
|
125
141
|
if (status.trim().length > 0) {
|
|
126
142
|
throw new Error("worktree isolation requires a clean git working tree. Commit or stash changes first.");
|
|
127
143
|
}
|
|
@@ -515,21 +531,111 @@ function writeEmptyPatch(patchPath: string): void {
|
|
|
515
531
|
}
|
|
516
532
|
}
|
|
517
533
|
|
|
518
|
-
function
|
|
534
|
+
function handoffRecordsPatch(manifestPath: string | undefined, patchPath: string): boolean {
|
|
535
|
+
if (!manifestPath || !fs.existsSync(manifestPath)) return false;
|
|
536
|
+
try {
|
|
537
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as {
|
|
538
|
+
version?: unknown;
|
|
539
|
+
groups?: Array<{ children?: Array<{ patch?: { path?: unknown; error?: unknown } }> }>;
|
|
540
|
+
};
|
|
541
|
+
if (manifest.version !== 1 || !Array.isArray(manifest.groups)) return false;
|
|
542
|
+
const resolvedPatchPath = path.resolve(patchPath);
|
|
543
|
+
return manifest.groups.some((group) => Array.isArray(group.children) && group.children.some((child) =>
|
|
544
|
+
child.patch?.error === undefined
|
|
545
|
+
&& typeof child.patch?.path === "string"
|
|
546
|
+
&& path.resolve(child.patch.path) === resolvedPatchPath,
|
|
547
|
+
));
|
|
548
|
+
} catch {
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function cleanupSingleWorktree(
|
|
554
|
+
setup: WorktreeSetup,
|
|
555
|
+
worktree: WorktreeInfo,
|
|
556
|
+
intent: WorktreeCleanupIntent,
|
|
557
|
+
): WorktreeCleanupTask {
|
|
519
558
|
const errors: string[] = [];
|
|
520
559
|
let worktreeRemoved = false;
|
|
521
560
|
let branchRemoved = false;
|
|
561
|
+
if (intent.kind !== "setup-rollback") {
|
|
562
|
+
try {
|
|
563
|
+
removeSyntheticPathsBeforeDiff(worktree);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
errors.push(`synthetic path cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
566
|
+
}
|
|
567
|
+
const status = runGit(worktree.path, ["status", "--porcelain"]);
|
|
568
|
+
const baseDiff = runGit(worktree.path, ["diff", "--quiet", setup.baseCommit, "--"]);
|
|
569
|
+
if (status.status !== 0 || (baseDiff.status !== 0 && baseDiff.status !== 1)) {
|
|
570
|
+
const reason = status.status !== 0
|
|
571
|
+
? status.stderr.trim() || status.stdout.trim() || "git status failed"
|
|
572
|
+
: baseDiff.stderr.trim() || baseDiff.stdout.trim() || "git diff check failed";
|
|
573
|
+
return {
|
|
574
|
+
index: worktree.index,
|
|
575
|
+
path: worktree.path,
|
|
576
|
+
branch: worktree.branch,
|
|
577
|
+
worktreeRemoved: false,
|
|
578
|
+
branchRemoved: false,
|
|
579
|
+
preserved: true,
|
|
580
|
+
reason: "cleanup safety check failed",
|
|
581
|
+
errors: [...errors, `cleanup refused: ${reason}`],
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
const hasWork = status.stdout.trim().length > 0 || baseDiff.status === 1;
|
|
585
|
+
if (hasWork && intent.kind === "preserve") {
|
|
586
|
+
const captured = (intent.capturedDiffs ?? setup.capturedDiffs)?.find((diff) => diff.index === worktree.index);
|
|
587
|
+
const patchCaptured = captured !== undefined
|
|
588
|
+
&& captured.error === undefined
|
|
589
|
+
&& fs.existsSync(captured.patchPath)
|
|
590
|
+
&& fs.statSync(captured.patchPath).size > 0
|
|
591
|
+
&& handoffRecordsPatch(intent.handoffManifestPath, captured.patchPath);
|
|
592
|
+
if (!patchCaptured) {
|
|
593
|
+
const reason = "worktree contains changes that are not represented by a captured handoff patch";
|
|
594
|
+
return {
|
|
595
|
+
index: worktree.index,
|
|
596
|
+
path: worktree.path,
|
|
597
|
+
branch: worktree.branch,
|
|
598
|
+
worktreeRemoved: false,
|
|
599
|
+
branchRemoved: false,
|
|
600
|
+
preserved: true,
|
|
601
|
+
reason,
|
|
602
|
+
errors: [...errors, `cleanup refused: ${reason}; preserved ${worktree.path}`],
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (hasWork && intent.kind === "discard") {
|
|
607
|
+
const decision = resolveAuthorityDecision({ action: "discardWorktree", policy: intent.authorization.policy });
|
|
608
|
+
const authorized = decision === "auto" || (decision === "confirm" && intent.authorization.kind === "confirmed");
|
|
609
|
+
if (!authorized) {
|
|
610
|
+
const reason = decision === "forbid"
|
|
611
|
+
? "authority policy forbids worktree discard"
|
|
612
|
+
: "worktree discard requires explicit user confirmation";
|
|
613
|
+
return {
|
|
614
|
+
index: worktree.index,
|
|
615
|
+
path: worktree.path,
|
|
616
|
+
branch: worktree.branch,
|
|
617
|
+
worktreeRemoved: false,
|
|
618
|
+
branchRemoved: false,
|
|
619
|
+
preserved: true,
|
|
620
|
+
reason,
|
|
621
|
+
errors: [...errors, `cleanup refused: ${reason}; preserved ${worktree.path}`],
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
522
626
|
try {
|
|
523
|
-
runGitChecked(
|
|
627
|
+
runGitChecked(setup.cwd, ["worktree", "remove", "--force", worktree.path]);
|
|
524
628
|
worktreeRemoved = true;
|
|
525
629
|
} catch (error) {
|
|
526
630
|
errors.push(`worktree removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
527
631
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
632
|
+
if (worktreeRemoved) {
|
|
633
|
+
try {
|
|
634
|
+
runGitChecked(setup.cwd, ["branch", "-D", worktree.branch]);
|
|
635
|
+
branchRemoved = true;
|
|
636
|
+
} catch (error) {
|
|
637
|
+
errors.push(`branch removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
638
|
+
}
|
|
533
639
|
}
|
|
534
640
|
return {
|
|
535
641
|
index: worktree.index,
|
|
@@ -569,7 +675,7 @@ export function createWorktrees(cwd: string, runId: string, count: number, optio
|
|
|
569
675
|
cwd: repo.toplevel,
|
|
570
676
|
worktrees,
|
|
571
677
|
baseCommit: repo.baseCommit,
|
|
572
|
-
});
|
|
678
|
+
}, { kind: "setup-rollback" });
|
|
573
679
|
throw error;
|
|
574
680
|
}
|
|
575
681
|
|
|
@@ -602,13 +708,17 @@ export function diffWorktrees(setup: WorktreeSetup, agents: string[], diffsDir:
|
|
|
602
708
|
}
|
|
603
709
|
}
|
|
604
710
|
|
|
711
|
+
setup.capturedDiffs = diffs;
|
|
605
712
|
return diffs;
|
|
606
713
|
}
|
|
607
714
|
|
|
608
|
-
export function cleanupWorktrees(
|
|
715
|
+
export function cleanupWorktrees(
|
|
716
|
+
setup: WorktreeSetup,
|
|
717
|
+
intent: WorktreeCleanupIntent = { kind: "preserve", ...(setup.capturedDiffs ? { capturedDiffs: setup.capturedDiffs } : {}) },
|
|
718
|
+
): WorktreeCleanupReport {
|
|
609
719
|
const tasks: WorktreeCleanupTask[] = [];
|
|
610
720
|
for (let index = setup.worktrees.length - 1; index >= 0; index--) {
|
|
611
|
-
tasks.push(cleanupSingleWorktree(setup
|
|
721
|
+
tasks.push(cleanupSingleWorktree(setup, setup.worktrees[index]!, intent));
|
|
612
722
|
}
|
|
613
723
|
tasks.sort((left, right) => left.index - right.index);
|
|
614
724
|
const errors: string[] = [];
|
|
@@ -8,18 +8,40 @@ type AccessibleDirOptions = {
|
|
|
8
8
|
retryDirectoryErrors?: boolean;
|
|
9
9
|
retryDelaysMs?: readonly number[];
|
|
10
10
|
wait?: (delayMs: number) => void;
|
|
11
|
+
pid?: number;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export function ensureAccessibleDir(dirPath: string, options: AccessibleDirOptions = {}):
|
|
14
|
+
export function ensureAccessibleDir(dirPath: string, options: AccessibleDirOptions = {}): string {
|
|
14
15
|
const fsImpl = options.fs ?? fs;
|
|
16
|
+
const pid = options.pid ?? process.pid;
|
|
15
17
|
const retryDirectoryErrors = options.retryDirectoryErrors ?? process.platform === "win32";
|
|
16
18
|
const retryDelaysMs = retryDirectoryErrors ? options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS : [];
|
|
17
19
|
const wait = options.wait ?? waitForFileSystemRetry;
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const mkdirWithRetry = (target: string): void => {
|
|
22
|
+
runFileSystemOperationWithRetry(() => {
|
|
23
|
+
fsImpl.mkdirSync(target, { recursive: true });
|
|
24
|
+
}, { retryDelaysMs, wait });
|
|
25
|
+
};
|
|
26
|
+
const accessWithRetry = (target: string): void => {
|
|
27
|
+
runFileSystemOperationWithRetry(() => {
|
|
28
|
+
fsImpl.accessSync(target, fs.constants.R_OK | fs.constants.W_OK);
|
|
29
|
+
}, { retryDelaysMs, wait });
|
|
30
|
+
};
|
|
31
|
+
const fallbackPath = (): string => {
|
|
32
|
+
const fallback = `${dirPath}-${pid}`;
|
|
33
|
+
mkdirWithRetry(fallback);
|
|
34
|
+
accessWithRetry(fallback);
|
|
35
|
+
return fallback;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
mkdirWithRetry(dirPath);
|
|
40
|
+
accessWithRetry(dirPath);
|
|
41
|
+
return dirPath;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
44
|
+
if (code !== "EPERM" && code !== "EACCES") throw error;
|
|
45
|
+
return fallbackPath();
|
|
46
|
+
}
|
|
25
47
|
}
|
package/src/shared/artifacts.ts
CHANGED
|
@@ -1,10 +1,135 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { TEMP_ARTIFACTS_DIR, type ArtifactPaths, type ArtifactDirPreference } from "./types.ts";
|
|
3
|
+
import { CHAIN_RUNS_DIR, TEMP_ARTIFACTS_DIR, type ArtifactPaths, type ArtifactDirPreference } from "./types.ts";
|
|
4
4
|
import { getAgentDir } from "./utils.ts";
|
|
5
5
|
const CLEANUP_MARKER_FILE = ".last-cleanup";
|
|
6
6
|
const PROJECT_ARTIFACT_ROOT = ".pi-subagents";
|
|
7
7
|
|
|
8
|
+
const PROJECT_ARTIFACT_PATHS = [
|
|
9
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/output.md`,
|
|
10
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_input.md`,
|
|
11
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_output.md`,
|
|
12
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker.jsonl`,
|
|
13
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_transcript.jsonl`,
|
|
14
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_meta.json`,
|
|
15
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/progress/run/progress.md`,
|
|
16
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/outputs/output.md`,
|
|
17
|
+
`${PROJECT_ARTIFACT_ROOT}/artifacts/outputs/run/output.md`,
|
|
18
|
+
`${PROJECT_ARTIFACT_ROOT}/chain-runs/run.json`,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function globMatchesPath(pattern: string, filePath: string): boolean {
|
|
22
|
+
let expression = "^";
|
|
23
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
24
|
+
const character = pattern[index];
|
|
25
|
+
if (character === undefined) break;
|
|
26
|
+
if (character === "*" && pattern[index + 1] === "*") {
|
|
27
|
+
index += 1;
|
|
28
|
+
if (pattern[index + 1] === "/") {
|
|
29
|
+
index += 1;
|
|
30
|
+
expression += "(?:.*/)?";
|
|
31
|
+
} else {
|
|
32
|
+
expression += ".*";
|
|
33
|
+
}
|
|
34
|
+
} else if (character === "*") {
|
|
35
|
+
expression += "[^/]*";
|
|
36
|
+
} else if (character === "?") {
|
|
37
|
+
expression += "[^/]";
|
|
38
|
+
} else if (character === "[") {
|
|
39
|
+
const end = pattern.indexOf("]", index + 1);
|
|
40
|
+
if (end === -1) expression += "\\[";
|
|
41
|
+
else {
|
|
42
|
+
expression += pattern.slice(index, end + 1);
|
|
43
|
+
index = end;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
expression += character.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return new RegExp(`${expression}$`).test(filePath);
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizePattern(pattern: string): string {
|
|
57
|
+
return pattern.replace(/^\.\//, "").replace(/^\//, "").replace(/\/+$/, "");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function patternMatchesArtifactPath(pattern: string, artifactPath: string): boolean {
|
|
61
|
+
const normalized = normalizePattern(pattern);
|
|
62
|
+
return normalized === PROJECT_ARTIFACT_ROOT
|
|
63
|
+
|| normalized === "*"
|
|
64
|
+
|| artifactPath.startsWith(`${normalized}/`)
|
|
65
|
+
|| globMatchesPath(normalized, artifactPath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function patternMatchesProjectArtifacts(pattern: string): boolean {
|
|
69
|
+
return PROJECT_ARTIFACT_PATHS.some((artifactPath) => patternMatchesArtifactPath(pattern, artifactPath));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ignoreFileExcludesProjectArtifacts(filePath: string): boolean {
|
|
73
|
+
if (!fs.existsSync(filePath)) return false;
|
|
74
|
+
try {
|
|
75
|
+
const excluded = new Set<string>();
|
|
76
|
+
for (const rawLine of fs.readFileSync(filePath, "utf-8").split(/\r?\n/)) {
|
|
77
|
+
const line = rawLine.trim();
|
|
78
|
+
if (!line || line.startsWith("#")) continue;
|
|
79
|
+
const negated = line.startsWith("!");
|
|
80
|
+
const pattern = negated ? line.slice(1) : line;
|
|
81
|
+
for (const artifactPath of PROJECT_ARTIFACT_PATHS) {
|
|
82
|
+
if (!patternMatchesArtifactPath(pattern, artifactPath)) continue;
|
|
83
|
+
if (negated) excluded.delete(artifactPath);
|
|
84
|
+
else excluded.add(artifactPath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return excluded.size === PROJECT_ARTIFACT_PATHS.length;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function filesIncludeProjectArtifacts(files: unknown): boolean | undefined {
|
|
94
|
+
if (!Array.isArray(files)) return undefined;
|
|
95
|
+
const included = new Set<string>();
|
|
96
|
+
for (const entry of files) {
|
|
97
|
+
if (typeof entry !== "string") continue;
|
|
98
|
+
const negated = entry.startsWith("!");
|
|
99
|
+
const pattern = negated ? entry.slice(1) : entry;
|
|
100
|
+
for (const artifactPath of PROJECT_ARTIFACT_PATHS) {
|
|
101
|
+
if (!patternMatchesArtifactPath(pattern, artifactPath)) continue;
|
|
102
|
+
if (negated) included.delete(artifactPath);
|
|
103
|
+
else included.add(artifactPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return included.size > 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Returns a package-publishing warning when project artifacts can enter npm packages. */
|
|
110
|
+
export function getProjectArtifactPackagingWarning(cwd: string): string | undefined {
|
|
111
|
+
const packagePath = path.join(cwd, "package.json");
|
|
112
|
+
if (!fs.existsSync(packagePath)) return undefined;
|
|
113
|
+
|
|
114
|
+
let packageJson: Record<string, unknown>;
|
|
115
|
+
try {
|
|
116
|
+
const parsed: unknown = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
117
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
118
|
+
packageJson = parsed as Record<string, unknown>;
|
|
119
|
+
} catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const filesIncludeArtifacts = filesIncludeProjectArtifacts(packageJson.files);
|
|
124
|
+
if (filesIncludeArtifacts === false) return undefined;
|
|
125
|
+
|
|
126
|
+
const npmIgnorePath = path.join(cwd, ".npmignore");
|
|
127
|
+
const ignorePath = fs.existsSync(npmIgnorePath) ? npmIgnorePath : path.join(cwd, ".gitignore");
|
|
128
|
+
if (filesIncludeArtifacts === undefined && ignoreFileExcludesProjectArtifacts(ignorePath)) return undefined;
|
|
129
|
+
|
|
130
|
+
return "Project-scoped subagent artifacts can be included when this package is published. Add '.pi-subagents/' to .npmignore, restrict package.json files, or set artifactDir to 'session' or 'temp'.";
|
|
131
|
+
}
|
|
132
|
+
|
|
8
133
|
export function getProjectSubagentsDir(cwd: string): string {
|
|
9
134
|
return path.join(cwd, PROJECT_ARTIFACT_ROOT);
|
|
10
135
|
}
|
|
@@ -17,6 +142,21 @@ export function getProjectChainRunsDir(cwd: string): string {
|
|
|
17
142
|
return path.join(getProjectSubagentsDir(cwd), "chain-runs");
|
|
18
143
|
}
|
|
19
144
|
|
|
145
|
+
export function getChainRunsDir(
|
|
146
|
+
projectCwd: string,
|
|
147
|
+
dirPreference: ArtifactDirPreference = "project",
|
|
148
|
+
): string {
|
|
149
|
+
switch (dirPreference) {
|
|
150
|
+
case "project":
|
|
151
|
+
return getProjectChainRunsDir(projectCwd);
|
|
152
|
+
case "session":
|
|
153
|
+
case "temp":
|
|
154
|
+
return CHAIN_RUNS_DIR;
|
|
155
|
+
default:
|
|
156
|
+
throw new Error(`Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected "project", "session", or "temp".`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
20
160
|
export function getArtifactsDir(
|
|
21
161
|
sessionFile: string | null,
|
|
22
162
|
projectCwd?: string,
|
|
@@ -61,6 +201,7 @@ export function ensureArtifactsDir(dir: string): void {
|
|
|
61
201
|
}
|
|
62
202
|
|
|
63
203
|
export function writeArtifact(filePath: string, content: string): void {
|
|
204
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
64
205
|
fs.writeFileSync(filePath, content, "utf-8");
|
|
65
206
|
}
|
|
66
207
|
|
|
@@ -78,6 +219,7 @@ export function formatOutputArtifactContent(input: {
|
|
|
78
219
|
}
|
|
79
220
|
|
|
80
221
|
export function writeMetadata(filePath: string, metadata: object): void {
|
|
222
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
81
223
|
fs.writeFileSync(filePath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
82
224
|
}
|
|
83
225
|
|
|
@@ -77,8 +77,9 @@ function isUnsafeAnthropicThinkingBlock(message: BranchSessionEntry["message"],
|
|
|
77
77
|
const isAnthropic = provider === "anthropic" || api === "anthropic-messages" || model.startsWith("anthropic/");
|
|
78
78
|
if (block.type === "redacted_thinking") return true;
|
|
79
79
|
if (block.type !== "thinking" || !isAnthropic) return false;
|
|
80
|
-
const
|
|
81
|
-
|
|
80
|
+
const record = block as Record<string, unknown>;
|
|
81
|
+
const signature = "thinkingSignature" in record ? record.thinkingSignature : "signature" in record ? record.signature : undefined;
|
|
82
|
+
return record.redacted === true || (typeof signature === "string" && signature.length > 0);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
function createEntryId(entries: BranchSessionEntry[]): string {
|
|
@@ -38,6 +38,7 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
|
|
|
38
38
|
packageName: agent.packageName,
|
|
39
39
|
filePath: agent.filePath,
|
|
40
40
|
fileContentDigest: fileDigest(agent.filePath),
|
|
41
|
+
runner: agent.runner,
|
|
41
42
|
systemPrompt: agent.systemPrompt,
|
|
42
43
|
systemPromptMode: agent.systemPromptMode,
|
|
43
44
|
inheritProjectContext: agent.inheritProjectContext,
|
package/src/shared/settings.ts
CHANGED
|
@@ -120,6 +120,16 @@ export interface CheckpointStep {
|
|
|
120
120
|
message?: string;
|
|
121
121
|
phase?: string;
|
|
122
122
|
label?: string;
|
|
123
|
+
agent?: string;
|
|
124
|
+
task?: string;
|
|
125
|
+
as?: string;
|
|
126
|
+
output?: string | false;
|
|
127
|
+
outputMode?: OutputMode;
|
|
128
|
+
reads?: string[] | false;
|
|
129
|
+
progress?: boolean;
|
|
130
|
+
skill?: string | string[] | false;
|
|
131
|
+
skills?: string[] | false;
|
|
132
|
+
model?: string;
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
/** Parallel step: multiple agents running concurrently */
|