pi-extensible-workflows 1.0.1 → 3.0.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 +9 -2
- package/dist/src/agent-execution.d.ts +13 -14
- package/dist/src/agent-execution.js +110 -197
- package/dist/src/budget.d.ts +38 -0
- package/dist/src/budget.js +160 -0
- package/dist/src/cli.d.ts +9 -0
- package/dist/src/cli.js +536 -6
- package/dist/src/doctor.d.ts +4 -4
- package/dist/src/doctor.js +9 -29
- package/dist/src/execution.d.ts +17 -0
- package/dist/src/execution.js +630 -0
- package/dist/src/herdr.d.ts +12 -0
- package/dist/src/herdr.js +74 -0
- package/dist/src/host.d.ts +62 -0
- package/dist/src/host.js +2696 -0
- package/dist/src/index.d.ts +11 -557
- package/dist/src/index.js +8 -3974
- package/dist/src/persistence.d.ts +32 -19
- package/dist/src/persistence.js +310 -70
- package/dist/src/registry.d.ts +31 -0
- package/dist/src/registry.js +169 -0
- package/dist/src/session-inspector.d.ts +1 -0
- package/dist/src/session-inspector.js +4 -1
- package/dist/src/types.d.ts +565 -0
- package/dist/src/types.js +27 -0
- package/dist/src/utils.d.ts +26 -0
- package/dist/src/utils.js +128 -0
- package/dist/src/validation.d.ts +24 -0
- package/dist/src/validation.js +740 -0
- package/dist/src/workflow-evals.js +2 -1
- package/package.json +4 -3
- package/skills/pi-extensible-workflows/SKILL.md +52 -67
- package/src/agent-execution.ts +84 -147
- package/src/budget.ts +75 -0
- package/src/cli.ts +427 -6
- package/src/doctor.ts +13 -32
- package/src/execution.ts +540 -0
- package/src/herdr.ts +73 -0
- package/src/host.ts +2265 -0
- package/src/index.ts +11 -3453
- package/src/persistence.ts +255 -47
- package/src/registry.ts +157 -0
- package/src/session-inspector.ts +5 -1
- package/src/types.ts +113 -0
- package/src/utils.ts +108 -0
- package/src/validation.ts +616 -0
- package/src/workflow-evals.ts +2 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Value } from "typebox/value";
|
|
2
|
+
import { deepFreeze, fail, jsonValue, object } from "./utils.js";
|
|
3
|
+
import { loadSettings, validateSchema } from "./validation.js";
|
|
4
|
+
const RESERVED_GLOBALS = new Set(["agent", "shell", "prompt", "checkpoint", "parallel", "pipeline", "phase", "withWorktree", "log", "args", "Promise", "JSON", "Math", "Date", "eval", "Function", "WebAssembly", "process", "require", "module", "exports", "console", "fetch", "XMLHttpRequest", "WebSocket", "performance", "crypto", "setTimeout", "setInterval", "setImmediate", "queueMicrotask", "Intl", "SharedArrayBuffer", "Atomics", "globalThis", "global", "undefined", "NaN", "Infinity", "extensions", "workflow_catalog"]);
|
|
5
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
6
|
+
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
7
|
+
export class WorkflowRegistry {
|
|
8
|
+
#extensions = new Set();
|
|
9
|
+
#globals = new Map();
|
|
10
|
+
#hooks = new Map();
|
|
11
|
+
#frozen = false;
|
|
12
|
+
get frozen() { return this.#frozen; }
|
|
13
|
+
freeze() { this.#frozen = true; }
|
|
14
|
+
register(extension) {
|
|
15
|
+
if (this.#frozen)
|
|
16
|
+
fail("REGISTRY_FROZEN", "Workflow extension registration is closed after session_start");
|
|
17
|
+
if (object(extension) && Object.prototype.hasOwnProperty.call(extension, "workflows"))
|
|
18
|
+
fail("INVALID_METADATA", "Separate registered workflow definitions were removed; register a function with input and output schemas instead");
|
|
19
|
+
if (!object(extension) || Object.keys(extension).some((key) => !["version", "headline", "description", "functions", "variables", "agentSetupHooks"].includes(key)) || typeof extension.version !== "string" || !SEMVER.test(extension.version) || typeof extension.headline !== "string" || !extension.headline.trim() || typeof extension.description !== "string" || !extension.description.trim())
|
|
20
|
+
fail("INVALID_METADATA", "Workflow extensions require a semantic version, headline, and description");
|
|
21
|
+
const functions = extension.functions ?? {};
|
|
22
|
+
const variables = extension.variables ?? {};
|
|
23
|
+
const agentSetupHooks = extension.agentSetupHooks ?? {};
|
|
24
|
+
if (!object(functions) || !object(variables) || !object(agentSetupHooks) || (Object.keys(functions).length === 0 && Object.keys(variables).length === 0 && Object.keys(agentSetupHooks).length === 0))
|
|
25
|
+
fail("INVALID_METADATA", "Workflow extensions require functions, variables, or agent setup hooks");
|
|
26
|
+
const names = [...Object.keys(functions), ...Object.keys(variables)];
|
|
27
|
+
if (new Set(names).size !== names.length)
|
|
28
|
+
fail("GLOBAL_COLLISION", "Global name collision inside extension");
|
|
29
|
+
for (const name of names) {
|
|
30
|
+
if (!IDENTIFIER.test(name) || name.startsWith("__pi_extensible_workflows_"))
|
|
31
|
+
fail("INVALID_METADATA", `Invalid global name: ${name}`);
|
|
32
|
+
if (RESERVED_GLOBALS.has(name))
|
|
33
|
+
fail("GLOBAL_COLLISION", `Global name is reserved: ${name}`);
|
|
34
|
+
if (this.#globals.has(name))
|
|
35
|
+
fail("GLOBAL_COLLISION", `Global name is already registered: ${name}`);
|
|
36
|
+
}
|
|
37
|
+
for (const [name, fn] of Object.entries(functions)) {
|
|
38
|
+
if (!object(fn) || Object.keys(fn).some((key) => !["description", "input", "output", "run"].includes(key)) || typeof fn.description !== "string" || !fn.description.trim() || typeof fn.run !== "function")
|
|
39
|
+
fail("INVALID_METADATA", `Invalid workflow function: ${name}`);
|
|
40
|
+
validateSchema(fn.input, `${name} input`);
|
|
41
|
+
validateSchema(fn.output, `${name} output`);
|
|
42
|
+
if (fn.input.type !== "object")
|
|
43
|
+
fail("INVALID_SCHEMA", `${name} input must describe one object`);
|
|
44
|
+
}
|
|
45
|
+
for (const [name, variable] of Object.entries(variables)) {
|
|
46
|
+
if (!object(variable) || Object.keys(variable).some((key) => !["description", "schema", "resolve"].includes(key)) || typeof variable.description !== "string" || !variable.description.trim() || typeof variable.resolve !== "function")
|
|
47
|
+
fail("INVALID_METADATA", `Invalid workflow variable: ${name}`);
|
|
48
|
+
validateSchema(variable.schema, `${name} schema`);
|
|
49
|
+
}
|
|
50
|
+
for (const [name, hook] of Object.entries(agentSetupHooks)) {
|
|
51
|
+
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)))
|
|
52
|
+
fail("INVALID_METADATA", `Invalid agent setup hook: ${name}`);
|
|
53
|
+
if (this.#hooks.has(name))
|
|
54
|
+
fail("DUPLICATE_NAME", `Agent setup hook already registered: ${name}`);
|
|
55
|
+
}
|
|
56
|
+
const stored = deepFreeze({ ...extension, functions, variables, agentSetupHooks });
|
|
57
|
+
this.#extensions.add(stored);
|
|
58
|
+
for (const name of names)
|
|
59
|
+
this.#globals.set(name, name);
|
|
60
|
+
for (const [name, hook] of Object.entries(agentSetupHooks))
|
|
61
|
+
this.#hooks.set(name, { name, priority: hook.priority ?? 10, setup: hook.setup });
|
|
62
|
+
}
|
|
63
|
+
function(name) {
|
|
64
|
+
if (!IDENTIFIER.test(name))
|
|
65
|
+
fail("MISSING_WORKFLOW", `Registered functions require an unqualified name: ${name}`);
|
|
66
|
+
const fn = [...this.#extensions].find((extension) => extension.functions?.[name])?.functions?.[name];
|
|
67
|
+
if (!fn)
|
|
68
|
+
fail("MISSING_WORKFLOW", `Registered function is unavailable: ${name}; the separate registered-workflow format was removed`);
|
|
69
|
+
return fn;
|
|
70
|
+
}
|
|
71
|
+
functions() {
|
|
72
|
+
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension) => Object.entries(extension.functions ?? {}))));
|
|
73
|
+
}
|
|
74
|
+
catalog() {
|
|
75
|
+
const functions = [];
|
|
76
|
+
const variables = [];
|
|
77
|
+
for (const extension of this.#extensions) {
|
|
78
|
+
for (const [name, fn] of Object.entries(extension.functions ?? {}))
|
|
79
|
+
functions.push({ name, version: extension.version, headline: extension.headline, extensionDescription: extension.description, description: fn.description, input: structuredClone(fn.input), output: structuredClone(fn.output) });
|
|
80
|
+
for (const [name, variable] of Object.entries(extension.variables ?? {}))
|
|
81
|
+
variables.push({ name, version: extension.version, headline: extension.headline, extensionDescription: extension.description, description: variable.description, schema: structuredClone(variable.schema) });
|
|
82
|
+
}
|
|
83
|
+
let aliases;
|
|
84
|
+
try {
|
|
85
|
+
aliases = loadSettings().modelAliases;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
aliases = undefined;
|
|
89
|
+
}
|
|
90
|
+
const sort = (left, right) => left.name.localeCompare(right.name);
|
|
91
|
+
return deepFreeze({ functions: functions.sort(sort), variables: variables.sort(sort), ...(aliases ? { modelAliases: structuredClone(aliases) } : {}) });
|
|
92
|
+
}
|
|
93
|
+
catalogIndex() {
|
|
94
|
+
const catalog = this.catalog();
|
|
95
|
+
return deepFreeze({
|
|
96
|
+
functions: catalog.functions.map(({ name, description, input }) => ({ name, description, input: structuredClone(input) })),
|
|
97
|
+
variables: catalog.variables.map(({ name, description, schema }) => ({ name, description, schema: structuredClone(schema) })),
|
|
98
|
+
...(catalog.modelAliases ? { modelAliases: structuredClone(catalog.modelAliases) } : {}),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catalogDetail(name) {
|
|
102
|
+
const catalog = this.catalog();
|
|
103
|
+
const entry = catalog.functions.find((candidate) => candidate.name === name) ?? catalog.variables.find((candidate) => candidate.name === name);
|
|
104
|
+
if (entry)
|
|
105
|
+
return entry;
|
|
106
|
+
return deepFreeze({ error: { code: "NOT_FOUND", name, message: `No registered workflow function or variable is available: ${name}` } });
|
|
107
|
+
}
|
|
108
|
+
globals() {
|
|
109
|
+
return Object.freeze(Object.fromEntries([...this.#extensions].flatMap((extension) => Object.keys(extension.functions ?? {}).map((name) => [name, { name }]))));
|
|
110
|
+
}
|
|
111
|
+
async invokeFunction(name, input, context, path, journal) {
|
|
112
|
+
const fn = this.function(name);
|
|
113
|
+
if (!object(input) || !jsonValue(input) || !Value.Check(fn.input, input))
|
|
114
|
+
fail("RESULT_INVALID", `Invalid input for ${name}`);
|
|
115
|
+
const replayed = journal.get(path);
|
|
116
|
+
if (replayed !== undefined) {
|
|
117
|
+
if (!jsonValue(replayed) || !Value.Check(fn.output, replayed))
|
|
118
|
+
fail("RESULT_INVALID", `Invalid replay for ${name}`);
|
|
119
|
+
return structuredClone(replayed);
|
|
120
|
+
}
|
|
121
|
+
const result = await fn.run(deepFreeze(structuredClone(input)), Object.freeze({ run: context.run, invoke: context.invoke, agent: context.agent, shell: context.shell, prompt: context.prompt, parallel: context.parallel, pipeline: context.pipeline, withWorktree: context.withWorktree, checkpoint: context.checkpoint, phase: context.phase, log: context.log }));
|
|
122
|
+
if (!jsonValue(result) || !Value.Check(fn.output, result))
|
|
123
|
+
fail("RESULT_INVALID", `Invalid output from ${name}`);
|
|
124
|
+
const stored = structuredClone(result);
|
|
125
|
+
journal.put(path, stored);
|
|
126
|
+
return structuredClone(stored);
|
|
127
|
+
}
|
|
128
|
+
variables() {
|
|
129
|
+
return [...this.#extensions].flatMap((extension) => Object.entries(extension.variables ?? {}).map(([name, variable]) => ({ name, variable })));
|
|
130
|
+
}
|
|
131
|
+
agentSetupHooks() {
|
|
132
|
+
return [...this.#hooks.values()].sort((left, right) => left.priority - right.priority || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const WORKFLOW_REGISTRY_KEY = Symbol.for("pi-extensible-workflows.workflow-registry");
|
|
136
|
+
const globalRegistry = globalThis;
|
|
137
|
+
function createWorkflowRegistryApi(registry) {
|
|
138
|
+
return {
|
|
139
|
+
get frozen() { return registry.frozen; },
|
|
140
|
+
freeze: () => { registry.freeze(); },
|
|
141
|
+
register: (extension) => { registry.register(extension); },
|
|
142
|
+
function: (name) => registry.function(name),
|
|
143
|
+
functions: () => registry.functions(),
|
|
144
|
+
catalog: () => registry.catalog(),
|
|
145
|
+
catalogIndex: () => registry.catalogIndex(),
|
|
146
|
+
catalogDetail: (name) => registry.catalogDetail(name),
|
|
147
|
+
globals: () => registry.globals(),
|
|
148
|
+
invokeFunction: (...args) => registry.invokeFunction(...args),
|
|
149
|
+
variables: () => registry.variables(),
|
|
150
|
+
agentSetupHooks: () => registry.agentSetupHooks(),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function workflowRegistryHost() {
|
|
154
|
+
return globalRegistry[WORKFLOW_REGISTRY_KEY] ??= { api: createWorkflowRegistryApi(new WorkflowRegistry()) };
|
|
155
|
+
}
|
|
156
|
+
export function resetWorkflowRegistry() {
|
|
157
|
+
workflowRegistryHost().api = createWorkflowRegistryApi(new WorkflowRegistry());
|
|
158
|
+
}
|
|
159
|
+
export function beginWorkflowExtensionLoading() {
|
|
160
|
+
if (workflowRegistryHost().api.frozen)
|
|
161
|
+
resetWorkflowRegistry();
|
|
162
|
+
}
|
|
163
|
+
export function loadingRegistry() { return workflowRegistryHost().api; }
|
|
164
|
+
beginWorkflowExtensionLoading();
|
|
165
|
+
export function registerWorkflowExtension(extension) { loadingRegistry().register(extension); }
|
|
166
|
+
export function workflowCatalog() { return loadingRegistry().catalog(); }
|
|
167
|
+
export function workflowCatalogIndex() { return loadingRegistry().catalogIndex(); }
|
|
168
|
+
export function workflowCatalogDetail(name) { return loadingRegistry().catalogDetail(name); }
|
|
169
|
+
export function registeredWorkflowFunctions() { return loadingRegistry().functions(); }
|
|
@@ -63,6 +63,7 @@ export interface InspectorViewState {
|
|
|
63
63
|
scroll: number;
|
|
64
64
|
}
|
|
65
65
|
export declare function transcriptLines(entries: readonly SessionEntry[]): string[];
|
|
66
|
+
export declare function transcriptFileLines(path: string): string[];
|
|
66
67
|
export declare function matchSession(query: string, sessions: readonly SessionInfo[]): SessionInfo;
|
|
67
68
|
export declare function loadSessionReport(path: string, home?: string): Promise<SessionReport>;
|
|
68
69
|
export declare function renderInspector(report: SessionReport, state: InspectorViewState, width?: number, height?: number, highlighter?: (script: string) => string[]): string[];
|
|
@@ -45,6 +45,9 @@ export function transcriptLines(entries) {
|
|
|
45
45
|
return index ? ["", ...lines] : lines;
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
+
export function transcriptFileLines(path) {
|
|
49
|
+
return transcriptLines(SessionManager.open(path).buildContextEntries());
|
|
50
|
+
}
|
|
48
51
|
function mergedModels(groups) {
|
|
49
52
|
const totals = new Map();
|
|
50
53
|
for (const group of groups)
|
|
@@ -220,7 +223,7 @@ export async function loadSessionReport(path, home = homedir()) {
|
|
|
220
223
|
const args = call.arguments;
|
|
221
224
|
const agents = loaded ? await Promise.all(loaded.run.agents.map(agentReport)) : [];
|
|
222
225
|
const models = mergedModels(agents.flatMap(({ attempts }) => attempts.map(({ models: attemptModels }) => attemptModels)));
|
|
223
|
-
const name = typeof args.
|
|
226
|
+
const name = loaded?.run.workflowName ?? (typeof args.workflow === "string" ? args.workflow : typeof args.name === "string" ? args.name : "workflow");
|
|
224
227
|
const description = typeof args.description === "string" ? args.description : loaded?.snapshot.metadata.description;
|
|
225
228
|
const script = typeof args.script === "string" && args.script.trim() ? args.script : loaded?.snapshot.script;
|
|
226
229
|
let staticCalls = [];
|