pi-extensible-workflows 3.2.0 → 3.4.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 +2 -31
- package/dist/src/agent-execution.d.ts +67 -9
- package/dist/src/agent-execution.js +385 -141
- 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 +156 -22
- package/dist/src/doctor-cleanup.js +68 -31
- package/dist/src/eval-capture-extension.js +16 -1
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +29 -13
- package/dist/src/host.d.ts +12 -9
- package/dist/src/host.js +525 -195
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.js +3 -2
- package/dist/src/persistence.d.ts +43 -8
- package/dist/src/persistence.js +54 -0
- package/dist/src/registry.d.ts +3 -2
- package/dist/src/registry.js +16 -4
- package/dist/src/session-inspector.d.ts +12 -2
- package/dist/src/session-inspector.js +50 -24
- package/dist/src/types.d.ts +141 -60
- package/dist/src/types.js +1 -0
- package/dist/src/validation.js +28 -7
- package/dist/src/workflow-artifacts.d.ts +1 -0
- package/dist/src/workflow-artifacts.js +1 -0
- 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 +13 -5
- package/src/agent-execution.ts +302 -107
- package/src/bundles.ts +471 -0
- package/src/cli.ts +130 -22
- package/src/doctor-cleanup.ts +38 -4
- package/src/eval-capture-extension.ts +15 -1
- package/src/execution.ts +27 -15
- package/src/host.ts +454 -175
- package/src/index.ts +5 -4
- package/src/persistence.ts +58 -4
- package/src/registry.ts +14 -5
- package/src/session-inspector.ts +49 -26
- package/src/types.ts +55 -30
- package/src/validation.ts +19 -6
- package/src/workflow-artifacts.ts +1 -0
- package/src/workflow-evals.ts +24 -3
package/dist/src/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CreateAgentSessionOptions, InlineExtension, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
export declare const RUN_STATES: readonly ["queued", "running", "pausing", "paused", "awaiting_input", "completed", "failed", "stopped", "interrupted", "budget_exhausted"];
|
|
3
3
|
export declare const AGENT_STATES: readonly ["queued", "running", "waiting_for_child", "paused", "retrying", "completed", "failed", "cancelled"];
|
|
4
4
|
export declare const WORKFLOW_CALL_KINDS: readonly ["agent", "parallel", "pipeline", "checkpoint", "phase", "withWorktree", "shell"];
|
|
@@ -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;
|
|
@@ -192,15 +193,16 @@ export interface AgentResourcePolicy {
|
|
|
192
193
|
global: AgentResourceExclusions;
|
|
193
194
|
project: AgentResourceExclusions;
|
|
194
195
|
effective: AgentResourceExclusions;
|
|
195
|
-
unmatchedSkills: string[];
|
|
196
|
-
unmatchedExtensions: string[];
|
|
197
|
-
excludedSkills?: string[];
|
|
198
|
-
excludedExtensions?: string[];
|
|
196
|
+
unmatchedSkills: readonly string[];
|
|
197
|
+
unmatchedExtensions: readonly string[];
|
|
198
|
+
excludedSkills?: readonly string[];
|
|
199
|
+
excludedExtensions?: readonly string[];
|
|
199
200
|
}
|
|
200
201
|
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;
|
|
@@ -222,22 +224,17 @@ export interface AgentSetupSummary {
|
|
|
222
224
|
unmatchedExtensions: readonly string[];
|
|
223
225
|
};
|
|
224
226
|
}
|
|
227
|
+
export interface AgentAttemptError {
|
|
228
|
+
code: string;
|
|
229
|
+
message: string;
|
|
230
|
+
}
|
|
225
231
|
export interface AgentAttemptSummary {
|
|
226
232
|
attempt: number;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
};
|
|
233
|
-
accounting: {
|
|
234
|
-
input: number;
|
|
235
|
-
output: number;
|
|
236
|
-
cacheRead: number;
|
|
237
|
-
cacheWrite: number;
|
|
238
|
-
cost: number;
|
|
239
|
-
};
|
|
240
|
-
setup?: AgentSetupSummary;
|
|
233
|
+
transport: string;
|
|
234
|
+
session?: WorkflowAgentSessionReference;
|
|
235
|
+
setup: AgentSetupSummary;
|
|
236
|
+
error?: AgentAttemptError;
|
|
237
|
+
accounting: AgentAccounting;
|
|
241
238
|
}
|
|
242
239
|
export interface WorkflowWorktreeCreatedEvent extends WorkflowEventBase {
|
|
243
240
|
owner: string;
|
|
@@ -250,6 +247,8 @@ export interface WorkflowWorktreeReference {
|
|
|
250
247
|
readonly branch: string;
|
|
251
248
|
}
|
|
252
249
|
export interface AgentRecord {
|
|
250
|
+
systemPrompt?: string;
|
|
251
|
+
prompt?: string;
|
|
253
252
|
id: string;
|
|
254
253
|
name: string;
|
|
255
254
|
label?: string;
|
|
@@ -279,6 +278,14 @@ export interface AgentRecord {
|
|
|
279
278
|
state: "running" | "completed" | "failed";
|
|
280
279
|
}[];
|
|
281
280
|
activity?: AgentActivity | undefined;
|
|
281
|
+
lastEventAt?: number;
|
|
282
|
+
}
|
|
283
|
+
export type WorkflowDeliveryMode = "foreground" | "background";
|
|
284
|
+
export type WorkflowDeliveryStatus = "attached" | "pending" | "delivered";
|
|
285
|
+
export interface WorkflowRunDelivery {
|
|
286
|
+
mode: WorkflowDeliveryMode;
|
|
287
|
+
state: WorkflowDeliveryStatus;
|
|
288
|
+
toolCallId?: string;
|
|
282
289
|
}
|
|
283
290
|
export interface WorkflowRunEvent {
|
|
284
291
|
type: string;
|
|
@@ -301,31 +308,38 @@ export interface RunRecord {
|
|
|
301
308
|
cwd: string;
|
|
302
309
|
sessionId: string;
|
|
303
310
|
state: RunState;
|
|
311
|
+
agentSessions: readonly WorkflowAgentSessionReference[];
|
|
304
312
|
parentRunId?: string;
|
|
305
313
|
retry?: WorkflowRetryProvenance;
|
|
306
314
|
phase?: string;
|
|
307
315
|
phaseHistory?: readonly WorkflowPhaseRecord[];
|
|
308
316
|
agents: readonly AgentRecord[];
|
|
317
|
+
activeShells?: number;
|
|
309
318
|
error?: WorkflowErrorShape;
|
|
319
|
+
failedAt?: string;
|
|
310
320
|
budget?: WorkflowBudget;
|
|
311
321
|
budgetVersion?: number;
|
|
312
322
|
usage?: WorkflowBudgetUsage;
|
|
313
323
|
budgetEvents?: readonly BudgetEvent[];
|
|
314
324
|
events?: readonly WorkflowRunEvent[];
|
|
325
|
+
delivery?: WorkflowRunDelivery;
|
|
315
326
|
}
|
|
316
327
|
export declare const LAUNCH_SNAPSHOT_IDENTITY_VERSION = 5;
|
|
328
|
+
export type WorkflowLaunchMode = "foreground" | "background";
|
|
317
329
|
export interface AgentDefinition {
|
|
318
330
|
prompt?: string;
|
|
319
331
|
description?: string;
|
|
320
332
|
model?: string;
|
|
321
333
|
thinking?: NonNullable<ModelSpec["thinking"]>;
|
|
322
334
|
tools?: readonly string[];
|
|
335
|
+
overrideSystemPrompt?: boolean;
|
|
323
336
|
disabledAgentResources?: AgentResourceExclusions;
|
|
324
337
|
}
|
|
325
338
|
export interface LaunchSnapshot {
|
|
326
339
|
identityVersion?: number;
|
|
327
340
|
launchKind?: "inline" | "function";
|
|
328
341
|
functionName?: string;
|
|
342
|
+
launchMode?: WorkflowLaunchMode;
|
|
329
343
|
script: string;
|
|
330
344
|
args: JsonValue;
|
|
331
345
|
metadata: WorkflowMetadata;
|
|
@@ -397,47 +411,65 @@ export interface WorkflowVariable {
|
|
|
397
411
|
schema: JsonSchema;
|
|
398
412
|
resolve: (run: Readonly<WorkflowRunContext>) => Promise<JsonValue> | JsonValue;
|
|
399
413
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
content?: unknown;
|
|
403
|
-
stopReason?: string;
|
|
404
|
-
errorMessage?: string;
|
|
405
|
-
usage?: {
|
|
406
|
-
input: number;
|
|
407
|
-
output: number;
|
|
408
|
-
cacheRead: number;
|
|
409
|
-
cacheWrite: number;
|
|
410
|
-
cost: {
|
|
411
|
-
total: number;
|
|
412
|
-
};
|
|
413
|
-
};
|
|
414
|
-
};
|
|
415
|
-
type NativeSessionStats = Pick<SessionStats, "tokens" | "cost">;
|
|
416
|
-
export interface NativeSession {
|
|
414
|
+
export interface WorkflowAgentSessionReference {
|
|
415
|
+
readonly transport: string;
|
|
417
416
|
readonly sessionId: string;
|
|
418
|
-
readonly
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
readonly
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
417
|
+
readonly locator?: JsonValue;
|
|
418
|
+
}
|
|
419
|
+
export interface WorkflowAgentSessionStats {
|
|
420
|
+
readonly tokens: {
|
|
421
|
+
readonly input: number;
|
|
422
|
+
readonly output: number;
|
|
423
|
+
readonly cacheRead: number;
|
|
424
|
+
readonly cacheWrite: number;
|
|
425
|
+
readonly total: number;
|
|
426
426
|
};
|
|
427
|
-
readonly
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
427
|
+
readonly cost: number;
|
|
428
|
+
}
|
|
429
|
+
export interface WorkflowAgentMessage {
|
|
430
|
+
readonly role: string;
|
|
431
|
+
readonly content?: unknown;
|
|
432
|
+
readonly stopReason?: string;
|
|
433
|
+
readonly errorMessage?: string;
|
|
434
|
+
readonly usage?: {
|
|
435
|
+
readonly input: number;
|
|
436
|
+
readonly output: number;
|
|
437
|
+
readonly cacheRead: number;
|
|
438
|
+
readonly cacheWrite: number;
|
|
439
|
+
readonly cost: {
|
|
440
|
+
readonly total: number;
|
|
432
441
|
};
|
|
433
442
|
};
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
443
|
+
}
|
|
444
|
+
export interface WorkflowAgentSessionState {
|
|
445
|
+
readonly model: ModelSpec;
|
|
446
|
+
readonly thinking?: ModelSpec["thinking"];
|
|
447
|
+
readonly tools: readonly string[];
|
|
448
|
+
readonly systemPrompt?: string;
|
|
449
|
+
}
|
|
450
|
+
export interface WorkflowAgentSessionEvent {
|
|
451
|
+
readonly type: string;
|
|
452
|
+
readonly state?: Readonly<WorkflowAgentSessionState>;
|
|
453
|
+
readonly message?: WorkflowAgentMessage;
|
|
454
|
+
readonly assistantMessageEvent?: {
|
|
455
|
+
readonly type: string;
|
|
456
|
+
};
|
|
457
|
+
readonly toolCallId?: string;
|
|
458
|
+
readonly toolName?: string;
|
|
459
|
+
readonly isError?: boolean;
|
|
460
|
+
}
|
|
461
|
+
export interface WorkflowAgentTurnResult {
|
|
462
|
+
readonly assistant?: WorkflowAgentMessage;
|
|
463
|
+
}
|
|
464
|
+
export interface WorkflowAgentSession {
|
|
465
|
+
readonly reference: WorkflowAgentSessionReference;
|
|
466
|
+
getState(): Readonly<WorkflowAgentSessionState>;
|
|
467
|
+
getSessionStats(): WorkflowAgentSessionStats;
|
|
468
|
+
subscribe(listener: (event: WorkflowAgentSessionEvent) => void): () => void;
|
|
469
|
+
prompt(text: string): Promise<WorkflowAgentTurnResult>;
|
|
470
|
+
steer(text: string): Promise<void>;
|
|
471
|
+
abort(): Promise<void>;
|
|
472
|
+
dispose(): Promise<void>;
|
|
441
473
|
}
|
|
442
474
|
type SessionTools = NonNullable<CreateAgentSessionOptions["tools"]>;
|
|
443
475
|
type SessionCustomTools = NonNullable<CreateAgentSessionOptions["customTools"]>;
|
|
@@ -449,17 +481,44 @@ export interface SessionInput {
|
|
|
449
481
|
agentDir?: string;
|
|
450
482
|
customTools?: SessionCustomTools;
|
|
451
483
|
resultTool?: ToolDefinition;
|
|
484
|
+
systemPrompt?: string;
|
|
452
485
|
systemPromptAppend?: string;
|
|
453
486
|
extensionFactories?: InlineExtension[];
|
|
487
|
+
additionalSkillPaths?: readonly string[];
|
|
454
488
|
resourcePolicy?: AgentResourcePolicy;
|
|
455
489
|
options?: AgentOptions;
|
|
456
490
|
}
|
|
457
|
-
export
|
|
491
|
+
export interface PreparedAgentSession {
|
|
492
|
+
readonly cwd: string;
|
|
493
|
+
readonly model: ModelSpec;
|
|
494
|
+
readonly tools: readonly string[];
|
|
495
|
+
readonly sessionLabel: string;
|
|
496
|
+
readonly agentDir?: string;
|
|
497
|
+
readonly customTools?: readonly ToolDefinition[];
|
|
498
|
+
readonly resultTool?: ToolDefinition;
|
|
499
|
+
readonly options?: Readonly<Record<string, JsonValue>>;
|
|
500
|
+
readonly systemPrompt?: string;
|
|
501
|
+
readonly systemPromptAppend?: string;
|
|
502
|
+
readonly extensionFactories?: readonly InlineExtension[];
|
|
503
|
+
readonly additionalSkillPaths?: readonly string[];
|
|
504
|
+
readonly resourcePolicy?: Readonly<AgentResourcePolicy>;
|
|
505
|
+
}
|
|
506
|
+
export interface AgentTransportContext {
|
|
507
|
+
readonly run: Readonly<WorkflowRunContext>;
|
|
508
|
+
readonly identity: Readonly<AgentIdentity>;
|
|
509
|
+
readonly attempt: number;
|
|
510
|
+
readonly signal: AbortSignal;
|
|
511
|
+
}
|
|
512
|
+
export interface AgentTransport {
|
|
513
|
+
readonly id: string;
|
|
514
|
+
createSession(prepared: Readonly<PreparedAgentSession>, context: Readonly<AgentTransportContext>): Promise<WorkflowAgentSession>;
|
|
515
|
+
}
|
|
458
516
|
export interface AgentSetup {
|
|
459
517
|
prompt: string;
|
|
460
518
|
options: AgentOptions;
|
|
461
519
|
sessionInput: SessionInput;
|
|
462
|
-
|
|
520
|
+
prepared: Readonly<PreparedAgentSession>;
|
|
521
|
+
transport: AgentTransport;
|
|
463
522
|
}
|
|
464
523
|
export interface AgentSetupContext {
|
|
465
524
|
readonly run: Readonly<WorkflowRunContext>;
|
|
@@ -485,11 +544,32 @@ export interface WorkflowRoleDirectoryRegistration {
|
|
|
485
544
|
path: string;
|
|
486
545
|
extension: WorkflowExtensionMetadata;
|
|
487
546
|
}
|
|
547
|
+
export interface AgentAttemptActionUi {
|
|
548
|
+
notify(message: string, level?: "info" | "warning" | "error"): void;
|
|
549
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
550
|
+
select(title: string, options: readonly string[]): Promise<string | undefined>;
|
|
551
|
+
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
552
|
+
}
|
|
553
|
+
export interface AgentAttemptActionContext {
|
|
554
|
+
readonly run: Readonly<RunRecord>;
|
|
555
|
+
readonly agent: Readonly<AgentRecord>;
|
|
556
|
+
readonly attempt: Readonly<AgentAttemptSummary>;
|
|
557
|
+
readonly session?: WorkflowAgentSessionReference;
|
|
558
|
+
readonly liveSession?: WorkflowAgentSession;
|
|
559
|
+
readonly signal: AbortSignal;
|
|
560
|
+
readonly ui: Readonly<AgentAttemptActionUi>;
|
|
561
|
+
}
|
|
562
|
+
export interface AgentAttemptAction {
|
|
563
|
+
readonly label: string;
|
|
564
|
+
visible(context: Readonly<AgentAttemptActionContext>): boolean;
|
|
565
|
+
run(context: Readonly<AgentAttemptActionContext>): void | Promise<void>;
|
|
566
|
+
}
|
|
488
567
|
export interface WorkflowExtension extends WorkflowExtensionMetadata {
|
|
489
568
|
functions?: Readonly<Record<string, WorkflowFunction>>;
|
|
490
569
|
variables?: Readonly<Record<string, WorkflowVariable>>;
|
|
491
570
|
modelAliases?: Readonly<Record<string, WorkflowModelAlias>>;
|
|
492
571
|
agentSetupHooks?: Readonly<Record<string, AgentSetupHook>>;
|
|
572
|
+
agentAttemptActions?: Readonly<Record<string, AgentAttemptAction>>;
|
|
493
573
|
roleDirectories?: readonly (string | URL)[];
|
|
494
574
|
}
|
|
495
575
|
export interface WorkflowJournal {
|
|
@@ -506,8 +586,8 @@ export interface WorkflowFailureAgent {
|
|
|
506
586
|
role?: string;
|
|
507
587
|
structuralPath: readonly string[];
|
|
508
588
|
attempt: number;
|
|
509
|
-
|
|
510
|
-
|
|
589
|
+
transport?: string;
|
|
590
|
+
session?: WorkflowAgentSessionReference;
|
|
511
591
|
}
|
|
512
592
|
export interface WorkflowSiblingAgent {
|
|
513
593
|
id: string;
|
|
@@ -669,6 +749,7 @@ export interface WorkflowValidationParameters {
|
|
|
669
749
|
name?: string;
|
|
670
750
|
description?: string;
|
|
671
751
|
script?: string;
|
|
752
|
+
scriptPath?: string;
|
|
672
753
|
workflow?: string;
|
|
673
754
|
args?: unknown;
|
|
674
755
|
}
|
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()) {
|
|
@@ -819,10 +825,25 @@ export function validateWorkflowLaunchWithRegistry(params, context, registry) {
|
|
|
819
825
|
fail("INVALID_METADATA", "maxAgentLaunches has been removed; use budget.agentLaunches");
|
|
820
826
|
if (params.script !== undefined && params.workflow !== undefined)
|
|
821
827
|
fail("INVALID_METADATA", "Provide either script or workflow, not both");
|
|
828
|
+
if (params.scriptPath !== undefined && (typeof params.scriptPath !== "string" || !params.scriptPath.trim()))
|
|
829
|
+
fail("INVALID_METADATA", "scriptPath must be a non-empty path");
|
|
830
|
+
if ((params.script !== undefined || params.workflow !== undefined) && params.scriptPath !== undefined)
|
|
831
|
+
fail("INVALID_METADATA", "Provide either script, scriptPath, or workflow, not more than one");
|
|
832
|
+
const scriptPath = typeof params.scriptPath === "string" ? params.scriptPath.trim() : undefined;
|
|
833
|
+
let fileScript;
|
|
834
|
+
if (scriptPath !== undefined) {
|
|
835
|
+
try {
|
|
836
|
+
fileScript = readFileSync(resolve(context.cwd, scriptPath), "utf8");
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
fail("INVALID_SYNTAX", `Cannot read workflow script file ${scriptPath}: ${errorText(error)}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
822
842
|
const functionName = typeof params.workflow === "string" ? params.workflow : undefined;
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
843
|
+
const explicitName = params.name === undefined ? undefined : typeof params.name === "string" ? params.name.trim() : "";
|
|
844
|
+
if (params.name !== undefined && !explicitName)
|
|
845
|
+
fail("INVALID_METADATA", "Workflow name must be non-empty when provided");
|
|
846
|
+
const workflowName = explicitName ?? functionName ?? "";
|
|
826
847
|
if (functionName === undefined && !workflowName)
|
|
827
848
|
fail("INVALID_METADATA", "Inline workflow launches require a non-empty name");
|
|
828
849
|
const fn = functionName === undefined ? undefined : registry?.function(functionName);
|
|
@@ -831,9 +852,9 @@ export function validateWorkflowLaunchWithRegistry(params, context, registry) {
|
|
|
831
852
|
const args = params.args === undefined ? null : params.args;
|
|
832
853
|
if (functionName !== undefined && fn && (!object(args) || !jsonValue(args) || !Value.Check(fn.input, args)))
|
|
833
854
|
fail("RESULT_INVALID", `Invalid input for ${functionName}`);
|
|
834
|
-
const script = functionName !== undefined && fn ? functionLaunchScript(functionName) : typeof params.script === "string" && params.script.trim() ? params.script : "";
|
|
855
|
+
const script = functionName !== undefined && fn ? functionLaunchScript(functionName) : typeof params.script === "string" && params.script.trim() ? params.script : fileScript ?? "";
|
|
835
856
|
if (!script)
|
|
836
|
-
fail("INVALID_SYNTAX", "Provide script or registered function");
|
|
857
|
+
fail("INVALID_SYNTAX", "Provide script, scriptPath, or registered function");
|
|
837
858
|
const metadata = validateWorkflowMetadata({ name: workflowName, ...(typeof params.description === "string" ? { description: params.description } : fn?.description ? { description: fn.description } : {}) });
|
|
838
859
|
const globalAgentDefinitions = loadAgentDefinitions(context.cwd, context.agentDir, false, registry && typeof registry.roleDirectoryRegistrations === "function" ? registry.roleDirectoryRegistrations() : registry && typeof registry.roleDirectories === "function" ? registry.roleDirectories() : undefined);
|
|
839
860
|
const projectAgentDefinitions = context.projectTrusted ? readRoleDefinitions(projectRoleDirectories(join(context.cwd, ".pi"))) : {};
|
|
@@ -9,5 +9,6 @@ export type WorkflowTui = {
|
|
|
9
9
|
requestRender(force?: boolean): void;
|
|
10
10
|
};
|
|
11
11
|
export declare function workflowScriptArtifact(script: string): WorkflowArtifact;
|
|
12
|
+
export declare function workflowPromptArtifact(prompt: string): WorkflowArtifact;
|
|
12
13
|
export declare function workflowResultArtifact(value: JsonValue): WorkflowArtifact;
|
|
13
14
|
export declare function openWorkflowArtifact(tui: WorkflowTui, command: string, artifact: WorkflowArtifact): Promise<number | null>;
|
|
@@ -3,6 +3,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
export function workflowScriptArtifact(script) { return { extension: ".js", content: script }; }
|
|
6
|
+
export function workflowPromptArtifact(prompt) { return { extension: ".md", content: prompt }; }
|
|
6
7
|
export function workflowResultArtifact(value) { return typeof value === "string" ? { extension: ".md", content: value } : { extension: ".json", content: `${JSON.stringify(value, null, 2)}\n` }; }
|
|
7
8
|
async function spawnWorkflowEditor(command, path) {
|
|
8
9
|
const [editor, ...editorArgs] = command.split(" ");
|
|
@@ -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
|
@@ -27,15 +27,23 @@ Await `parallel(...)` or `pipeline(...)` results before interpolation. Runs are
|
|
|
27
27
|
## Runtime and safety rules
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
Inline launches require a non-empty `name`. Registered function launches must omit `name`; they use `workflow` as the run name:
|
|
31
30
|
|
|
32
31
|
```json
|
|
33
32
|
{ "workflow": "workflowName", "args": { "issue": 42 } }
|
|
34
33
|
```
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
|
|
35
|
+
A reviewed JavaScript file on disk can use `scriptPath` instead of `script`.
|
|
36
|
+
|
|
37
|
+
Recovery map:
|
|
38
|
+
- `agent(..., { retries })` reruns one agent call in the same run for transient failures;
|
|
39
|
+
- `workflow_retry({ runId, foreground? })` replays a failed run into a child;
|
|
40
|
+
- `workflow_resume({ runId, budget?, foreground? })` continues a `budget_exhausted` run;
|
|
41
|
+
- 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;
|
|
42
|
+
- `parentRunId` on a new launch only borrows named worktrees and never replays or resumes.
|
|
43
|
+
|
|
44
|
+
For an explicitly failed run, use the exact `runId` from failure diagnostics with `workflow_retry({ runId, foreground: <prevValue> })`: 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.
|
|
45
|
+
`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.
|
|
46
|
+
Inspect tool `workflow_catalog` result at least once before creating the first workflow for a task. Make sure to call workflow_catalog more than once if you need to inspect details about a global function.
|
|
39
47
|
|
|
40
48
|
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`.
|
|
41
49
|
|