pi-extensible-workflows 3.2.0 → 3.3.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/README.md +1 -30
- package/dist/src/agent-execution.d.ts +4 -0
- package/dist/src/agent-execution.js +146 -60
- package/dist/src/bundles.d.ts +53 -0
- package/dist/src/bundles.js +457 -0
- package/dist/src/cli.d.ts +3 -1
- package/dist/src/cli.js +146 -21
- package/dist/src/doctor-cleanup.js +4 -2
- package/dist/src/eval-capture-extension.js +16 -1
- package/dist/src/host.d.ts +9 -6
- package/dist/src/host.js +375 -129
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +2 -1
- package/dist/src/persistence.d.ts +40 -1
- package/dist/src/persistence.js +51 -0
- package/dist/src/session-inspector.d.ts +12 -2
- package/dist/src/session-inspector.js +36 -2
- package/dist/src/types.d.ts +18 -0
- package/dist/src/types.js +1 -0
- package/dist/src/validation.js +8 -2
- package/dist/src/workflow-evals.d.ts +7 -1
- package/dist/src/workflow-evals.js +23 -3
- package/evals/cases/recovery-completed-worktree.yaml +17 -0
- package/evals/cases/recovery-failed-run.yaml +14 -0
- package/package.json +1 -1
- package/skills/pi-extensible-workflows/SKILL.md +2 -2
- package/src/agent-execution.ts +102 -30
- package/src/bundles.ts +471 -0
- package/src/cli.ts +118 -21
- package/src/doctor-cleanup.ts +3 -2
- package/src/eval-capture-extension.ts +15 -1
- package/src/host.ts +329 -124
- package/src/index.ts +3 -2
- package/src/persistence.ts +53 -1
- package/src/session-inspector.ts +36 -4
- package/src/types.ts +12 -5
- package/src/validation.ts +6 -2
- package/src/workflow-evals.ts +24 -3
package/dist/src/index.d.ts
CHANGED
|
@@ -6,9 +6,10 @@ export * from "./registry.js";
|
|
|
6
6
|
export * from "./execution.js";
|
|
7
7
|
export * from "./host.js";
|
|
8
8
|
export * from "./workflow-artifacts.js";
|
|
9
|
+
export * from "./bundles.js";
|
|
9
10
|
export { default } from "./host.js";
|
|
10
|
-
export { acquireSessionLease, hasLiveSessionLease, projectSessionsDirectory, projectStorageKey, RunStore, runsDirectory, SessionLease, structuralPath } from "./persistence.js";
|
|
11
|
-
export type { AwaitingCheckpoint, CompletedOperation, NativeSessionReference, PendingWorkflowDecision, PersistedOwnershipNode, PersistedRun, WorktreeReference } from "./persistence.js";
|
|
11
|
+
export { acquireSessionLease, hasLiveSessionLease, listPersistedSessionIds, projectSessionsDirectory, projectStorageKey, RunStore, runsDirectory, SessionLease, structuralPath } from "./persistence.js";
|
|
12
|
+
export type { AwaitingCheckpoint, CompletedOperation, NativeSessionReference, PendingWorkflowDecision, PersistedOwnershipNode, PersistedRun, RunSummary, RunSummaryAgent, RunSummaryArtifacts, WorktreeReference } from "./persistence.js";
|
|
12
13
|
export { FairAgentScheduler, WorkflowAgentExecutor } from "./agent-execution.js";
|
|
13
14
|
export type { AgentAttempt, AgentBudgetHooks, AgentExecutionOptions, AgentExecutionResult, AgentExecutionRoot, AgentProgress, AgentProviderFailure, AgentProviderRecovery, AgentToolCallProgress, AgentSetup, AgentSetupContext, AgentSetupHook, NativeSession, RegisteredAgentSetupHook, SessionFactory, SessionInput } from "./agent-execution.js";
|
|
14
15
|
export { doctor, doctorExitCode, formatDoctorReport } from "./doctor.js";
|
package/dist/src/index.js
CHANGED
|
@@ -6,8 +6,9 @@ export * from "./registry.js";
|
|
|
6
6
|
export * from "./execution.js";
|
|
7
7
|
export * from "./host.js";
|
|
8
8
|
export * from "./workflow-artifacts.js";
|
|
9
|
+
export * from "./bundles.js";
|
|
9
10
|
export { default } from "./host.js";
|
|
10
|
-
export { acquireSessionLease, hasLiveSessionLease, projectSessionsDirectory, projectStorageKey, RunStore, runsDirectory, SessionLease, structuralPath } from "./persistence.js";
|
|
11
|
+
export { acquireSessionLease, hasLiveSessionLease, listPersistedSessionIds, projectSessionsDirectory, projectStorageKey, RunStore, runsDirectory, SessionLease, structuralPath } from "./persistence.js";
|
|
11
12
|
export { FairAgentScheduler, WorkflowAgentExecutor } from "./agent-execution.js";
|
|
12
13
|
export { doctor, doctorExitCode, formatDoctorReport } from "./doctor.js";
|
|
13
14
|
export { doctorCleanup, doctorCleanupExitCode, formatDoctorCleanupReport } from "./doctor-cleanup.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BudgetApprovalRequest, JsonValue, LaunchSnapshot, RunRecord, WorkflowRunEvent } from "./types.js";
|
|
1
|
+
import type { BudgetApprovalRequest, JsonValue, LaunchSnapshot, RunRecord, WorkflowBudgetUsage, WorkflowRunEvent } from "./types.js";
|
|
2
2
|
import type { OwnershipRecord } from "./agent-execution.js";
|
|
3
3
|
export interface NativeSessionReference {
|
|
4
4
|
sessionId: string;
|
|
@@ -14,6 +14,40 @@ export interface EffectiveSystemPrompt {
|
|
|
14
14
|
export interface PersistedRun extends RunRecord {
|
|
15
15
|
nativeSessions: readonly NativeSessionReference[];
|
|
16
16
|
}
|
|
17
|
+
export interface RunSummaryAgent {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
state: string;
|
|
22
|
+
role?: string;
|
|
23
|
+
attempts: number;
|
|
24
|
+
}
|
|
25
|
+
export interface RunSummaryArtifacts {
|
|
26
|
+
runDirectory: string;
|
|
27
|
+
statePath: string;
|
|
28
|
+
journalPath: string;
|
|
29
|
+
snapshotPath: string;
|
|
30
|
+
workflowPath: string;
|
|
31
|
+
resultPath: string;
|
|
32
|
+
summaryPath: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RunSummary {
|
|
35
|
+
schemaVersion: 1;
|
|
36
|
+
runId: string;
|
|
37
|
+
sessionId: string;
|
|
38
|
+
workflowName: string;
|
|
39
|
+
state: RunRecord["state"];
|
|
40
|
+
createdAt: string;
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
terminalAt?: string;
|
|
43
|
+
usage: WorkflowBudgetUsage;
|
|
44
|
+
agents: readonly RunSummaryAgent[];
|
|
45
|
+
error?: RunRecord["error"];
|
|
46
|
+
failedAt?: string;
|
|
47
|
+
replayablePaths: readonly string[];
|
|
48
|
+
incompletePaths: readonly string[];
|
|
49
|
+
artifacts: RunSummaryArtifacts;
|
|
50
|
+
}
|
|
17
51
|
export interface CompletedOperation {
|
|
18
52
|
path: string;
|
|
19
53
|
value: JsonValue;
|
|
@@ -41,6 +75,7 @@ export interface BorrowedWorktreeBinding {
|
|
|
41
75
|
export declare function projectStorageKey(cwd: string): string;
|
|
42
76
|
export declare function projectSessionsDirectory(cwd: string, home?: string): string;
|
|
43
77
|
export declare function runsDirectory(cwd: string, sessionId: string, home?: string): string;
|
|
78
|
+
export declare function listPersistedSessionIds(cwd: string, home?: string): Promise<string[]>;
|
|
44
79
|
export declare function hasLiveSessionLease(cwd: string, sessionId: string, home?: string): Promise<boolean>;
|
|
45
80
|
export declare class SessionLease {
|
|
46
81
|
#private;
|
|
@@ -63,6 +98,7 @@ export declare class RunStore {
|
|
|
63
98
|
readonly directory: string;
|
|
64
99
|
private journalWrite;
|
|
65
100
|
private stateWrite;
|
|
101
|
+
private summaryWrite;
|
|
66
102
|
private worktreeWrite;
|
|
67
103
|
private borrowedWorktreeWrite;
|
|
68
104
|
private snapshotWrite;
|
|
@@ -70,11 +106,14 @@ export declare class RunStore {
|
|
|
70
106
|
private systemPromptWrite;
|
|
71
107
|
constructor(cwd: string, sessionId: string, runId: string, home?: string);
|
|
72
108
|
create(run: PersistedRun, snapshot: Readonly<LaunchSnapshot>): Promise<void>;
|
|
109
|
+
private refreshSummary;
|
|
110
|
+
private refreshSummaryBestEffort;
|
|
73
111
|
isComplete(): Promise<boolean>;
|
|
74
112
|
load(): Promise<{
|
|
75
113
|
run: PersistedRun;
|
|
76
114
|
snapshot: Readonly<LaunchSnapshot>;
|
|
77
115
|
}>;
|
|
116
|
+
loadSummary(): Promise<RunSummary>;
|
|
78
117
|
saveState(run: PersistedRun): Promise<void>;
|
|
79
118
|
updateState(update: (run: PersistedRun) => PersistedRun | Promise<PersistedRun>): Promise<PersistedRun>;
|
|
80
119
|
saveSnapshot(snapshot: Readonly<LaunchSnapshot>): Promise<void>;
|
package/dist/src/persistence.js
CHANGED
|
@@ -7,6 +7,16 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
import { WorkflowError } from "./types.js";
|
|
9
9
|
import { loadLaunchSnapshot } from "./utils.js";
|
|
10
|
+
const TERMINAL_SUMMARY_STATES = new Set(["completed", "failed", "stopped"]);
|
|
11
|
+
const EMPTY_USAGE = { tokens: 0, costUsd: 0, durationMs: 0, agentLaunches: 0 };
|
|
12
|
+
function summaryArtifacts(directory) { return { runDirectory: directory, statePath: join(directory, "state.json"), journalPath: join(directory, "journal.json"), snapshotPath: join(directory, "snapshot.json"), workflowPath: join(directory, "workflow.js"), resultPath: join(directory, "result.json"), summaryPath: join(directory, "summary.json") }; }
|
|
13
|
+
function summaryFromRun(run, directory, journal, previous, fallbackCreatedAt, now = new Date().toISOString()) {
|
|
14
|
+
const createdAt = typeof previous?.createdAt === "string" ? previous.createdAt : fallbackCreatedAt;
|
|
15
|
+
const failedAt = run.failedAt ?? run.error?.failedAt;
|
|
16
|
+
const replayablePaths = [...new Set([...(run.retry?.completedPaths ?? []), ...Object.keys(journal.completed)])];
|
|
17
|
+
const incompletePaths = [...new Set([...(run.retry?.incompletePaths ?? []), ...(failedAt ? [failedAt] : [])])];
|
|
18
|
+
return { schemaVersion: 1, runId: run.id, sessionId: run.sessionId, workflowName: run.workflowName, state: run.state, createdAt, updatedAt: now, ...(previous?.terminalAt || TERMINAL_SUMMARY_STATES.has(run.state) ? { terminalAt: previous?.terminalAt ?? now } : {}), usage: { ...EMPTY_USAGE, ...(run.usage ?? {}) }, agents: run.agents.map(({ id, name, label, state, role, attempts }) => ({ id, name, ...(label ? { label } : {}), state, ...(role ? { role } : {}), attempts })), ...(run.error ? { error: run.error } : {}), ...(failedAt ? { failedAt } : {}), replayablePaths, incompletePaths, artifacts: summaryArtifacts(directory) };
|
|
19
|
+
}
|
|
10
20
|
const execute = promisify(execFile);
|
|
11
21
|
const gitIdentity = {
|
|
12
22
|
GIT_AUTHOR_NAME: "pi-extensible-workflows", GIT_AUTHOR_EMAIL: "pi-extensible-workflows@localhost", GIT_COMMITTER_NAME: "pi-extensible-workflows", GIT_COMMITTER_EMAIL: "pi-extensible-workflows@localhost",
|
|
@@ -24,6 +34,17 @@ export function projectSessionsDirectory(cwd, home = homedir()) {
|
|
|
24
34
|
export function runsDirectory(cwd, sessionId, home = homedir()) {
|
|
25
35
|
return join(projectSessionsDirectory(cwd, home), safePart(sessionId), "runs");
|
|
26
36
|
}
|
|
37
|
+
export async function listPersistedSessionIds(cwd, home = homedir()) {
|
|
38
|
+
try {
|
|
39
|
+
const entries = await readdir(projectSessionsDirectory(cwd, home), { withFileTypes: true });
|
|
40
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map(({ name }) => name);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code === "ENOENT")
|
|
44
|
+
return [];
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
27
48
|
const SESSION_OWNER_FILE = "owner.json";
|
|
28
49
|
const SESSION_OWNER_WRITE_GRACE_MS = 30_000;
|
|
29
50
|
const RUN_CREATE_TEMP = /^\.([a-zA-Z0-9._-]+)\.(\d+)\.[0-9a-f-]+\.tmp$/;
|
|
@@ -236,6 +257,7 @@ export class RunStore {
|
|
|
236
257
|
journalWrite = Promise.resolve();
|
|
237
258
|
// ponytail: serializes one RunStore instance; cross-process run sharing remains unsupported.
|
|
238
259
|
stateWrite = Promise.resolve();
|
|
260
|
+
summaryWrite = Promise.resolve();
|
|
239
261
|
worktreeWrite = Promise.resolve();
|
|
240
262
|
borrowedWorktreeWrite = Promise.resolve();
|
|
241
263
|
snapshotWrite = Promise.resolve();
|
|
@@ -265,6 +287,7 @@ export class RunStore {
|
|
|
265
287
|
await atomicJson(join(temporary, "borrowed-worktrees.json"), []);
|
|
266
288
|
await atomicJson(join(temporary, "state.json"), run);
|
|
267
289
|
await atomicJson(join(temporary, "system-prompts.json"), { version: 1, entries: [] });
|
|
290
|
+
await atomicJson(join(temporary, "summary.json"), summaryFromRun(run, this.directory, { completed: {} }, undefined, new Date().toISOString()));
|
|
268
291
|
await rename(temporary, this.directory);
|
|
269
292
|
}
|
|
270
293
|
catch (error) {
|
|
@@ -272,6 +295,18 @@ export class RunStore {
|
|
|
272
295
|
throw error;
|
|
273
296
|
}
|
|
274
297
|
}
|
|
298
|
+
async refreshSummary() {
|
|
299
|
+
const write = this.summaryWrite.then(async () => {
|
|
300
|
+
const run = await json(join(this.directory, "state.json"));
|
|
301
|
+
const journal = await json(join(this.directory, "journal.json"));
|
|
302
|
+
const previous = await json(join(this.directory, "summary.json")).catch(() => undefined);
|
|
303
|
+
const fallbackCreatedAt = await stat(join(this.directory, "state.json")).then((value) => new Date(value.mtimeMs).toISOString());
|
|
304
|
+
await atomicJson(join(this.directory, "summary.json"), summaryFromRun(run, this.directory, journal, previous, fallbackCreatedAt));
|
|
305
|
+
});
|
|
306
|
+
this.summaryWrite = write.catch(() => undefined);
|
|
307
|
+
await write;
|
|
308
|
+
}
|
|
309
|
+
refreshSummaryBestEffort() { void this.refreshSummary().catch(() => undefined); }
|
|
275
310
|
async isComplete() {
|
|
276
311
|
try {
|
|
277
312
|
await Promise.all([access(join(this.directory, "snapshot.json")), access(join(this.directory, "journal.json")), access(join(this.directory, "ownership.json")), access(join(this.directory, "state.json"))]);
|
|
@@ -288,11 +323,25 @@ export class RunStore {
|
|
|
288
323
|
throw new WorkflowError("RESUME_INCOMPATIBLE", "Persisted run belongs to another cwd or Pi session");
|
|
289
324
|
return { run, snapshot: loadLaunchSnapshot(await json(join(this.directory, "snapshot.json"))) };
|
|
290
325
|
}
|
|
326
|
+
async loadSummary() {
|
|
327
|
+
await this.stateWrite;
|
|
328
|
+
await this.journalWrite;
|
|
329
|
+
await this.summaryWrite;
|
|
330
|
+
const run = await json(join(this.directory, "state.json"));
|
|
331
|
+
const journal = await json(join(this.directory, "journal.json"));
|
|
332
|
+
const previous = await json(join(this.directory, "summary.json")).catch(() => undefined);
|
|
333
|
+
const [stateStat, journalStat] = await Promise.all([stat(join(this.directory, "state.json")), stat(join(this.directory, "journal.json"))]);
|
|
334
|
+
const fallbackCreatedAt = new Date(stateStat.mtimeMs).toISOString();
|
|
335
|
+
const previousUpdatedAt = previous?.updatedAt === undefined ? Number.NaN : Date.parse(previous.updatedAt);
|
|
336
|
+
const updatedAt = new Date(Math.max(stateStat.mtimeMs, journalStat.mtimeMs, Number.isNaN(previousUpdatedAt) ? 0 : previousUpdatedAt)).toISOString();
|
|
337
|
+
return summaryFromRun(run, this.directory, journal, previous, fallbackCreatedAt, updatedAt);
|
|
338
|
+
}
|
|
291
339
|
async saveState(run) {
|
|
292
340
|
const write = this.stateWrite.then(async () => {
|
|
293
341
|
if (resolve(run.cwd) !== this.cwd || run.sessionId !== this.sessionId || run.id !== this.runId)
|
|
294
342
|
throw new WorkflowError("INTERNAL_ERROR", "Run identity does not match its session-scoped store");
|
|
295
343
|
await atomicJson(join(this.directory, "state.json"), run);
|
|
344
|
+
this.refreshSummaryBestEffort();
|
|
296
345
|
});
|
|
297
346
|
this.stateWrite = write.catch(() => undefined);
|
|
298
347
|
await write;
|
|
@@ -307,6 +356,7 @@ export class RunStore {
|
|
|
307
356
|
if (resolve(result.cwd) !== this.cwd || result.sessionId !== this.sessionId || result.id !== this.runId)
|
|
308
357
|
throw new WorkflowError("INTERNAL_ERROR", "Run identity does not match its session-scoped store");
|
|
309
358
|
await atomicJson(join(this.directory, "state.json"), result);
|
|
359
|
+
this.refreshSummaryBestEffort();
|
|
310
360
|
});
|
|
311
361
|
this.stateWrite = write.catch(() => undefined);
|
|
312
362
|
await write;
|
|
@@ -351,6 +401,7 @@ export class RunStore {
|
|
|
351
401
|
journal.awaiting ??= {};
|
|
352
402
|
result = await update(journal);
|
|
353
403
|
await atomicJson(journalPath, journal);
|
|
404
|
+
this.refreshSummaryBestEffort();
|
|
354
405
|
});
|
|
355
406
|
this.journalWrite = write.catch(() => undefined);
|
|
356
407
|
await write;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SessionEntry, type SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type AgentSetupSummary, type ModelSpec, type StaticWorkflowCall } from "./index.js";
|
|
3
|
-
import { type PersistedRun } from "./persistence.js";
|
|
3
|
+
import { type PersistedRun, type RunSummary } from "./persistence.js";
|
|
4
4
|
export interface ModelUsage {
|
|
5
5
|
model: string;
|
|
6
6
|
cost: number;
|
|
@@ -62,11 +62,21 @@ export interface InspectorViewState {
|
|
|
62
62
|
selected: number;
|
|
63
63
|
scroll: number;
|
|
64
64
|
}
|
|
65
|
+
export type InspectMode = "tui" | "json" | "summary";
|
|
66
|
+
export interface PersistedSessionSummary {
|
|
67
|
+
schemaVersion: 1;
|
|
68
|
+
cwd: string;
|
|
69
|
+
sessionId: string;
|
|
70
|
+
runs: readonly RunSummary[];
|
|
71
|
+
}
|
|
65
72
|
export declare function transcriptLines(entries: readonly SessionEntry[]): string[];
|
|
66
73
|
export declare function transcriptFileLines(path: string): string[];
|
|
67
74
|
export declare function matchSession(query: string, sessions: readonly SessionInfo[]): SessionInfo;
|
|
68
75
|
export declare function loadSessionReport(path: string, home?: string): Promise<SessionReport>;
|
|
69
76
|
export declare function renderInspector(report: SessionReport, state: InspectorViewState, width?: number, height?: number, highlighter?: (script: string) => string[]): string[];
|
|
77
|
+
export declare function loadPersistedSessionSummary(cwd: string, sessionId: string, home?: string, failedOnly?: boolean): Promise<PersistedSessionSummary>;
|
|
78
|
+
export declare function loadPersistedSummaries(cwd: string, sessionId: string | undefined, home?: string, failedOnly?: boolean): Promise<readonly PersistedSessionSummary[]>;
|
|
79
|
+
export declare function formatPersistedRunSummary(summary: RunSummary, sessionId?: string): string;
|
|
70
80
|
export declare function showSessionInspector(report: SessionReport): Promise<void>;
|
|
71
81
|
export declare function resolveSession(query: string, sessionDir?: string | undefined): Promise<SessionInfo>;
|
|
72
|
-
export declare function runSessionInspector(sessionId?: string): Promise<void>;
|
|
82
|
+
export declare function runSessionInspector(sessionId?: string, mode?: InspectMode, cwd?: string, home?: string, write?: (text: string) => void, failedOnly?: boolean): Promise<void>;
|
|
@@ -5,7 +5,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { stdin, stdout } from "node:process";
|
|
6
6
|
import { highlightCode, initTheme, SessionManager, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { formatBudgetStatus, inspectWorkflowScript } from "./index.js";
|
|
8
|
-
import { listRunIds, RunStore } from "./persistence.js";
|
|
8
|
+
import { listPersistedSessionIds, listRunIds, RunStore } from "./persistence.js";
|
|
9
9
|
function text(content) {
|
|
10
10
|
if (typeof content === "string")
|
|
11
11
|
return content;
|
|
@@ -346,6 +346,28 @@ export function renderInspector(report, state, width = 80, height = 24, highligh
|
|
|
346
346
|
const scroll = Math.max(0, Math.min(state.scroll, Math.max(0, fitted.length - room)));
|
|
347
347
|
return [...header, ...fitted.slice(scroll, scroll + room), ...footer].slice(0, height);
|
|
348
348
|
}
|
|
349
|
+
export async function loadPersistedSessionSummary(cwd, sessionId, home = homedir(), failedOnly = false) {
|
|
350
|
+
const runs = [];
|
|
351
|
+
for (const runId of (await listRunIds(cwd, sessionId, home)).sort()) {
|
|
352
|
+
try {
|
|
353
|
+
const summary = await new RunStore(cwd, sessionId, runId, home).loadSummary();
|
|
354
|
+
if (!failedOnly || summary.state === "failed")
|
|
355
|
+
runs.push(summary);
|
|
356
|
+
}
|
|
357
|
+
catch { /* Ignore corrupt or concurrently removed runs. */ }
|
|
358
|
+
}
|
|
359
|
+
return { schemaVersion: 1, cwd, sessionId, runs };
|
|
360
|
+
}
|
|
361
|
+
export async function loadPersistedSummaries(cwd, sessionId, home = homedir(), failedOnly = false) {
|
|
362
|
+
const sessionIds = sessionId ? [sessionId] : (await listPersistedSessionIds(cwd, home)).sort();
|
|
363
|
+
const sessions = await Promise.all(sessionIds.map((id) => loadPersistedSessionSummary(cwd, id, home, failedOnly)));
|
|
364
|
+
return sessionId || !failedOnly ? sessions : sessions.filter((session) => session.runs.length > 0);
|
|
365
|
+
}
|
|
366
|
+
export function formatPersistedRunSummary(summary, sessionId = summary.sessionId) {
|
|
367
|
+
const counts = summary.agents.reduce((result, agent) => { result[agent.state] = (result[agent.state] ?? 0) + 1; return result; }, {});
|
|
368
|
+
const status = Object.entries(counts).map(([state, count]) => `${state}=${String(count)}`).join(", ") || "agents=0";
|
|
369
|
+
return `${sessionId} ${summary.runId} ${summary.workflowName} ${summary.state} ${status} updated=${summary.updatedAt}`;
|
|
370
|
+
}
|
|
349
371
|
function nextState(current, key, workflowCount) {
|
|
350
372
|
if (current.view === "list") {
|
|
351
373
|
if (key === "up")
|
|
@@ -410,7 +432,19 @@ async function askSessionId() {
|
|
|
410
432
|
export async function resolveSession(query, sessionDir = process.env.PI_CODING_AGENT_SESSION_DIR) {
|
|
411
433
|
return matchSession(query, await SessionManager.listAll(sessionDir));
|
|
412
434
|
}
|
|
413
|
-
export async function runSessionInspector(sessionId) {
|
|
435
|
+
export async function runSessionInspector(sessionId, mode = "tui", cwd = process.cwd(), home = homedir(), write = (text) => { stdout.write(text); }, failedOnly = false) {
|
|
436
|
+
if (mode !== "tui") {
|
|
437
|
+
const sessions = await loadPersistedSummaries(cwd, sessionId?.trim() || undefined, home, failedOnly);
|
|
438
|
+
if (mode === "json") {
|
|
439
|
+
const value = sessionId?.trim() ? sessions[0] : undefined;
|
|
440
|
+
write(`${JSON.stringify(value ?? { schemaVersion: 1, cwd, sessions })}\n`);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const emptyLabel = failedOnly ? "no failed runs" : "no persisted runs";
|
|
444
|
+
const lines = sessions.flatMap((session) => session.runs.length ? session.runs.map((run) => formatPersistedRunSummary(run, session.sessionId)) : [`${session.sessionId} (${emptyLabel})`]);
|
|
445
|
+
write(`${lines.length ? lines.join("\n") : failedOnly ? "No failed workflow runs." : "No persisted workflow runs."}\n`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
414
448
|
const query = sessionId?.trim() || await askSessionId();
|
|
415
449
|
if (!query)
|
|
416
450
|
throw new Error("Session ID is required.");
|
package/dist/src/types.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ export interface BudgetApprovalRequest {
|
|
|
83
83
|
export interface WorkflowErrorShape {
|
|
84
84
|
code: WorkflowErrorCode;
|
|
85
85
|
message: string;
|
|
86
|
+
failedAt?: string;
|
|
86
87
|
}
|
|
87
88
|
export interface WorkflowEventBase {
|
|
88
89
|
runId: string;
|
|
@@ -201,6 +202,7 @@ export interface AgentActivity {
|
|
|
201
202
|
kind: "reasoning" | "tool" | "text";
|
|
202
203
|
text: string;
|
|
203
204
|
}
|
|
205
|
+
export declare const WORKFLOW_AGENT_STALL_THRESHOLD_MS: number;
|
|
204
206
|
export interface AgentAccounting {
|
|
205
207
|
input: number;
|
|
206
208
|
output: number;
|
|
@@ -279,6 +281,14 @@ export interface AgentRecord {
|
|
|
279
281
|
state: "running" | "completed" | "failed";
|
|
280
282
|
}[];
|
|
281
283
|
activity?: AgentActivity | undefined;
|
|
284
|
+
lastEventAt?: number;
|
|
285
|
+
}
|
|
286
|
+
export type WorkflowDeliveryMode = "foreground" | "background";
|
|
287
|
+
export type WorkflowDeliveryStatus = "attached" | "pending" | "delivered";
|
|
288
|
+
export interface WorkflowRunDelivery {
|
|
289
|
+
mode: WorkflowDeliveryMode;
|
|
290
|
+
state: WorkflowDeliveryStatus;
|
|
291
|
+
toolCallId?: string;
|
|
282
292
|
}
|
|
283
293
|
export interface WorkflowRunEvent {
|
|
284
294
|
type: string;
|
|
@@ -306,26 +316,32 @@ export interface RunRecord {
|
|
|
306
316
|
phase?: string;
|
|
307
317
|
phaseHistory?: readonly WorkflowPhaseRecord[];
|
|
308
318
|
agents: readonly AgentRecord[];
|
|
319
|
+
activeShells?: number;
|
|
309
320
|
error?: WorkflowErrorShape;
|
|
321
|
+
failedAt?: string;
|
|
310
322
|
budget?: WorkflowBudget;
|
|
311
323
|
budgetVersion?: number;
|
|
312
324
|
usage?: WorkflowBudgetUsage;
|
|
313
325
|
budgetEvents?: readonly BudgetEvent[];
|
|
314
326
|
events?: readonly WorkflowRunEvent[];
|
|
327
|
+
delivery?: WorkflowRunDelivery;
|
|
315
328
|
}
|
|
316
329
|
export declare const LAUNCH_SNAPSHOT_IDENTITY_VERSION = 5;
|
|
330
|
+
export type WorkflowLaunchMode = "foreground" | "background";
|
|
317
331
|
export interface AgentDefinition {
|
|
318
332
|
prompt?: string;
|
|
319
333
|
description?: string;
|
|
320
334
|
model?: string;
|
|
321
335
|
thinking?: NonNullable<ModelSpec["thinking"]>;
|
|
322
336
|
tools?: readonly string[];
|
|
337
|
+
overrideSystemPrompt?: boolean;
|
|
323
338
|
disabledAgentResources?: AgentResourceExclusions;
|
|
324
339
|
}
|
|
325
340
|
export interface LaunchSnapshot {
|
|
326
341
|
identityVersion?: number;
|
|
327
342
|
launchKind?: "inline" | "function";
|
|
328
343
|
functionName?: string;
|
|
344
|
+
launchMode?: WorkflowLaunchMode;
|
|
329
345
|
script: string;
|
|
330
346
|
args: JsonValue;
|
|
331
347
|
metadata: WorkflowMetadata;
|
|
@@ -449,8 +465,10 @@ export interface SessionInput {
|
|
|
449
465
|
agentDir?: string;
|
|
450
466
|
customTools?: SessionCustomTools;
|
|
451
467
|
resultTool?: ToolDefinition;
|
|
468
|
+
systemPrompt?: string;
|
|
452
469
|
systemPromptAppend?: string;
|
|
453
470
|
extensionFactories?: InlineExtension[];
|
|
471
|
+
additionalSkillPaths?: readonly string[];
|
|
454
472
|
resourcePolicy?: AgentResourcePolicy;
|
|
455
473
|
options?: AgentOptions;
|
|
456
474
|
}
|
package/dist/src/types.js
CHANGED
|
@@ -16,6 +16,7 @@ export const ERROR_CODES = [
|
|
|
16
16
|
"RUN_OWNED", "REGISTRY_FROZEN", "GLOBAL_COLLISION", "MISSING_WORKFLOW", "RPC_LIMIT_EXCEEDED", "SHELL_FAILED", "AGENT_TIMEOUT", "AGENT_FAILED", "RESULT_INVALID",
|
|
17
17
|
"CANCELLED", "WORKER_UNRESPONSIVE", "WORKTREE_FAILED", "RESUME_INCOMPATIBLE", "BUDGET_EXHAUSTED", "INTERNAL_ERROR",
|
|
18
18
|
];
|
|
19
|
+
export const WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1000;
|
|
19
20
|
export const LAUNCH_SNAPSHOT_IDENTITY_VERSION = 5;
|
|
20
21
|
export class WorkflowError extends Error {
|
|
21
22
|
code;
|
package/dist/src/validation.js
CHANGED
|
@@ -180,7 +180,7 @@ export function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
|
180
180
|
return { prompt: content };
|
|
181
181
|
const meta = {};
|
|
182
182
|
for (const line of content.slice(4, end).split("\n")) {
|
|
183
|
-
const match = /^(model|thinking|tools|description)\s*:\s*(.+)$/.exec(line.trim());
|
|
183
|
+
const match = /^(model|thinking|tools|description|overrideSystemPrompt|override_system_prompt|is_system_prompt)\s*:\s*(.+)$/.exec(line.trim());
|
|
184
184
|
if (match?.[1] && match[2])
|
|
185
185
|
meta[match[1]] = match[2].trim();
|
|
186
186
|
}
|
|
@@ -197,6 +197,9 @@ export function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
|
197
197
|
definition.thinking = thinking;
|
|
198
198
|
if (tools)
|
|
199
199
|
definition.tools = tools;
|
|
200
|
+
const overrideSystemPrompt = meta.overrideSystemPrompt ?? meta.override_system_prompt ?? meta.is_system_prompt;
|
|
201
|
+
if (overrideSystemPrompt)
|
|
202
|
+
definition.overrideSystemPrompt = overrideSystemPrompt === "true";
|
|
200
203
|
return definition;
|
|
201
204
|
}
|
|
202
205
|
const normalized = content.replace(/\r\n?/g, "\n");
|
|
@@ -212,16 +215,19 @@ export function parseRoleMarkdown(content, strict = false, rolePath) {
|
|
|
212
215
|
if (!object(parsed.frontmatter))
|
|
213
216
|
fail("INVALID_METADATA", "Role frontmatter must be an object");
|
|
214
217
|
const { model, thinking, tools, description, disabledAgentResources } = parsed.frontmatter;
|
|
218
|
+
const overrideSystemPrompt = parsed.frontmatter.overrideSystemPrompt ?? parsed.frontmatter.override_system_prompt ?? parsed.frontmatter.is_system_prompt;
|
|
215
219
|
if (model !== undefined && (typeof model !== "string" || model.trim() === ""))
|
|
216
220
|
fail("INVALID_METADATA", "Role model must be a non-empty string");
|
|
217
221
|
if (thinking !== undefined && (typeof thinking !== "string" || !["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(thinking)))
|
|
218
222
|
fail("INVALID_METADATA", `Invalid role thinking level: ${typeof thinking === "string" ? thinking : typeof thinking}`);
|
|
219
223
|
if (description !== undefined && (typeof description !== "string" || description.trim() === "" || description.length > 1024 || /[\r\n]/.test(description)))
|
|
220
224
|
fail("INVALID_METADATA", "Role description must be a non-empty single-line string of at most 1024 characters");
|
|
225
|
+
if (overrideSystemPrompt !== undefined && typeof overrideSystemPrompt !== "boolean")
|
|
226
|
+
fail("INVALID_METADATA", "Role overrideSystemPrompt must be a boolean");
|
|
221
227
|
if (tools !== undefined && (!Array.isArray(tools) || tools.some((tool) => typeof tool !== "string" || tool.trim() === "")))
|
|
222
228
|
fail("INVALID_METADATA", "Role tools must be an array of non-empty strings");
|
|
223
229
|
const normalizedResources = validateAgentResourceExclusions(disabledAgentResources, rolePath ?? "<role>", "INVALID_METADATA");
|
|
224
|
-
return { prompt: parsed.body, ...(typeof description === "string" ? { description: description.trim() } : {}), ...(typeof model === "string" ? { model: model.trim() } : {}), ...(typeof thinking === "string" ? { thinking: thinking } : {}), ...(Array.isArray(tools) ? { tools: tools.map((tool) => tool.trim()) } : {}), ...(normalizedResources ? { disabledAgentResources: normalizedResources } : {}) };
|
|
230
|
+
return { prompt: parsed.body, ...(typeof description === "string" ? { description: description.trim() } : {}), ...(typeof model === "string" ? { model: model.trim() } : {}), ...(typeof thinking === "string" ? { thinking: thinking } : {}), ...(Array.isArray(tools) ? { tools: tools.map((tool) => tool.trim()) } : {}), ...(typeof overrideSystemPrompt === "boolean" ? { overrideSystemPrompt } : {}), ...(normalizedResources ? { disabledAgentResources: normalizedResources } : {}) };
|
|
225
231
|
}
|
|
226
232
|
const ROLE_DIRECTORY = "pi-extensible-workflows";
|
|
227
233
|
export function workflowRoleDirectories(agentDir = getAgentDir()) {
|
|
@@ -74,6 +74,10 @@ export interface ParentToolResult {
|
|
|
74
74
|
isError?: boolean;
|
|
75
75
|
text?: string;
|
|
76
76
|
}
|
|
77
|
+
export interface ParentToolCall {
|
|
78
|
+
name: string;
|
|
79
|
+
arguments: JsonValue;
|
|
80
|
+
}
|
|
77
81
|
export interface ParentUsage {
|
|
78
82
|
input: number;
|
|
79
83
|
output: number;
|
|
@@ -216,13 +220,14 @@ export interface EvalCaseResult {
|
|
|
216
220
|
realWorkflowAgentsLaunched: number | null;
|
|
217
221
|
};
|
|
218
222
|
}
|
|
219
|
-
export declare const SAFE_PARENT_EVAL_TOOLS: readonly ["read", "grep", "find", "bash", "workflow"];
|
|
223
|
+
export declare const SAFE_PARENT_EVAL_TOOLS: readonly ["read", "grep", "find", "bash", "workflow", "workflow_retry"];
|
|
220
224
|
export declare function validateWorkflowEvalCases(values: readonly unknown[], source?: string): readonly WorkflowEvalCase[];
|
|
221
225
|
export declare function loadWorkflowEvalCases(directory?: string): readonly WorkflowEvalCase[];
|
|
222
226
|
export declare const INITIAL_WORKFLOW_EVAL_CASES: readonly WorkflowEvalCase[];
|
|
223
227
|
export declare function matchesJsonResult(shape: JsonResultShape, value: JsonValue): boolean;
|
|
224
228
|
export declare function matchesOutputSchema(shape: OutputSchemaShape, schema: JsonSchema): boolean;
|
|
225
229
|
export declare function extractParentOracle(entries: readonly unknown[]): ParentOracle;
|
|
230
|
+
export declare function extractParentToolCalls(oracle: ParentOracle): ParentToolCall[];
|
|
226
231
|
export declare function extractParentOracleFile(path: string): ParentOracle;
|
|
227
232
|
export declare function extractCapturedWorkflows(oracle: ParentOracle): CapturedWorkflowCall[];
|
|
228
233
|
export declare function captureValidationReports(oracle: ParentOracle, calls: readonly CapturedWorkflowCall[]): {
|
|
@@ -231,6 +236,7 @@ export declare function captureValidationReports(oracle: ParentOracle, calls: re
|
|
|
231
236
|
verified: boolean;
|
|
232
237
|
};
|
|
233
238
|
export declare function evalExpectationErrors(oracle: ParentOracle, expectations: EvalExpectations): string[];
|
|
239
|
+
export declare function recoverySelectionErrors(evalCase: Pick<WorkflowEvalCase, "id">, oracle: ParentOracle): string[];
|
|
234
240
|
export declare function replayExpectationErrors(calls: readonly CapturedWorkflowCall[], reports: readonly ReplayReport[], expectations: EvalExpectations): string[];
|
|
235
241
|
export declare function staticExpectationResults(calls: readonly CapturedWorkflowCall[], expectations: EvalExpectations): CriterionResult[];
|
|
236
242
|
export declare function selectStaticCandidate(calls: readonly CapturedWorkflowCall[], validations: readonly ProductionValidationReport[], expectations: EvalExpectations, requiredCount?: number): {
|
|
@@ -10,7 +10,7 @@ import { CAPTURE_ERROR_PREFIX, CAPTURE_IDENTITY, resolveWorkflowSkillPath } from
|
|
|
10
10
|
export { resolveWorkflowSkillPath } from "./eval-capture-extension.js";
|
|
11
11
|
import { ERROR_CODES, inspectWorkflowScript, isObject, loadAgentDefinitions, runWorkflow, WORKFLOW_CALL_KINDS, WorkflowError } from "./index.js";
|
|
12
12
|
const CASE_PROCESS_GRACE_MS = 1_000;
|
|
13
|
-
export const SAFE_PARENT_EVAL_TOOLS = Object.freeze(["read", "grep", "find", "bash", "workflow"]);
|
|
13
|
+
export const SAFE_PARENT_EVAL_TOOLS = Object.freeze(["read", "grep", "find", "bash", "workflow", "workflow_retry"]);
|
|
14
14
|
const EVAL_MODEL_TOKEN = "$EVAL_MODEL";
|
|
15
15
|
const semantic = (description) => [{ id: "intent", description }];
|
|
16
16
|
const JSON_RESULT_TYPES = ["null", "boolean", "number", "integer", "string", "array", "object"];
|
|
@@ -367,6 +367,13 @@ export function extractParentOracle(entries) {
|
|
|
367
367
|
}
|
|
368
368
|
return { assistantBatches: batches, workflowToolResults, skillReads, ...(firstSignificantAction ? { firstSignificantAction } : {}), ...(parentToolSequence[0] ? { firstTool: parentToolSequence[0] } : {}), firstBatchToolSequence: firstBatch?.tools ?? [], toolsBeforeFirstWorkflow: firstWorkflowIndex < 0 ? parentToolSequence : parentToolSequence.slice(0, firstWorkflowIndex), firstWorkflowBatchToolSequence: firstWorkflowBatch?.tools ?? [], parentToolSequence, workflowCallCount: parentToolSequence.filter((name) => name === "workflow").length, usage: { ...totals, models: [...modelCosts].map(([model, cost]) => ({ model, cost })) } };
|
|
369
369
|
}
|
|
370
|
+
export function extractParentToolCalls(oracle) {
|
|
371
|
+
return oracle.assistantBatches.flatMap(({ parts }) => parts.flatMap((part) => {
|
|
372
|
+
if (!isObject(part) || part.type !== "toolCall" || typeof part.name !== "string")
|
|
373
|
+
return [];
|
|
374
|
+
return [{ name: part.name, arguments: isJson(part.arguments) ? part.arguments : null }];
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
370
377
|
export function extractParentOracleFile(path) {
|
|
371
378
|
const entries = readFileSync(path, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
372
379
|
return extractParentOracle(entries);
|
|
@@ -434,6 +441,19 @@ export function evalExpectationErrors(oracle, expectations) {
|
|
|
434
441
|
}
|
|
435
442
|
return errors;
|
|
436
443
|
}
|
|
444
|
+
const RECOVERY_SELECTIONS = {
|
|
445
|
+
"recovery-failed-run": { tool: "workflow_retry", arguments: { runId: "failed-run-42" } },
|
|
446
|
+
"recovery-completed-worktree": { tool: "workflow", arguments: { name: "borrow-worktree", script: "return true;", parentRunId: "completed-run-42" } },
|
|
447
|
+
};
|
|
448
|
+
export function recoverySelectionErrors(evalCase, oracle) {
|
|
449
|
+
const expected = RECOVERY_SELECTIONS[evalCase.id];
|
|
450
|
+
if (!expected)
|
|
451
|
+
return [];
|
|
452
|
+
const actual = extractParentToolCalls(oracle);
|
|
453
|
+
if (actual.length === 1 && actual[0]?.name === expected.tool && equalJson(actual[0].arguments, expected.arguments))
|
|
454
|
+
return [];
|
|
455
|
+
return [`${evalCase.id} parent tool calls were ${JSON.stringify(actual)}; expected ${JSON.stringify([expected])}`];
|
|
456
|
+
}
|
|
437
457
|
export function replayExpectationErrors(calls, reports, expectations) {
|
|
438
458
|
const errors = [];
|
|
439
459
|
const staticCalls = calls.flatMap((call) => { try {
|
|
@@ -862,7 +882,7 @@ function semanticJudgePrompt(evalCase, calls, cwd, home) {
|
|
|
862
882
|
} }));
|
|
863
883
|
const roleText = [...usedRoles].map((role) => `${role}: ${roles[role]?.description ?? "no description"}`).join("\n") || "none";
|
|
864
884
|
const docs = "agent(prompt, options) delegates; shell(command, options) runs a deterministic host command and returns exitCode/stdout/stderr; parallel(name, tasks) runs independent tasks concurrently; pipeline(name, items, stages) applies ordered stages; prompt(template, data) carries values into prompts. A role owns model/thinking/tools policy.";
|
|
865
|
-
return `Judge whether the captured workflow design satisfies each criterion. Do not execute it. Return only JSON: {"criteria":[{"id":"criterion id","pass":true,"evidence":"specific script evidence"}]}.\n\nOriginal request:\n${evalCase.prompt}\n\nCriteria:\n${JSON.stringify(evalCase.semanticCriteria ?? [])}\n\nDSL:\n${docs}\n\nRelevant roles:\n${roleText}\n\nCaptured workflow
|
|
885
|
+
return `Judge whether the captured workflow design satisfies each criterion. Do not execute it. Return only JSON: {"criteria":[{"id":"criterion id","pass":true,"evidence":"specific script evidence"}]}.\n\nOriginal request:\n${evalCase.prompt}\n\nCriteria:\n${JSON.stringify(evalCase.semanticCriteria ?? [])}\n\nDSL:\n${docs}\n\nRelevant roles:\n${roleText}\n\nCaptured workflow call(s):\n${calls.map((call, index) => `--- ${String(index)} ---\nArguments:\n${JSON.stringify(call.arguments)}\nScript:\n${call.script ?? "<missing>"}`).join("\n")}`;
|
|
866
886
|
}
|
|
867
887
|
async function runSemanticJudge(input, calls, cwd, home, sessionDir, maxCost) {
|
|
868
888
|
const args = ["--offline", "--no-extensions", "--no-skills", "--no-context-files", "--no-tools", "--mode", "json", "--session-dir", sessionDir, "--session-id", randomUUID()];
|
|
@@ -1045,7 +1065,7 @@ export async function captureEvalCase(input) {
|
|
|
1045
1065
|
const parentUsageThroughCandidate = usageThroughCandidate(oracle, workflows, selection.callIndices);
|
|
1046
1066
|
const parentAccounting = parentUsageThroughCandidate ?? oracle.usage;
|
|
1047
1067
|
const unsafeTool = oracle.parentToolSequence.find((tool) => !SAFE_PARENT_EVAL_TOOLS.includes(tool));
|
|
1048
|
-
const errors = [...evalExpectationErrors(oracle, input.case.expectations), ...validation.errors, ...(unsafeTool ? [`parent tool is outside the safe eval allowlist: ${unsafeTool}`] : [])];
|
|
1068
|
+
const errors = [...evalExpectationErrors(oracle, input.case.expectations), ...recoverySelectionErrors(input.case, oracle), ...validation.errors, ...(unsafeTool ? [`parent tool is outside the safe eval allowlist: ${unsafeTool}`] : [])];
|
|
1049
1069
|
if (requiredCount > 0 && selection.callIndices.length === 0)
|
|
1050
1070
|
errors.push("Catastrophic validity failure: no production-valid workflow candidate satisfied static expectations.");
|
|
1051
1071
|
let judge;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
id: recovery-completed-worktree
|
|
2
|
+
prompt: >-
|
|
3
|
+
The completed workflow run ID is "completed-run-42". You need a new workflow to do named worktree work,
|
|
4
|
+
so launch the workflow with exactly parentRunId "completed-run-42". Use exactly the workflow arguments {"name":"borrow-worktree","script":"return true;","parentRunId":"completed-run-42"}.
|
|
5
|
+
Do not call workflow_retry because the source is completed and parentRunId only borrows the named worktree; it does not replay or resume.
|
|
6
|
+
maxCost: 0.1
|
|
7
|
+
expectations:
|
|
8
|
+
firstTool: workflow
|
|
9
|
+
firstBatchToolSequence:
|
|
10
|
+
startsWith: [workflow]
|
|
11
|
+
parentToolSequence:
|
|
12
|
+
startsWith: [workflow]
|
|
13
|
+
workflowCallCount: 1
|
|
14
|
+
expectedWorkflowCalls: 1
|
|
15
|
+
semanticCriteria:
|
|
16
|
+
- id: parent-run-id
|
|
17
|
+
description: The captured workflow call has exactly name borrow-worktree, script return true;, and parentRunId completed-run-42; it does not call workflow_retry.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
id: recovery-failed-run
|
|
2
|
+
prompt: >-
|
|
3
|
+
A workflow failure diagnostic identifies the persisted failed run ID "failed-run-42" and says the exact next action is
|
|
4
|
+
workflow_retry({ runId: "failed-run-42" }). Select that recovery tool now with exactly {"runId":"failed-run-42"}.
|
|
5
|
+
Do not launch a new workflow, use parentRunId, or answer with prose.
|
|
6
|
+
maxCost: 0.1
|
|
7
|
+
expectedWorkflowCalls: 0
|
|
8
|
+
expectations:
|
|
9
|
+
firstTool: workflow_retry
|
|
10
|
+
firstBatchToolSequence:
|
|
11
|
+
equals: [workflow_retry]
|
|
12
|
+
parentToolSequence:
|
|
13
|
+
equals: [workflow_retry]
|
|
14
|
+
workflowCallCount: 0
|
package/package.json
CHANGED
|
@@ -32,9 +32,9 @@ Inline launches require a non-empty `name`. Registered function launches must om
|
|
|
32
32
|
```json
|
|
33
33
|
{ "workflow": "workflowName", "args": { "issue": 42 } }
|
|
34
34
|
```
|
|
35
|
-
Recovery map: `agent(..., { retries })` reruns one agent call in the same run for transient failures; `workflow_retry({ runId })` replays a failed run into a child; `workflow_resume({ runId, budget? })` continues a `budget_exhausted` run; `parentRunId` on a new launch only borrows named worktrees and never replays or resumes. Use each only for its stated case.
|
|
35
|
+
Recovery map: `agent(..., { retries })` reruns one agent call in the same run for transient failures; `workflow_retry({ runId, foreground? })` replays a failed run into a child; `workflow_resume({ runId, budget?, foreground? })` continues a `budget_exhausted` run; recovery inherits the source snapshot's foreground/background launch mode, while legacy snapshots without `launchMode` recover in background; set `foreground: true` or `false` to override it; `parentRunId` on a new launch only borrows named worktrees and never replays or resumes. Use each only for its stated case.
|
|
36
36
|
For an explicitly failed run, use the exact `runId` from failure diagnostics with `workflow_retry({ runId })`: diagnostics list replayable and incomplete paths, artifacts, and valid named worktrees; the tool creates a linked child, replays completed agent, shell, function, and checkpoint operations, and executes incomplete work. External side effects before failure are not guaranteed exactly once.
|
|
37
|
-
Inside the workflow, read `args.issue` (`args` is `null` when omitted). `workflow_stop` requires the exact run ID; foreground
|
|
37
|
+
Inside the workflow, read `args.issue` (`args` is `null` when omitted). `workflow_stop` requires the exact run ID; foreground launches and foreground recovery retain their terminal value and completed `runId`, while background launches and recovery return `runId` immediately and deliver completion or failure as a follow-up. Retry versus per-agent `retries` and `workflow_resume` is always explicit.
|
|
38
38
|
Inspect tool `workflow_catalog` result at least once before creating the first workflow for a task.
|
|
39
39
|
|
|
40
40
|
Workflow JavaScript has no imports, filesystem, network, process, or timers. Delegate that work to agents. `shell(command, options)` is the trusted host RPC for deterministic gates: it inherits the workflow or active-worktree cwd, merges string `env` overrides, and returns `{ exitCode, stdout, stderr }`; nonzero exits are results, but launch failures and timeouts fail with `SHELL_FAILED`.
|