pi-extensible-workflows 3.1.0 → 3.2.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 +11 -18
- package/dist/src/execution.js +13 -4
- package/dist/src/host.d.ts +44 -4
- package/dist/src/host.js +843 -395
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/types.d.ts +1 -0
- package/dist/src/validation.js +34 -0
- package/dist/src/workflow-artifacts.d.ts +13 -0
- package/dist/src/workflow-artifacts.js +39 -0
- package/examples/workflow-extension-template/README.md +37 -0
- package/examples/workflow-extension-template/extension.test.mjs +59 -0
- package/examples/workflow-extension-template/index.js +51 -0
- package/examples/workflow-extension-template/roles/reviewer.md +4 -0
- package/package.json +3 -2
- package/skills/pi-extensible-workflows/SKILL.md +21 -28
- package/src/execution.ts +13 -4
- package/src/host.ts +702 -362
- package/src/index.ts +1 -0
- package/src/types.ts +1 -1
- package/src/validation.ts +27 -0
- package/src/workflow-artifacts.ts +34 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Turn multi-agent tasks into deterministic jobs that fan out in parallel, pause for approval, and resume without rerunning completed work.
|
|
8
8
|
|
|
9
|
-
[Documentation](https://vekexasia.github.io/pi-extensible-workflows/) | [Developer guide](https://vekexasia.github.io/pi-extensible-workflows/developers.html) | [Agent guide](https://vekexasia.github.io/pi-extensible-workflows/agents.html) | [Extension authoring](https://vekexasia.github.io/pi-extensible-workflows/extensions.html)
|
|
9
|
+
[Documentation](https://vekexasia.github.io/pi-extensible-workflows/) | [Developer guide](https://vekexasia.github.io/pi-extensible-workflows/developers.html) | [Agent guide](https://vekexasia.github.io/pi-extensible-workflows/agents.html) | [Extension authoring](https://vekexasia.github.io/pi-extensible-workflows/extensions.html) | [Video overview](https://youtu.be/qAiivspEHmU)
|
|
10
10
|
|
|
11
11
|
Requires Node.js 22.19 or newer. This is a trusted Pi extension with the same filesystem and process access as Pi.
|
|
12
12
|
|
|
@@ -20,32 +20,24 @@ For source installs and local development, see the [installation guide](https://
|
|
|
20
20
|
|
|
21
21
|
## Capabilities
|
|
22
22
|
|
|
23
|
-
The
|
|
24
|
-
|
|
25
|
-
Inline workflow launches require a non-empty `name`; registered function launches reject `name` and use their registered function name as the run name. Workflow worktree scopes always use the explicit `withWorktree(name, callback)` form.
|
|
26
|
-
|
|
27
|
-
A workflow can fan out across specialized agents, combine their results, and resume without rerunning completed work.
|
|
23
|
+
The default path is a named inline workflow: write a `script` that fans out independent work with `parallel(...)`, awaits the keyed results, passes them into one summarizing `agent(...)`, and returns. Inline launches require a non-empty `name`; registered function launches reject `name` and use their registered function name as the run name. Runs are backgrounded by default; set `foreground: true` when the final value must be returned in the same tool call.
|
|
28
24
|
|
|
29
25
|
```js
|
|
30
26
|
const reviews = await parallel("review", {
|
|
31
|
-
correctness: () =>
|
|
32
|
-
|
|
33
|
-
security: () =>
|
|
34
|
-
agent("Review the current changes for security risks.", {
|
|
35
|
-
role: "security-specialist",
|
|
36
|
-
}),
|
|
27
|
+
correctness: () => agent("Review the current changes for correctness issues."),
|
|
28
|
+
security: () => agent("Review the current changes for security risks."),
|
|
37
29
|
tests: () => agent("Review the current changes for missing test coverage."),
|
|
38
30
|
});
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
prompt("
|
|
42
|
-
reviews,
|
|
43
|
-
}),
|
|
32
|
+
return await agent(
|
|
33
|
+
prompt("Summarize and prioritize these findings:\n\n{reviews}", { reviews }),
|
|
44
34
|
);
|
|
45
|
-
|
|
46
|
-
return summary;
|
|
47
35
|
```
|
|
48
36
|
|
|
37
|
+
**Advanced capabilities:** Use registered functions, `outputSchema`, budgets, checkpoints, worktrees, retry/resume, CLI export, and `pipeline(...)` when the task requires them. They remain available without complicating the basic inline path. Workflow worktree scopes always use the explicit `withWorktree(name, callback)` form.
|
|
38
|
+
|
|
39
|
+
The main Pi agent writes these scripts on the fly for each task; extensions can add reusable functions and variables, and completed workflows can resume without rerunning completed work.
|
|
40
|
+
|
|
49
41
|
Learn more about roles, workflow contracts, and extension APIs in the documentation:
|
|
50
42
|
|
|
51
43
|
- [Workflow tool and invocation API](https://vekexasia.github.io/pi-extensible-workflows/developers.html#tool-api)
|
|
@@ -53,6 +45,7 @@ Learn more about roles, workflow contracts, and extension APIs in the documentat
|
|
|
53
45
|
- [Aggregate run budgets](https://vekexasia.github.io/pi-extensible-workflows/developers.html#budgets)
|
|
54
46
|
- [Workflow DSL and worktrees](https://vekexasia.github.io/pi-extensible-workflows/developers.html#dsl)
|
|
55
47
|
- [Extension authoring guide](https://vekexasia.github.io/pi-extensible-workflows/extensions.html)
|
|
48
|
+
- [Copy-paste extension template](https://github.com/vekexasia/pi-extensible-workflows/tree/main/examples/workflow-extension-template)
|
|
56
49
|
- [Run artifacts and lifecycle events](https://vekexasia.github.io/pi-extensible-workflows/developers.html#lifecycle)
|
|
57
50
|
- [Run inspection and recovery](https://vekexasia.github.io/pi-extensible-workflows/developers.html#operations)
|
|
58
51
|
- [Agent patterns and model selection](https://vekexasia.github.io/pi-extensible-workflows/agents.html#patterns)
|
package/dist/src/execution.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fork, spawn } from "node:child_process";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -72,6 +72,7 @@ const named = (value, kind) => { if (typeof value !== "string" || !value.trim())
|
|
|
72
72
|
const path = (...names) => names.map(encodeURIComponent).join("/");
|
|
73
73
|
const inheritedAgentPath = new AsyncLocalStorage();
|
|
74
74
|
const agentOccurrences = new Map();
|
|
75
|
+
const agentInflight = new Set();
|
|
75
76
|
const shellOccurrences = new Map();
|
|
76
77
|
const worktreeOwners = new AsyncLocalStorage();
|
|
77
78
|
const rejectAgent = () => { throw workError("INVALID_METADATA", "Workflow agent calls must use a direct agent(...) call; aliases and indirect calls are unsupported"); };
|
|
@@ -92,14 +93,22 @@ const internalAgent = (...values) => {
|
|
|
92
93
|
const callSite = values.pop();
|
|
93
94
|
if (typeof callSite !== "string") throw workError("INTERNAL_ERROR", "Missing workflow agent call-site identity");
|
|
94
95
|
const inherited = inheritedAgentPath.getStore() || [];
|
|
95
|
-
// ponytail: same-callsite races outside parallel/pipeline lack a stable structural scope and are unsupported.
|
|
96
96
|
const occurrenceKey = JSON.stringify([inherited, callSite]);
|
|
97
|
+
if (agentInflight.has(occurrenceKey)) throw workError("INVALID_METADATA", "Concurrent agent calls from the same source call site are unsupported; use parallel(...) or pipeline(...)");
|
|
98
|
+
agentInflight.add(occurrenceKey);
|
|
97
99
|
const occurrence = (agentOccurrences.get(occurrenceKey) || 0) + 1;
|
|
98
100
|
agentOccurrences.set(occurrenceKey, occurrence);
|
|
99
101
|
const options = values.length < 2 || values[1] === undefined ? {} : values[1];
|
|
100
102
|
const worktreeOwner = worktreeOwners.getStore();
|
|
101
103
|
const identity = { structuralPath: [...inherited], callSite, occurrence, ...(worktreeOwner ? { worktreeOwner } : {}) };
|
|
102
|
-
|
|
104
|
+
let result;
|
|
105
|
+
try {
|
|
106
|
+
result = rpc("agent", [values[0], options, identity]).then(unwrap);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
agentInflight.delete(occurrenceKey);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
void result.then(() => agentInflight.delete(occurrenceKey), () => agentInflight.delete(occurrenceKey));
|
|
103
112
|
Object.defineProperties(result, {
|
|
104
113
|
toJSON: { value() { throw workError("INVALID_METADATA", "Workflow agent result is a Promise; await it before serialization"); } },
|
|
105
114
|
toString: { value() { throw workError("INVALID_METADATA", "Workflow agent result is a Promise; await it before interpolation"); } },
|
|
@@ -421,7 +430,7 @@ function workflowErrorFromWorker(error) {
|
|
|
421
430
|
export function runWorkflow(script, args = null, bridge = {}, signal) {
|
|
422
431
|
encoded(args);
|
|
423
432
|
const config = JSON.stringify({ script: instrumentWorkflow(script), args: structuredClone(args), functions: bridge.functions ?? {}, variables: bridge.variables ?? {} });
|
|
424
|
-
const childDir = mkdtempSync(join(tmpdir(), "pi-wf-"));
|
|
433
|
+
const childDir = realpathSync(mkdtempSync(join(tmpdir(), "pi-wf-")));
|
|
425
434
|
const childFile = join(childDir, "child.cjs");
|
|
426
435
|
writeFileSync(childFile, childSource);
|
|
427
436
|
const child = fork(childFile, [String(RPC_LIMIT_BYTES), config], {
|
package/dist/src/host.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ export declare function formatWorkflowPreview(args: {
|
|
|
34
34
|
description?: unknown;
|
|
35
35
|
}): string;
|
|
36
36
|
export declare const WORKFLOW_TOOL_LABEL = "Workflow";
|
|
37
|
-
export declare const WORKFLOW_TOOL_DESCRIPTION = "Run a deterministic JavaScript workflow";
|
|
38
|
-
export declare const WORKFLOW_TOOL_PROMPT_SNIPPET = "Run a deterministic, resumable JavaScript workflow that
|
|
37
|
+
export declare const WORKFLOW_TOOL_DESCRIPTION = "Run a deterministic JavaScript workflow with a named inline parallel-to-summary path by default";
|
|
38
|
+
export declare const WORKFLOW_TOOL_PROMPT_SNIPPET = "Run a deterministic, resumable JavaScript workflow. Prefer a named inline script that fans out independent work with parallel(...), awaits the keyed results before interpolating them into one summarizing agent(...), and returns. Inline launches require an explicit non-empty name; registered function launches reject name and use workflow as the run name. Advanced controls include registered functions, outputSchema, budgets, checkpoints, worktrees, retry/resume, CLI export, and pipelines. Use workflow_retry with an explicit failed run ID; parentRunId only reuses named worktrees. Runs are in the background by default; completion arrives as a follow-up message. Set foreground: true when the caller must wait for the final value. Foreground results include the completed run ID. Recovery map: agent(..., { retries }) reruns one agent call in the same run for transient failures; workflow_retry({ runId }) replays a failed run into a child; workflow_resume({ runId, budget? }) continues a budget_exhausted run; parentRunId on a new launch only borrows named worktrees and never replays or resumes.";
|
|
39
39
|
export declare const WORKFLOW_TOOL_PARAMETERS: Type.TObject<{
|
|
40
40
|
name: Type.TOptional<Type.TString>;
|
|
41
41
|
description: Type.TOptional<Type.TString>;
|
|
@@ -64,7 +64,6 @@ export interface WorkflowPhaseView {
|
|
|
64
64
|
name: string;
|
|
65
65
|
occurrence: number;
|
|
66
66
|
state: WorkflowPhaseState;
|
|
67
|
-
status: WorkflowPhaseState;
|
|
68
67
|
observed: boolean;
|
|
69
68
|
afterAgent?: number;
|
|
70
69
|
agents: readonly AgentRecord[];
|
|
@@ -83,7 +82,48 @@ export declare function buildWorkflowPhaseModel(run: Pick<PersistedRun, "state"
|
|
|
83
82
|
export interface WorkflowPhaseSelection {
|
|
84
83
|
phaseId?: string | undefined;
|
|
85
84
|
agentId?: string | undefined;
|
|
85
|
+
nodeId?: string | undefined;
|
|
86
|
+
expandedNodeIds?: readonly string[] | undefined;
|
|
87
|
+
treeOnly?: boolean | undefined;
|
|
88
|
+
detailsOnly?: boolean | undefined;
|
|
89
|
+
actions?: {
|
|
90
|
+
title: string;
|
|
91
|
+
options: readonly string[];
|
|
92
|
+
index: number;
|
|
93
|
+
} | undefined;
|
|
86
94
|
}
|
|
95
|
+
export type WorkflowPhaseTreeNodeKind = "phase" | "operation" | "agent";
|
|
96
|
+
export interface WorkflowPhaseTreeNode {
|
|
97
|
+
id: string;
|
|
98
|
+
kind: WorkflowPhaseTreeNodeKind;
|
|
99
|
+
label: string;
|
|
100
|
+
depth: number;
|
|
101
|
+
phaseId: string;
|
|
102
|
+
operationPath: readonly string[];
|
|
103
|
+
parentId?: string;
|
|
104
|
+
children: readonly string[];
|
|
105
|
+
state: WorkflowPhaseState | AgentRecord["state"];
|
|
106
|
+
agentId?: string;
|
|
107
|
+
agent?: AgentRecord;
|
|
108
|
+
phase?: WorkflowPhaseView;
|
|
109
|
+
}
|
|
110
|
+
export interface WorkflowPhaseTree {
|
|
111
|
+
roots: readonly string[];
|
|
112
|
+
nodes: readonly WorkflowPhaseTreeNode[];
|
|
113
|
+
byId: ReadonlyMap<string, WorkflowPhaseTreeNode>;
|
|
114
|
+
}
|
|
115
|
+
export interface WorkflowPhaseTreeSelection {
|
|
116
|
+
nodeId?: string | undefined;
|
|
117
|
+
}
|
|
118
|
+
export type WorkflowPhaseTreeDirection = "up" | "down" | "left" | "right";
|
|
119
|
+
export declare function buildWorkflowPhaseTree(model: WorkflowPhaseModel): WorkflowPhaseTree;
|
|
120
|
+
export declare function workflowPhaseTreeVisibleNodes(tree: WorkflowPhaseTree, expanded?: ReadonlySet<string>): readonly WorkflowPhaseTreeNode[];
|
|
121
|
+
export declare function workflowPhaseTreeInitialExpanded(tree: WorkflowPhaseTree): ReadonlySet<string>;
|
|
122
|
+
export declare function preserveWorkflowPhaseTreeSelection(tree: WorkflowPhaseTree, selection: WorkflowPhaseTreeSelection): WorkflowPhaseTreeSelection;
|
|
123
|
+
export declare function navigateWorkflowPhaseTree(tree: WorkflowPhaseTree, selectedNodeId: string | undefined, expandedNodeIds: ReadonlySet<string>, direction: WorkflowPhaseTreeDirection): {
|
|
124
|
+
nodeId?: string;
|
|
125
|
+
expandedNodeIds: ReadonlySet<string>;
|
|
126
|
+
};
|
|
87
127
|
export declare function preserveWorkflowPhaseSelection(model: WorkflowPhaseModel, selection: WorkflowPhaseSelection): WorkflowPhaseSelection;
|
|
88
128
|
export declare function formatWorkflowProgress(run: PersistedRun, spinner?: string, styles?: WorkflowProgressStyles): string;
|
|
89
129
|
export declare function truncateWorkflowProgress(text: string, width: number): string[];
|
|
@@ -94,7 +134,7 @@ export declare function formatNavigatorDashboard(run: PersistedRun, checkpoints:
|
|
|
94
134
|
export declare function formatNavigatorRun(loaded: {
|
|
95
135
|
run: PersistedRun;
|
|
96
136
|
snapshot: Readonly<LaunchSnapshot>;
|
|
97
|
-
}, checkpoints: readonly AwaitingCheckpoint[],
|
|
137
|
+
}, checkpoints: readonly AwaitingCheckpoint[], worktrees: readonly WorktreeReference[]): string;
|
|
98
138
|
export declare function formatWorkflowPhaseDashboard(run: PersistedRun, snapshot: Readonly<LaunchSnapshot>, width: number, selection?: WorkflowPhaseSelection, styles?: WorkflowProgressStyles): string[];
|
|
99
139
|
export declare function formatWorkflowFailureDiagnostics(diagnostic: WorkflowFailureDiagnostics): string;
|
|
100
140
|
export default function workflowExtension(pi: ExtensionAPI, home?: string, clipboard?: typeof copyToClipboard, createSession?: SessionFactory, agentDir?: string): void;
|