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/src/registry.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isAbsolute } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { Value } from "typebox/value";
|
|
4
|
-
import type { JsonValue, RegisteredAgentSetupHook, WorkflowCatalog, WorkflowCatalogContext, WorkflowCatalogError, WorkflowCatalogFunction, WorkflowCatalogIndex, WorkflowCatalogModelAlias, WorkflowCatalogVariable, WorkflowExtension, WorkflowFunction, WorkflowFunctionContext, WorkflowJournal, WorkflowModelAlias, WorkflowModelAliasResolverContext, WorkflowRoleDirectoryRegistration, WorkflowVariable } from "./types.js";
|
|
4
|
+
import type { AgentAttemptAction, JsonValue, RegisteredAgentSetupHook, WorkflowCatalog, WorkflowCatalogContext, WorkflowCatalogError, WorkflowCatalogFunction, WorkflowCatalogIndex, WorkflowCatalogModelAlias, WorkflowCatalogVariable, WorkflowExtension, WorkflowFunction, WorkflowFunctionContext, WorkflowJournal, WorkflowModelAlias, WorkflowModelAliasResolverContext, WorkflowRoleDirectoryRegistration, WorkflowVariable } from "./types.js";
|
|
5
5
|
import { deepFreeze, fail, jsonValue, object } from "./utils.js";
|
|
6
6
|
import { loadSettings, resolveWorkflowSettings, validateSchema } from "./validation.js";
|
|
7
7
|
|
|
@@ -28,6 +28,7 @@ export class WorkflowRegistry {
|
|
|
28
28
|
readonly #extensions = new Set<Readonly<WorkflowExtension>>();
|
|
29
29
|
readonly #globals = new Map<string, string>();
|
|
30
30
|
readonly #hooks = new Map<string, RegisteredAgentSetupHook>();
|
|
31
|
+
readonly #agentAttemptActions = new Map<string, AgentAttemptAction>();
|
|
31
32
|
readonly #roleDirectories = new Map<string, WorkflowRoleDirectoryRegistration>();
|
|
32
33
|
readonly #modelAliases = new Map<string, { name: string; version: string; headline: string; extensionDescription: string; resolve: WorkflowModelAlias["resolve"] }>();
|
|
33
34
|
#frozen = false;
|
|
@@ -38,15 +39,16 @@ export class WorkflowRegistry {
|
|
|
38
39
|
register(extension: WorkflowExtension): void {
|
|
39
40
|
if (this.#frozen) fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
|
|
40
41
|
if (object(extension) && Object.prototype.hasOwnProperty.call(extension, "workflows")) fail("INVALID_METADATA", "Separate registered workflow definitions were removed; register a function with input and output schemas instead");
|
|
41
|
-
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()) fail("INVALID_METADATA", "Workflow extensions require a semantic version, headline, and description");
|
|
42
|
+
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()) fail("INVALID_METADATA", "Workflow extensions require a semantic version, headline, and description");
|
|
42
43
|
const functions = extension.functions ?? {};
|
|
43
44
|
const variables = extension.variables ?? {};
|
|
44
45
|
const modelAliases = extension.modelAliases ?? {};
|
|
45
46
|
const agentSetupHooks = extension.agentSetupHooks ?? {};
|
|
47
|
+
const agentAttemptActions = extension.agentAttemptActions ?? {};
|
|
46
48
|
const roleDirectoryValues = extension.roleDirectories === undefined ? [] : extension.roleDirectories;
|
|
47
49
|
if (!Array.isArray(roleDirectoryValues)) fail("INVALID_METADATA", "Workflow extension roleDirectories must be an array");
|
|
48
50
|
const roleDirectories = [...new Set(Array.from(roleDirectoryValues, (value) => normalizeRoleDirectory(value)))];
|
|
49
|
-
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)) fail("INVALID_METADATA", "Workflow extensions require functions, variables, model aliases, agent setup hooks, or role directories");
|
|
51
|
+
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)) fail("INVALID_METADATA", "Workflow extensions require functions, variables, model aliases, agent setup hooks, agent attempt actions, or role directories");
|
|
50
52
|
const names = [...Object.keys(functions), ...Object.keys(variables)];
|
|
51
53
|
if (new Set(names).size !== names.length) fail("GLOBAL_COLLISION", "Global name collision inside extension");
|
|
52
54
|
for (const name of names) {
|
|
@@ -73,12 +75,17 @@ export class WorkflowRegistry {
|
|
|
73
75
|
if (!IDENTIFIER.test(name) || !object(hook) || Object.keys(hook).some((key) => !["priority", "setup"].includes(key)) || typeof hook.setup !== "function" || hook.priority !== undefined && (typeof hook.priority !== "number" || !Number.isFinite(hook.priority))) fail("INVALID_METADATA", `Invalid agent setup hook: ${name}`);
|
|
74
76
|
if (this.#hooks.has(name)) fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
|
|
75
77
|
}
|
|
76
|
-
|
|
78
|
+
for (const [name, action] of Object.entries(agentAttemptActions)) {
|
|
79
|
+
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") fail("INVALID_METADATA", `Invalid agent attempt action: ${name}`);
|
|
80
|
+
if (this.#agentAttemptActions.has(name)) fail("DUPLICATE_NAME", `Agent attempt action already registered: ${name}`);
|
|
81
|
+
}
|
|
82
|
+
const stored = deepFreeze({ ...extension, functions, variables, modelAliases, agentSetupHooks, agentAttemptActions, ...(roleDirectories.length ? { roleDirectories } : {}) });
|
|
77
83
|
this.#extensions.add(stored);
|
|
78
84
|
for (const directory of roleDirectories) if (!this.#roleDirectories.has(directory)) this.#roleDirectories.set(directory, deepFreeze({ path: directory, extension: { version: extension.version, headline: extension.headline, description: extension.description } }));
|
|
79
85
|
for (const name of names) this.#globals.set(name, name);
|
|
80
86
|
for (const [name, alias] of Object.entries(modelAliases)) this.#modelAliases.set(name, { name, version: extension.version, headline: extension.headline, extensionDescription: extension.description, resolve: alias.resolve });
|
|
81
87
|
for (const [name, hook] of Object.entries(agentSetupHooks)) this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
|
|
88
|
+
for (const [name, action] of Object.entries(agentAttemptActions)) this.#agentAttemptActions.set(name, action);
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
function(name: string): WorkflowFunction {
|
|
@@ -162,6 +169,7 @@ export class WorkflowRegistry {
|
|
|
162
169
|
agentSetupHooks(): readonly RegisteredAgentSetupHook[] {
|
|
163
170
|
return [...this.#hooks.values()].sort((left, right) => left.priority - right.priority || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
|
|
164
171
|
}
|
|
172
|
+
agentAttemptActions(): Readonly<Record<string, AgentAttemptAction>> { return Object.freeze(Object.fromEntries(this.#agentAttemptActions.entries())); }
|
|
165
173
|
roleDirectories(): readonly string[] {
|
|
166
174
|
return [...this.#roleDirectories.keys()];
|
|
167
175
|
}
|
|
@@ -188,7 +196,7 @@ export class WorkflowRegistry {
|
|
|
188
196
|
return Object.freeze(resolved);
|
|
189
197
|
}
|
|
190
198
|
}
|
|
191
|
-
export type WorkflowRegistryApi = Pick<WorkflowRegistry, "frozen" | "freeze" | "register" | "function" | "functions" | "catalog" | "catalogIndex" | "catalogDetail" | "globals" | "invokeFunction" | "variables" | "modelAliases" | "resolveModelAliases" | "agentSetupHooks" | "roleDirectories" | "roleDirectoryRegistrations">;
|
|
199
|
+
export type WorkflowRegistryApi = Pick<WorkflowRegistry, "frozen" | "freeze" | "register" | "function" | "functions" | "catalog" | "catalogIndex" | "catalogDetail" | "globals" | "invokeFunction" | "variables" | "modelAliases" | "resolveModelAliases" | "agentSetupHooks" | "agentAttemptActions" | "roleDirectories" | "roleDirectoryRegistrations">;
|
|
192
200
|
interface WorkflowRegistryHost { api: WorkflowRegistryApi }
|
|
193
201
|
const WORKFLOW_REGISTRY_KEY = Symbol.for("pi-extensible-workflows.workflow-registry");
|
|
194
202
|
const globalRegistry = globalThis as typeof globalThis & Record<symbol, WorkflowRegistryHost | undefined>;
|
|
@@ -210,6 +218,7 @@ function createWorkflowRegistryApi(registry: WorkflowRegistry): WorkflowRegistry
|
|
|
210
218
|
roleDirectories: () => registry.roleDirectories(),
|
|
211
219
|
roleDirectoryRegistrations: () => registry.roleDirectoryRegistrations(),
|
|
212
220
|
agentSetupHooks: () => registry.agentSetupHooks(),
|
|
221
|
+
agentAttemptActions: () => registry.agentAttemptActions(),
|
|
213
222
|
};
|
|
214
223
|
}
|
|
215
224
|
function workflowRegistryHost(): WorkflowRegistryHost {
|
package/src/session-inspector.ts
CHANGED
|
@@ -119,6 +119,12 @@ function readTranscript(path: string): TranscriptSummary | undefined {
|
|
|
119
119
|
return summary.model === undefined ? undefined : summary;
|
|
120
120
|
} catch { return undefined; }
|
|
121
121
|
}
|
|
122
|
+
function attemptTranscriptPath(attempt: NonNullable<PersistedRun["agents"][number]["attemptDetails"]>[number]): string | undefined {
|
|
123
|
+
const session = attempt.session;
|
|
124
|
+
if (!session || session.transport !== "local" || typeof session.locator !== "object" || session.locator === null || Array.isArray(session.locator)) return undefined;
|
|
125
|
+
const path = session.locator.sessionFile;
|
|
126
|
+
return typeof path === "string" && path ? path : undefined;
|
|
127
|
+
}
|
|
122
128
|
|
|
123
129
|
function resultRunId(result: ToolResult | undefined): string | undefined {
|
|
124
130
|
if (!result) return undefined;
|
|
@@ -157,33 +163,18 @@ async function agentReport(agent: PersistedRun["agents"][number]): Promise<Agent
|
|
|
157
163
|
const fallbackThinking = agent.model.thinking;
|
|
158
164
|
const attempts: AttemptReport[] = [];
|
|
159
165
|
for (const attempt of agent.attemptDetails ?? []) {
|
|
160
|
-
const
|
|
166
|
+
const setup = attempt.setup;
|
|
167
|
+
const path = attemptTranscriptPath(attempt);
|
|
168
|
+
const log = path ? readTranscript(path) : undefined;
|
|
161
169
|
if (log) {
|
|
162
|
-
const model = log.model ??
|
|
170
|
+
const model = log.model ?? `${setup.model.provider}/${setup.model.model}`;
|
|
163
171
|
const cost = log.cost;
|
|
164
|
-
attempts.push({
|
|
165
|
-
attempt: attempt.attempt,
|
|
166
|
-
prompt: log.prompt ?? "(transcript unavailable)",
|
|
167
|
-
model,
|
|
168
|
-
...(log.thinking !== undefined ? { thinking: log.thinking } : {}),
|
|
169
|
-
cost,
|
|
170
|
-
models: log.models.length ? log.models : [{ model, cost }],
|
|
171
|
-
...(attempt.error ? { error: `${attempt.error.code}: ${attempt.error.message}` } : {}),
|
|
172
|
-
...(attempt.setup ? { setup: attempt.setup } : {}),
|
|
173
|
-
});
|
|
172
|
+
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 });
|
|
174
173
|
continue;
|
|
175
174
|
}
|
|
175
|
+
const model = `${setup.model.provider}/${setup.model.model}`;
|
|
176
176
|
const cost = attempt.accounting.cost;
|
|
177
|
-
attempts.push({
|
|
178
|
-
attempt: attempt.attempt,
|
|
179
|
-
prompt: "(transcript unavailable)",
|
|
180
|
-
model: fallbackModel,
|
|
181
|
-
...(fallbackThinking !== undefined ? { thinking: fallbackThinking } : {}),
|
|
182
|
-
cost,
|
|
183
|
-
models: [{ model: fallbackModel, cost }],
|
|
184
|
-
...(attempt.error ? { error: `${attempt.error.code}: ${attempt.error.message}` } : {}),
|
|
185
|
-
...(attempt.setup ? { setup: attempt.setup } : {}),
|
|
186
|
-
});
|
|
177
|
+
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 });
|
|
187
178
|
}
|
|
188
179
|
if (!attempts.length) {
|
|
189
180
|
const cost = agent.accounting?.cost ?? 0;
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CreateAgentSessionOptions, InlineExtension, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
export const RUN_STATES = ["queued", "running", "pausing", "paused", "awaiting_input", "completed", "failed", "stopped", "interrupted", "budget_exhausted"] as const;
|
|
3
3
|
export const AGENT_STATES = ["queued", "running", "waiting_for_child", "paused", "retrying", "completed", "failed", "cancelled"] as const;
|
|
4
4
|
export const WORKFLOW_CALL_KINDS = ["agent", "parallel", "pipeline", "checkpoint", "phase", "withWorktree", "shell"] as const;
|
|
@@ -66,22 +66,23 @@ export interface WorkflowSettingsOverrides { concurrency?: number; modelAliases?
|
|
|
66
66
|
export interface WorkflowSettingsSources { concurrency: string; modelAliases: string; disabledAgentResources: string }
|
|
67
67
|
export interface WorkflowSettingsResolution { globalSettingsPath: string; projectSettingsPath: string; projectTrusted: boolean; global: Readonly<WorkflowSettings>; project: Readonly<WorkflowSettingsOverrides>; effective: Readonly<WorkflowSettings>; sources: Readonly<WorkflowSettingsSources> }
|
|
68
68
|
export interface AgentResourceExclusions { skills: readonly string[]; extensions: readonly string[] }
|
|
69
|
-
export interface AgentResourcePolicy { globalSettingsPath: string; projectSettingsPath: string; projectTrusted: boolean; global: AgentResourceExclusions; project: AgentResourceExclusions; effective: AgentResourceExclusions; unmatchedSkills: string[]; unmatchedExtensions: string[]; excludedSkills?: string[]; excludedExtensions?: string[] }
|
|
69
|
+
export interface AgentResourcePolicy { globalSettingsPath: string; projectSettingsPath: string; projectTrusted: boolean; global: AgentResourceExclusions; project: AgentResourceExclusions; effective: AgentResourceExclusions; unmatchedSkills: readonly string[]; unmatchedExtensions: readonly string[]; excludedSkills?: readonly string[]; excludedExtensions?: readonly string[] }
|
|
70
70
|
export interface AgentActivity { kind: "reasoning" | "tool" | "text"; text: string }
|
|
71
71
|
export const WORKFLOW_AGENT_STALL_THRESHOLD_MS = 10 * 60 * 1000;
|
|
72
72
|
export interface AgentAccounting { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number }
|
|
73
73
|
export interface AgentSetupSummary { hookNames: readonly string[]; model: ModelSpec; tools: readonly string[]; cwd: string; disabledAgentResources?: { skills: readonly string[]; extensions: readonly string[]; excludedSkills?: readonly string[]; excludedExtensions?: readonly string[]; unmatchedSkills: readonly string[]; unmatchedExtensions: readonly string[] } }
|
|
74
|
-
export interface
|
|
74
|
+
export interface AgentAttemptError { code: string; message: string }
|
|
75
|
+
export interface AgentAttemptSummary { attempt: number; transport: string; session?: WorkflowAgentSessionReference; setup: AgentSetupSummary; error?: AgentAttemptError; accounting: AgentAccounting }
|
|
75
76
|
export interface WorkflowWorktreeCreatedEvent extends WorkflowEventBase { owner: string; branch: string; path: string; base: string }
|
|
76
77
|
export interface WorkflowWorktreeReference { readonly path: string; readonly branch: string }
|
|
77
|
-
export interface AgentRecord { id: string; name: string; label?: string; path: string; state: AgentState; parentId?: string; structuralPath?: readonly string[]; resultPath?: string; parentBreadcrumb?: string; worktreeOwner?: string; role?: string; requestedModel?: string; model: ModelSpec; tools: readonly string[]; attempts: number; attemptDetails?: readonly AgentAttemptSummary[]; accounting?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number }; toolCalls?: readonly { id: string; name: string; state: "running" | "completed" | "failed" }[]; activity?: AgentActivity | undefined; lastEventAt?: number }
|
|
78
|
+
export interface AgentRecord { systemPrompt?: string; prompt?: string; id: string; name: string; label?: string; path: string; state: AgentState; parentId?: string; structuralPath?: readonly string[]; resultPath?: string; parentBreadcrumb?: string; worktreeOwner?: string; role?: string; requestedModel?: string; model: ModelSpec; tools: readonly string[]; attempts: number; attemptDetails?: readonly AgentAttemptSummary[]; accounting?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number }; toolCalls?: readonly { id: string; name: string; state: "running" | "completed" | "failed" }[]; activity?: AgentActivity | undefined; lastEventAt?: number }
|
|
78
79
|
export type WorkflowDeliveryMode = "foreground" | "background";
|
|
79
80
|
export type WorkflowDeliveryStatus = "attached" | "pending" | "delivered";
|
|
80
81
|
export interface WorkflowRunDelivery { mode: WorkflowDeliveryMode; state: WorkflowDeliveryStatus; toolCallId?: string }
|
|
81
82
|
export interface WorkflowRunEvent { type: string; message: string }
|
|
82
83
|
export interface WorkflowRetryProvenance { sourceRunId: string; lineageRootRunId: string; completedPaths: readonly string[]; incompletePaths: readonly string[]; namedWorktrees: readonly string[] }
|
|
83
84
|
export interface WorkflowPhaseRecord { phase: string; afterAgent: number }
|
|
84
|
-
export interface RunRecord { id: string; workflowName: string; cwd: string; sessionId: string; state: RunState; parentRunId?: string; retry?: WorkflowRetryProvenance; phase?: string; phaseHistory?: readonly WorkflowPhaseRecord[]; agents: readonly AgentRecord[]; activeShells?: number; error?: WorkflowErrorShape; failedAt?: string; budget?: WorkflowBudget; budgetVersion?: number; usage?: WorkflowBudgetUsage; budgetEvents?: readonly BudgetEvent[]; events?: readonly WorkflowRunEvent[]; delivery?: WorkflowRunDelivery }
|
|
85
|
+
export interface RunRecord { id: string; workflowName: string; cwd: string; sessionId: string; state: RunState; agentSessions: readonly WorkflowAgentSessionReference[]; parentRunId?: string; retry?: WorkflowRetryProvenance; phase?: string; phaseHistory?: readonly WorkflowPhaseRecord[]; agents: readonly AgentRecord[]; activeShells?: number; error?: WorkflowErrorShape; failedAt?: string; budget?: WorkflowBudget; budgetVersion?: number; usage?: WorkflowBudgetUsage; budgetEvents?: readonly BudgetEvent[]; events?: readonly WorkflowRunEvent[]; delivery?: WorkflowRunDelivery }
|
|
85
86
|
export const LAUNCH_SNAPSHOT_IDENTITY_VERSION = 5;
|
|
86
87
|
export type WorkflowLaunchMode = "foreground" | "background";
|
|
87
88
|
export interface AgentDefinition { prompt?: string; description?: string; model?: string; thinking?: NonNullable<ModelSpec["thinking"]>; tools?: readonly string[]; overrideSystemPrompt?: boolean; disabledAgentResources?: AgentResourceExclusions }
|
|
@@ -94,23 +95,21 @@ export interface WorkflowFunctionContext extends WorkflowOrchestrationContext {
|
|
|
94
95
|
export type WorkflowWorktreeCallback = (reference: Readonly<WorkflowWorktreeReference>) => JsonValue | Promise<JsonValue>;
|
|
95
96
|
export interface WorkflowFunction { description: string; input: JsonSchema; output: JsonSchema; run: (input: Readonly<Record<string, JsonValue>>, context: Readonly<WorkflowFunctionContext>) => Promise<JsonValue> | JsonValue }
|
|
96
97
|
export interface WorkflowVariable { description: string; schema: JsonSchema; resolve: (run: Readonly<WorkflowRunContext>) => Promise<JsonValue> | JsonValue }
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
export interface
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
readonly
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
abort?(): Promise<void>;
|
|
113
|
-
dispose(): void;
|
|
98
|
+
export interface WorkflowAgentSessionReference { readonly transport: string; readonly sessionId: string; readonly locator?: JsonValue }
|
|
99
|
+
export interface WorkflowAgentSessionStats { readonly tokens: { readonly input: number; readonly output: number; readonly cacheRead: number; readonly cacheWrite: number; readonly total: number }; readonly cost: number }
|
|
100
|
+
export interface WorkflowAgentMessage { readonly role: string; readonly content?: unknown; readonly stopReason?: string; readonly errorMessage?: string; readonly usage?: { readonly input: number; readonly output: number; readonly cacheRead: number; readonly cacheWrite: number; readonly cost: { readonly total: number } } }
|
|
101
|
+
export interface WorkflowAgentSessionState { readonly model: ModelSpec; readonly thinking?: ModelSpec["thinking"]; readonly tools: readonly string[]; readonly systemPrompt?: string }
|
|
102
|
+
export interface WorkflowAgentSessionEvent { readonly type: string; readonly state?: Readonly<WorkflowAgentSessionState>; readonly message?: WorkflowAgentMessage; readonly assistantMessageEvent?: { readonly type: string }; readonly toolCallId?: string; readonly toolName?: string; readonly isError?: boolean }
|
|
103
|
+
export interface WorkflowAgentTurnResult { readonly assistant?: WorkflowAgentMessage }
|
|
104
|
+
export interface WorkflowAgentSession {
|
|
105
|
+
readonly reference: WorkflowAgentSessionReference;
|
|
106
|
+
getState(): Readonly<WorkflowAgentSessionState>;
|
|
107
|
+
getSessionStats(): WorkflowAgentSessionStats;
|
|
108
|
+
subscribe(listener: (event: WorkflowAgentSessionEvent) => void): () => void;
|
|
109
|
+
prompt(text: string): Promise<WorkflowAgentTurnResult>;
|
|
110
|
+
steer(text: string): Promise<void>;
|
|
111
|
+
abort(): Promise<void>;
|
|
112
|
+
dispose(): Promise<void>;
|
|
114
113
|
}
|
|
115
114
|
type SessionTools = NonNullable<CreateAgentSessionOptions["tools"]>;
|
|
116
115
|
type SessionCustomTools = NonNullable<CreateAgentSessionOptions["customTools"]>;
|
|
@@ -129,17 +128,36 @@ export interface SessionInput {
|
|
|
129
128
|
resourcePolicy?: AgentResourcePolicy;
|
|
130
129
|
options?: AgentOptions;
|
|
131
130
|
}
|
|
132
|
-
export
|
|
133
|
-
|
|
131
|
+
export interface PreparedAgentSession {
|
|
132
|
+
readonly cwd: string;
|
|
133
|
+
readonly model: ModelSpec;
|
|
134
|
+
readonly tools: readonly string[];
|
|
135
|
+
readonly sessionLabel: string;
|
|
136
|
+
readonly agentDir?: string;
|
|
137
|
+
readonly customTools?: readonly ToolDefinition[];
|
|
138
|
+
readonly resultTool?: ToolDefinition;
|
|
139
|
+
readonly options?: Readonly<Record<string, JsonValue>>;
|
|
140
|
+
readonly systemPrompt?: string;
|
|
141
|
+
readonly systemPromptAppend?: string;
|
|
142
|
+
readonly extensionFactories?: readonly InlineExtension[];
|
|
143
|
+
readonly additionalSkillPaths?: readonly string[];
|
|
144
|
+
readonly resourcePolicy?: Readonly<AgentResourcePolicy>;
|
|
145
|
+
}
|
|
146
|
+
export interface AgentTransportContext { readonly run: Readonly<WorkflowRunContext>; readonly identity: Readonly<AgentIdentity>; readonly attempt: number; readonly signal: AbortSignal }
|
|
147
|
+
export interface AgentTransport { readonly id: string; createSession(prepared: Readonly<PreparedAgentSession>, context: Readonly<AgentTransportContext>): Promise<WorkflowAgentSession> }
|
|
148
|
+
export interface AgentSetup { prompt: string; options: AgentOptions; sessionInput: SessionInput; prepared: Readonly<PreparedAgentSession>; transport: AgentTransport }
|
|
134
149
|
export interface AgentSetupContext { readonly run: Readonly<WorkflowRunContext>; readonly identity: Readonly<AgentIdentity>; readonly attempt: number; readonly signal: AbortSignal }
|
|
135
150
|
export interface AgentSetupHook { priority?: number; setup: (agent: AgentSetup, context: Readonly<AgentSetupContext>) => void | Promise<void> }
|
|
136
151
|
export interface RegisteredAgentSetupHook { name: string; priority: number; setup: AgentSetupHook["setup"] }
|
|
137
152
|
export interface WorkflowExtensionMetadata { version: string; headline: string; description: string }
|
|
138
153
|
export interface WorkflowRoleDirectoryRegistration { path: string; extension: WorkflowExtensionMetadata }
|
|
139
|
-
export interface
|
|
154
|
+
export interface AgentAttemptActionUi { notify(message: string, level?: "info" | "warning" | "error"): void; confirm(title: string, message: string): Promise<boolean>; select(title: string, options: readonly string[]): Promise<string | undefined>; input(title: string, placeholder?: string): Promise<string | undefined> }
|
|
155
|
+
export interface AgentAttemptActionContext { readonly run: Readonly<RunRecord>; readonly agent: Readonly<AgentRecord>; readonly attempt: Readonly<AgentAttemptSummary>; readonly session?: WorkflowAgentSessionReference; readonly liveSession?: WorkflowAgentSession; readonly signal: AbortSignal; readonly ui: Readonly<AgentAttemptActionUi> }
|
|
156
|
+
export interface AgentAttemptAction { readonly label: string; visible(context: Readonly<AgentAttemptActionContext>): boolean; run(context: Readonly<AgentAttemptActionContext>): void | Promise<void> }
|
|
157
|
+
export interface WorkflowExtension extends WorkflowExtensionMetadata { functions?: Readonly<Record<string, WorkflowFunction>>; variables?: Readonly<Record<string, WorkflowVariable>>; modelAliases?: Readonly<Record<string, WorkflowModelAlias>>; agentSetupHooks?: Readonly<Record<string, AgentSetupHook>>; agentAttemptActions?: Readonly<Record<string, AgentAttemptAction>>; roleDirectories?: readonly (string | URL)[] }
|
|
140
158
|
export interface WorkflowJournal { get(path: string): JsonValue | undefined; put(path: string, value: JsonValue): void }
|
|
141
159
|
export class WorkflowError extends Error { constructor(public readonly code: WorkflowErrorCode, message: string) { super(message); this.name = "WorkflowError"; } }
|
|
142
|
-
export interface WorkflowFailureAgent { id: string; label?: string; role?: string; structuralPath: readonly string[]; attempt: number;
|
|
160
|
+
export interface WorkflowFailureAgent { id: string; label?: string; role?: string; structuralPath: readonly string[]; attempt: number; transport?: string; session?: WorkflowAgentSessionReference }
|
|
143
161
|
export interface WorkflowSiblingAgent { id: string; label?: string; role?: string; structuralPath: readonly string[] }
|
|
144
162
|
export interface WorkflowFailureDiagnostics { runId: string; workflowName: string; state: RunState; failedAt: string | null; error: WorkflowErrorShape; failedAgent?: WorkflowFailureAgent; completedSiblingAgents?: readonly WorkflowSiblingAgent[]; completedSiblingPaths: readonly (readonly string[])[]; retry?: { sourceRunId: string; action: string; completedPaths: readonly string[]; incompletePaths: readonly string[]; namedWorktrees: readonly string[]; warning: string }; artifacts: { runDirectory: string; statePath: string; journalPath: string } }
|
|
145
163
|
export interface CheckpointInput { name: string; prompt: string; context: JsonValue }
|
|
@@ -160,6 +178,6 @@ export interface WorkflowCatalogIndexFunction { name: string; description: strin
|
|
|
160
178
|
export interface WorkflowCatalogIndexVariable { name: string; description: string; schema: JsonSchema }
|
|
161
179
|
export interface WorkflowCatalogIndex { functions: readonly WorkflowCatalogIndexFunction[]; variables: readonly WorkflowCatalogIndexVariable[]; modelAliases?: Readonly<Record<string, string>>; modelAliasEntries?: readonly WorkflowCatalogModelAlias[]; settings?: WorkflowCatalogSettings }
|
|
162
180
|
export interface WorkflowCatalogError { error: { code: "NOT_FOUND"; name: string; message: string } }
|
|
163
|
-
export interface WorkflowValidationParameters { name?: string; description?: string; script?: string; workflow?: string; args?: unknown }
|
|
181
|
+
export interface WorkflowValidationParameters { name?: string; description?: string; script?: string; scriptPath?: string; workflow?: string; args?: unknown }
|
|
164
182
|
export interface WorkflowValidationContext { cwd: string; projectTrusted: boolean; availableModels: ReadonlySet<string>; rootTools: ReadonlySet<string>; modelAliases?: Readonly<Record<string, string>>; knownModels?: ReadonlySet<string>; settingsPath?: string; agentDir?: string }
|
|
165
183
|
export interface ValidatedWorkflowLaunch { script: string; checked: PreflightResult; agentDefinitions: Readonly<Record<string, AgentDefinition>>; projectAgentDefinitions: Readonly<Record<string, AgentDefinition>>; roleNames: readonly string[]; functionName?: string }
|
package/src/validation.ts
CHANGED
|
@@ -682,16 +682,25 @@ export function validateWorkflowLaunch(params: WorkflowValidationParameters, con
|
|
|
682
682
|
export function validateWorkflowLaunchWithRegistry(params: WorkflowValidationParameters, context: WorkflowValidationContext, registry?: WorkflowRegistryApi): ValidatedWorkflowLaunch {
|
|
683
683
|
if (Object.prototype.hasOwnProperty.call(params, "maxAgentLaunches")) fail("INVALID_METADATA", "maxAgentLaunches has been removed; use budget.agentLaunches");
|
|
684
684
|
if (params.script !== undefined && params.workflow !== undefined) fail("INVALID_METADATA", "Provide either script or workflow, not both");
|
|
685
|
+
if (params.scriptPath !== undefined && (typeof params.scriptPath !== "string" || !params.scriptPath.trim())) fail("INVALID_METADATA", "scriptPath must be a non-empty path");
|
|
686
|
+
if ((params.script !== undefined || params.workflow !== undefined) && params.scriptPath !== undefined) fail("INVALID_METADATA", "Provide either script, scriptPath, or workflow, not more than one");
|
|
687
|
+
const scriptPath = typeof params.scriptPath === "string" ? params.scriptPath.trim() : undefined;
|
|
688
|
+
let fileScript: string | undefined;
|
|
689
|
+
if (scriptPath !== undefined) {
|
|
690
|
+
try { fileScript = readFileSync(resolve(context.cwd, scriptPath), "utf8"); }
|
|
691
|
+
catch (error) { fail("INVALID_SYNTAX", `Cannot read workflow script file ${scriptPath}: ${errorText(error)}`); }
|
|
692
|
+
}
|
|
685
693
|
const functionName = typeof params.workflow === "string" ? params.workflow : undefined;
|
|
686
|
-
|
|
687
|
-
|
|
694
|
+
const explicitName = params.name === undefined ? undefined : typeof params.name === "string" ? params.name.trim() : "";
|
|
695
|
+
if (params.name !== undefined && !explicitName) fail("INVALID_METADATA", "Workflow name must be non-empty when provided");
|
|
696
|
+
const workflowName = explicitName ?? functionName ?? "";
|
|
688
697
|
if (functionName === undefined && !workflowName) fail("INVALID_METADATA", "Inline workflow launches require a non-empty name");
|
|
689
698
|
const fn = functionName === undefined ? undefined : registry?.function(functionName);
|
|
690
699
|
if (functionName !== undefined && !registry) fail("MISSING_WORKFLOW", `Registered function is unavailable: ${functionName}`);
|
|
691
700
|
const args = params.args === undefined ? null : params.args;
|
|
692
701
|
if (functionName !== undefined && fn && (!object(args) || !jsonValue(args) || !Value.Check(fn.input, args))) fail("RESULT_INVALID", `Invalid input for ${functionName}`);
|
|
693
|
-
const script = functionName !== undefined && fn ? functionLaunchScript(functionName) : typeof params.script === "string" && params.script.trim() ? params.script : "";
|
|
694
|
-
if (!script) fail("INVALID_SYNTAX", "Provide script or registered function");
|
|
702
|
+
const script = functionName !== undefined && fn ? functionLaunchScript(functionName) : typeof params.script === "string" && params.script.trim() ? params.script : fileScript ?? "";
|
|
703
|
+
if (!script) fail("INVALID_SYNTAX", "Provide script, scriptPath, or registered function");
|
|
695
704
|
const metadata = validateWorkflowMetadata({ name: workflowName, ...(typeof params.description === "string" ? { description: params.description } : fn?.description ? { description: fn.description } : {}) });
|
|
696
705
|
const globalAgentDefinitions = loadAgentDefinitions(context.cwd, context.agentDir, false, registry && typeof registry.roleDirectoryRegistrations === "function" ? registry.roleDirectoryRegistrations() : registry && typeof registry.roleDirectories === "function" ? registry.roleDirectories() : undefined);
|
|
697
706
|
const projectAgentDefinitions = context.projectTrusted ? readRoleDefinitions(projectRoleDirectories(join(context.cwd, ".pi"))) : {};
|
|
@@ -8,6 +8,7 @@ export interface WorkflowArtifact { extension: ".js" | ".json" | ".md"; content:
|
|
|
8
8
|
export type WorkflowTui = { stop(): void; start(): void; requestRender(force?: boolean): void };
|
|
9
9
|
|
|
10
10
|
export function workflowScriptArtifact(script: string): WorkflowArtifact { return { extension: ".js", content: script }; }
|
|
11
|
+
export function workflowPromptArtifact(prompt: string): WorkflowArtifact { return { extension: ".md", content: prompt }; }
|
|
11
12
|
export function workflowResultArtifact(value: JsonValue): WorkflowArtifact { return typeof value === "string" ? { extension: ".md", content: value } : { extension: ".json", content: `${JSON.stringify(value, null, 2)}\n` }; }
|
|
12
13
|
|
|
13
14
|
async function spawnWorkflowEditor(command: string, path: string): Promise<number | null> {
|