pi-extensible-workflows 3.3.0 → 3.4.1
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 -2
- package/dist/src/agent-execution.d.ts +63 -9
- package/dist/src/agent-execution.js +265 -92
- package/dist/src/cli.js +11 -2
- package/dist/src/doctor-cleanup.js +66 -31
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +29 -13
- package/dist/src/host.d.ts +5 -5
- package/dist/src/host.js +160 -76
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +1 -1
- package/dist/src/persistence.d.ts +4 -8
- package/dist/src/persistence.js +3 -0
- package/dist/src/registry.d.ts +3 -2
- package/dist/src/registry.js +16 -4
- package/dist/src/session-inspector.js +14 -22
- package/dist/src/types.d.ts +123 -60
- package/dist/src/validation.js +20 -5
- package/dist/src/workflow-artifacts.d.ts +1 -0
- package/dist/src/workflow-artifacts.js +1 -0
- package/package.json +2 -2
- package/skills/pi-extensible-workflows/SKILL.md +13 -5
- package/src/agent-execution.ts +221 -90
- package/src/cli.ts +14 -3
- package/src/doctor-cleanup.ts +36 -3
- package/src/execution.ts +27 -15
- package/src/host.ts +136 -62
- package/src/index.ts +3 -3
- package/src/persistence.ts +5 -3
- package/src/registry.ts +14 -5
- package/src/session-inspector.ts +13 -22
- package/src/types.ts +45 -27
- package/src/validation.ts +13 -4
- package/src/workflow-artifacts.ts +1 -0
package/dist/src/registry.js
CHANGED
|
@@ -29,6 +29,7 @@ export class WorkflowRegistry {
|
|
|
29
29
|
#extensions = new Set();
|
|
30
30
|
#globals = new Map();
|
|
31
31
|
#hooks = new Map();
|
|
32
|
+
#agentAttemptActions = new Map();
|
|
32
33
|
#roleDirectories = new Map();
|
|
33
34
|
#modelAliases = new Map();
|
|
34
35
|
#frozen = false;
|
|
@@ -39,18 +40,19 @@ export class WorkflowRegistry {
|
|
|
39
40
|
fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
|
|
40
41
|
if (object(extension) && Object.prototype.hasOwnProperty.call(extension, "workflows"))
|
|
41
42
|
fail("INVALID_METADATA", "Separate registered workflow definitions were removed; register a function with input and output schemas instead");
|
|
42
|
-
if (!object(extension) || Object.keys(extension).some((key) => !["version", "headline", "description", "functions", "variables", "modelAliases", "agentSetupHooks", "roleDirectories"].includes(key)) || typeof extension.version !== "string" || !SEMVER.test(extension.version) || typeof extension.headline !== "string" || !extension.headline.trim() || typeof extension.description !== "string" || !extension.description.trim())
|
|
43
|
+
if (!object(extension) || Object.keys(extension).some((key) => !["version", "headline", "description", "functions", "variables", "modelAliases", "agentSetupHooks", "agentAttemptActions", "roleDirectories"].includes(key)) || typeof extension.version !== "string" || !SEMVER.test(extension.version) || typeof extension.headline !== "string" || !extension.headline.trim() || typeof extension.description !== "string" || !extension.description.trim())
|
|
43
44
|
fail("INVALID_METADATA", "Workflow extensions require a semantic version, headline, and description");
|
|
44
45
|
const functions = extension.functions ?? {};
|
|
45
46
|
const variables = extension.variables ?? {};
|
|
46
47
|
const modelAliases = extension.modelAliases ?? {};
|
|
47
48
|
const agentSetupHooks = extension.agentSetupHooks ?? {};
|
|
49
|
+
const agentAttemptActions = extension.agentAttemptActions ?? {};
|
|
48
50
|
const roleDirectoryValues = extension.roleDirectories === undefined ? [] : extension.roleDirectories;
|
|
49
51
|
if (!Array.isArray(roleDirectoryValues))
|
|
50
52
|
fail("INVALID_METADATA", "Workflow extension roleDirectories must be an array");
|
|
51
53
|
const roleDirectories = [...new Set(Array.from(roleDirectoryValues, (value) => normalizeRoleDirectory(value)))];
|
|
52
|
-
if (!object(functions) || !object(variables) || !object(modelAliases) || !object(agentSetupHooks) || (Object.keys(functions).length === 0 && Object.keys(variables).length === 0 && Object.keys(modelAliases).length === 0 && Object.keys(agentSetupHooks).length === 0 && roleDirectories.length === 0))
|
|
53
|
-
fail("INVALID_METADATA", "Workflow extensions require functions, variables, model aliases, agent setup hooks, or role directories");
|
|
54
|
+
if (!object(functions) || !object(variables) || !object(modelAliases) || !object(agentSetupHooks) || !object(agentAttemptActions) || (Object.keys(functions).length === 0 && Object.keys(variables).length === 0 && Object.keys(modelAliases).length === 0 && Object.keys(agentSetupHooks).length === 0 && Object.keys(agentAttemptActions).length === 0 && roleDirectories.length === 0))
|
|
55
|
+
fail("INVALID_METADATA", "Workflow extensions require functions, variables, model aliases, agent setup hooks, agent attempt actions, or role directories");
|
|
54
56
|
const names = [...Object.keys(functions), ...Object.keys(variables)];
|
|
55
57
|
if (new Set(names).size !== names.length)
|
|
56
58
|
fail("GLOBAL_COLLISION", "Global name collision inside extension");
|
|
@@ -89,7 +91,13 @@ export class WorkflowRegistry {
|
|
|
89
91
|
if (this.#hooks.has(name))
|
|
90
92
|
fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
|
|
91
93
|
}
|
|
92
|
-
|
|
94
|
+
for (const [name, action] of Object.entries(agentAttemptActions)) {
|
|
95
|
+
if (!IDENTIFIER.test(name) || !object(action) || Object.keys(action).some((key) => !["label", "visible", "run"].includes(key)) || typeof action.label !== "string" || !action.label.trim() || typeof action.visible !== "function" || typeof action.run !== "function")
|
|
96
|
+
fail("INVALID_METADATA", `Invalid agent attempt action: ${name}`);
|
|
97
|
+
if (this.#agentAttemptActions.has(name))
|
|
98
|
+
fail("DUPLICATE_NAME", `Agent attempt action already registered: ${name}`);
|
|
99
|
+
}
|
|
100
|
+
const stored = deepFreeze({ ...extension, functions, variables, modelAliases, agentSetupHooks, agentAttemptActions, ...(roleDirectories.length ? { roleDirectories } : {}) });
|
|
93
101
|
this.#extensions.add(stored);
|
|
94
102
|
for (const directory of roleDirectories)
|
|
95
103
|
if (!this.#roleDirectories.has(directory))
|
|
@@ -100,6 +108,8 @@ export class WorkflowRegistry {
|
|
|
100
108
|
this.#modelAliases.set(name, { name, version: extension.version, headline: extension.headline, extensionDescription: extension.description, resolve: alias.resolve });
|
|
101
109
|
for (const [name, hook] of Object.entries(agentSetupHooks))
|
|
102
110
|
this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
|
|
111
|
+
for (const [name, action] of Object.entries(agentAttemptActions))
|
|
112
|
+
this.#agentAttemptActions.set(name, action);
|
|
103
113
|
}
|
|
104
114
|
function(name) {
|
|
105
115
|
if (!IDENTIFIER.test(name))
|
|
@@ -197,6 +207,7 @@ export class WorkflowRegistry {
|
|
|
197
207
|
agentSetupHooks() {
|
|
198
208
|
return [...this.#hooks.values()].sort((left, right) => left.priority - right.priority || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
|
|
199
209
|
}
|
|
210
|
+
agentAttemptActions() { return Object.freeze(Object.fromEntries(this.#agentAttemptActions.entries())); }
|
|
200
211
|
roleDirectories() {
|
|
201
212
|
return [...this.#roleDirectories.keys()];
|
|
202
213
|
}
|
|
@@ -250,6 +261,7 @@ function createWorkflowRegistryApi(registry) {
|
|
|
250
261
|
roleDirectories: () => registry.roleDirectories(),
|
|
251
262
|
roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
|
|
252
263
|
agentSetupHooks: () => registry.agentSetupHooks(),
|
|
264
|
+
agentAttemptActions: () => registry.agentAttemptActions(),
|
|
253
265
|
};
|
|
254
266
|
}
|
|
255
267
|
function workflowRegistryHost() {
|
|
@@ -115,6 +115,13 @@ function readTranscript(path) {
|
|
|
115
115
|
return undefined;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
function attemptTranscriptPath(attempt) {
|
|
119
|
+
const session = attempt.session;
|
|
120
|
+
if (!session || session.transport !== "local" || typeof session.locator !== "object" || session.locator === null || Array.isArray(session.locator))
|
|
121
|
+
return undefined;
|
|
122
|
+
const path = session.locator.sessionFile;
|
|
123
|
+
return typeof path === "string" && path ? path : undefined;
|
|
124
|
+
}
|
|
118
125
|
function resultRunId(result) {
|
|
119
126
|
if (!result)
|
|
120
127
|
return undefined;
|
|
@@ -161,33 +168,18 @@ async function agentReport(agent) {
|
|
|
161
168
|
const fallbackThinking = agent.model.thinking;
|
|
162
169
|
const attempts = [];
|
|
163
170
|
for (const attempt of agent.attemptDetails ?? []) {
|
|
164
|
-
const
|
|
171
|
+
const setup = attempt.setup;
|
|
172
|
+
const path = attemptTranscriptPath(attempt);
|
|
173
|
+
const log = path ? readTranscript(path) : undefined;
|
|
165
174
|
if (log) {
|
|
166
|
-
const model = log.model ??
|
|
175
|
+
const model = log.model ?? `${setup.model.provider}/${setup.model.model}`;
|
|
167
176
|
const cost = log.cost;
|
|
168
|
-
attempts.push({
|
|
169
|
-
attempt: attempt.attempt,
|
|
170
|
-
prompt: log.prompt ?? "(transcript unavailable)",
|
|
171
|
-
model,
|
|
172
|
-
...(log.thinking !== undefined ? { thinking: log.thinking } : {}),
|
|
173
|
-
cost,
|
|
174
|
-
models: log.models.length ? log.models : [{ model, cost }],
|
|
175
|
-
...(attempt.error ? { error: `${attempt.error.code}: ${attempt.error.message}` } : {}),
|
|
176
|
-
...(attempt.setup ? { setup: attempt.setup } : {}),
|
|
177
|
-
});
|
|
177
|
+
attempts.push({ attempt: attempt.attempt, prompt: log.prompt ?? "(transcript unavailable)", model, ...(log.thinking !== undefined ? { thinking: log.thinking } : {}), cost, models: log.models.length ? log.models : [{ model, cost }], ...(attempt.error ? { error: `${attempt.error.code}: ${attempt.error.message}` } : {}), setup });
|
|
178
178
|
continue;
|
|
179
179
|
}
|
|
180
|
+
const model = `${setup.model.provider}/${setup.model.model}`;
|
|
180
181
|
const cost = attempt.accounting.cost;
|
|
181
|
-
attempts.push({
|
|
182
|
-
attempt: attempt.attempt,
|
|
183
|
-
prompt: "(transcript unavailable)",
|
|
184
|
-
model: fallbackModel,
|
|
185
|
-
...(fallbackThinking !== undefined ? { thinking: fallbackThinking } : {}),
|
|
186
|
-
cost,
|
|
187
|
-
models: [{ model: fallbackModel, cost }],
|
|
188
|
-
...(attempt.error ? { error: `${attempt.error.code}: ${attempt.error.message}` } : {}),
|
|
189
|
-
...(attempt.setup ? { setup: attempt.setup } : {}),
|
|
190
|
-
});
|
|
182
|
+
attempts.push({ attempt: attempt.attempt, prompt: "(transcript unavailable)", model, ...(setup.model.thinking !== undefined ? { thinking: setup.model.thinking } : {}), cost, models: [{ model, cost }], ...(attempt.error ? { error: `${attempt.error.code}: ${attempt.error.message}` } : {}), setup });
|
|
191
183
|
}
|
|
192
184
|
if (!attempts.length) {
|
|
193
185
|
const cost = agent.accounting?.cost ?? 0;
|
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"];
|
|
@@ -193,10 +193,10 @@ export interface AgentResourcePolicy {
|
|
|
193
193
|
global: AgentResourceExclusions;
|
|
194
194
|
project: AgentResourceExclusions;
|
|
195
195
|
effective: AgentResourceExclusions;
|
|
196
|
-
unmatchedSkills: string[];
|
|
197
|
-
unmatchedExtensions: string[];
|
|
198
|
-
excludedSkills?: string[];
|
|
199
|
-
excludedExtensions?: string[];
|
|
196
|
+
unmatchedSkills: readonly string[];
|
|
197
|
+
unmatchedExtensions: readonly string[];
|
|
198
|
+
excludedSkills?: readonly string[];
|
|
199
|
+
excludedExtensions?: readonly string[];
|
|
200
200
|
}
|
|
201
201
|
export interface AgentActivity {
|
|
202
202
|
kind: "reasoning" | "tool" | "text";
|
|
@@ -224,22 +224,17 @@ export interface AgentSetupSummary {
|
|
|
224
224
|
unmatchedExtensions: readonly string[];
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
|
+
export interface AgentAttemptError {
|
|
228
|
+
code: string;
|
|
229
|
+
message: string;
|
|
230
|
+
}
|
|
227
231
|
export interface AgentAttemptSummary {
|
|
228
232
|
attempt: number;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
};
|
|
235
|
-
accounting: {
|
|
236
|
-
input: number;
|
|
237
|
-
output: number;
|
|
238
|
-
cacheRead: number;
|
|
239
|
-
cacheWrite: number;
|
|
240
|
-
cost: number;
|
|
241
|
-
};
|
|
242
|
-
setup?: AgentSetupSummary;
|
|
233
|
+
transport: string;
|
|
234
|
+
session?: WorkflowAgentSessionReference;
|
|
235
|
+
setup: AgentSetupSummary;
|
|
236
|
+
error?: AgentAttemptError;
|
|
237
|
+
accounting: AgentAccounting;
|
|
243
238
|
}
|
|
244
239
|
export interface WorkflowWorktreeCreatedEvent extends WorkflowEventBase {
|
|
245
240
|
owner: string;
|
|
@@ -252,6 +247,8 @@ export interface WorkflowWorktreeReference {
|
|
|
252
247
|
readonly branch: string;
|
|
253
248
|
}
|
|
254
249
|
export interface AgentRecord {
|
|
250
|
+
systemPrompt?: string;
|
|
251
|
+
prompt?: string;
|
|
255
252
|
id: string;
|
|
256
253
|
name: string;
|
|
257
254
|
label?: string;
|
|
@@ -311,6 +308,7 @@ export interface RunRecord {
|
|
|
311
308
|
cwd: string;
|
|
312
309
|
sessionId: string;
|
|
313
310
|
state: RunState;
|
|
311
|
+
agentSessions: readonly WorkflowAgentSessionReference[];
|
|
314
312
|
parentRunId?: string;
|
|
315
313
|
retry?: WorkflowRetryProvenance;
|
|
316
314
|
phase?: string;
|
|
@@ -413,47 +411,65 @@ export interface WorkflowVariable {
|
|
|
413
411
|
schema: JsonSchema;
|
|
414
412
|
resolve: (run: Readonly<WorkflowRunContext>) => Promise<JsonValue> | JsonValue;
|
|
415
413
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
content?: unknown;
|
|
419
|
-
stopReason?: string;
|
|
420
|
-
errorMessage?: string;
|
|
421
|
-
usage?: {
|
|
422
|
-
input: number;
|
|
423
|
-
output: number;
|
|
424
|
-
cacheRead: number;
|
|
425
|
-
cacheWrite: number;
|
|
426
|
-
cost: {
|
|
427
|
-
total: number;
|
|
428
|
-
};
|
|
429
|
-
};
|
|
430
|
-
};
|
|
431
|
-
type NativeSessionStats = Pick<SessionStats, "tokens" | "cost">;
|
|
432
|
-
export interface NativeSession {
|
|
414
|
+
export interface WorkflowAgentSessionReference {
|
|
415
|
+
readonly transport: string;
|
|
433
416
|
readonly sessionId: string;
|
|
434
|
-
readonly
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
readonly
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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;
|
|
442
426
|
};
|
|
443
|
-
readonly
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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;
|
|
448
441
|
};
|
|
449
442
|
};
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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>;
|
|
457
473
|
}
|
|
458
474
|
type SessionTools = NonNullable<CreateAgentSessionOptions["tools"]>;
|
|
459
475
|
type SessionCustomTools = NonNullable<CreateAgentSessionOptions["customTools"]>;
|
|
@@ -472,12 +488,37 @@ export interface SessionInput {
|
|
|
472
488
|
resourcePolicy?: AgentResourcePolicy;
|
|
473
489
|
options?: AgentOptions;
|
|
474
490
|
}
|
|
475
|
-
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
|
+
}
|
|
476
516
|
export interface AgentSetup {
|
|
477
517
|
prompt: string;
|
|
478
518
|
options: AgentOptions;
|
|
479
519
|
sessionInput: SessionInput;
|
|
480
|
-
|
|
520
|
+
prepared: Readonly<PreparedAgentSession>;
|
|
521
|
+
transport: AgentTransport;
|
|
481
522
|
}
|
|
482
523
|
export interface AgentSetupContext {
|
|
483
524
|
readonly run: Readonly<WorkflowRunContext>;
|
|
@@ -503,11 +544,32 @@ export interface WorkflowRoleDirectoryRegistration {
|
|
|
503
544
|
path: string;
|
|
504
545
|
extension: WorkflowExtensionMetadata;
|
|
505
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
|
+
}
|
|
506
567
|
export interface WorkflowExtension extends WorkflowExtensionMetadata {
|
|
507
568
|
functions?: Readonly<Record<string, WorkflowFunction>>;
|
|
508
569
|
variables?: Readonly<Record<string, WorkflowVariable>>;
|
|
509
570
|
modelAliases?: Readonly<Record<string, WorkflowModelAlias>>;
|
|
510
571
|
agentSetupHooks?: Readonly<Record<string, AgentSetupHook>>;
|
|
572
|
+
agentAttemptActions?: Readonly<Record<string, AgentAttemptAction>>;
|
|
511
573
|
roleDirectories?: readonly (string | URL)[];
|
|
512
574
|
}
|
|
513
575
|
export interface WorkflowJournal {
|
|
@@ -524,8 +586,8 @@ export interface WorkflowFailureAgent {
|
|
|
524
586
|
role?: string;
|
|
525
587
|
structuralPath: readonly string[];
|
|
526
588
|
attempt: number;
|
|
527
|
-
|
|
528
|
-
|
|
589
|
+
transport?: string;
|
|
590
|
+
session?: WorkflowAgentSessionReference;
|
|
529
591
|
}
|
|
530
592
|
export interface WorkflowSiblingAgent {
|
|
531
593
|
id: string;
|
|
@@ -687,6 +749,7 @@ export interface WorkflowValidationParameters {
|
|
|
687
749
|
name?: string;
|
|
688
750
|
description?: string;
|
|
689
751
|
script?: string;
|
|
752
|
+
scriptPath?: string;
|
|
690
753
|
workflow?: string;
|
|
691
754
|
args?: unknown;
|
|
692
755
|
}
|
package/dist/src/validation.js
CHANGED
|
@@ -825,10 +825,25 @@ export function validateWorkflowLaunchWithRegistry(params, context, registry) {
|
|
|
825
825
|
fail("INVALID_METADATA", "maxAgentLaunches has been removed; use budget.agentLaunches");
|
|
826
826
|
if (params.script !== undefined && params.workflow !== undefined)
|
|
827
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
|
+
}
|
|
828
842
|
const functionName = typeof params.workflow === "string" ? params.workflow : undefined;
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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 ?? "";
|
|
832
847
|
if (functionName === undefined && !workflowName)
|
|
833
848
|
fail("INVALID_METADATA", "Inline workflow launches require a non-empty name");
|
|
834
849
|
const fn = functionName === undefined ? undefined : registry?.function(functionName);
|
|
@@ -837,9 +852,9 @@ export function validateWorkflowLaunchWithRegistry(params, context, registry) {
|
|
|
837
852
|
const args = params.args === undefined ? null : params.args;
|
|
838
853
|
if (functionName !== undefined && fn && (!object(args) || !jsonValue(args) || !Value.Check(fn.input, args)))
|
|
839
854
|
fail("RESULT_INVALID", `Invalid input for ${functionName}`);
|
|
840
|
-
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 ?? "";
|
|
841
856
|
if (!script)
|
|
842
|
-
fail("INVALID_SYNTAX", "Provide script or registered function");
|
|
857
|
+
fail("INVALID_SYNTAX", "Provide script, scriptPath, or registered function");
|
|
843
858
|
const metadata = validateWorkflowMetadata({ name: workflowName, ...(typeof params.description === "string" ? { description: params.description } : fn?.description ? { description: fn.description } : {}) });
|
|
844
859
|
const globalAgentDefinitions = loadAgentDefinitions(context.cwd, context.agentDir, false, registry && typeof registry.roleDirectoryRegistrations === "function" ? registry.roleDirectoryRegistrations() : registry && typeof registry.roleDirectories === "function" ? registry.roleDirectories() : undefined);
|
|
845
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(" ");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extensible-workflows",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "Deterministic multi-agent workflow orchestration for Pi",
|
|
5
5
|
"homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"inspect": "node dist/src/cli.js inspect",
|
|
34
34
|
"lint": "eslint .",
|
|
35
35
|
"test": "npm run build && TEST_FILES='dist/test/*.test.js' npm run test:run",
|
|
36
|
-
"test:run": "tmp=$(mktemp -d); trap 'rm -rf \"$tmp\"' EXIT; TMPDIR=\"$tmp\" node --test --test-concurrency=1 $TEST_FILES",
|
|
36
|
+
"test:run": "tmp=$(mktemp -d); trap 'rm -rf \"$tmp\"' EXIT; TMPDIR=\"$tmp\" node --test --test-concurrency=1 --test-reporter=dot $TEST_FILES",
|
|
37
37
|
"acceptance": "npm run build && TEST_FILES='dist/test/runtime-acceptance.test.js' npm run test:run",
|
|
38
38
|
"docs:check": "node scripts/check-docs.mjs",
|
|
39
39
|
"check": "npm run lint && npm test && npm run docs:check",
|
|
@@ -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
|
|