@baryonlabs/pi-dynamic-workflows 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Livshits (original work)
4
+ Copyright (c) 2026 Baryon Labs (this fork)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # @baryonlabs/pi-dynamic-workflows
2
+
3
+ > Claude-Code-style dynamic workflows for [Pi](https://github.com/earendil-works/pi).
4
+
5
+ **Baryon fork** of [Michaelliv/pi-dynamic-workflows](https://github.com/Michaelliv/pi-dynamic-workflows) (MIT). This fork fixes a crash: a workflow script that calls a non-existent host API (`bash`, `fetch`, `require`, ...) inside an un-awaited async function used to escape as an unhandled promise rejection and kill the whole `pi` process, not just the workflow call. See [CHANGELOG.md](./CHANGELOG.md).
6
+
7
+ A Pi extension that adds a `workflow` tool. Instead of one assistant doing everything sequentially, the model writes a small JavaScript script that fans out the work across many isolated subagents, then synthesizes the results.
8
+
9
+ Great for codebase audits, multi-perspective review, large refactors, and fan-out research.
10
+
11
+ Inspired by Anthropic's [dynamic workflows in Claude Code](https://claude.com/blog/introducing-dynamic-workflows-in-claude-code).
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pi install npm:pi-dynamic-workflows
17
+ # or from a local checkout
18
+ pi install /path/to/pi-dynamic-workflows
19
+ ```
20
+
21
+ Then in Pi:
22
+
23
+ ```text
24
+ /reload
25
+ ```
26
+
27
+ That's it. The extension registers a `workflow` tool and activates it on session start.
28
+
29
+ ## Usage
30
+
31
+ Just ask Pi for a workflow in plain language:
32
+
33
+ ```text
34
+ Run a workflow to inspect this repository and summarize the main modules.
35
+ ```
36
+
37
+ The model will write a workflow script and call the `workflow` tool. Live progress shows up inline:
38
+
39
+ ```text
40
+ ◆ Workflow: inspect_project (3/3 done)
41
+ ✓ Scan 1/1
42
+ #1 ✓ repo inventory
43
+ ✓ Analyze 2/2
44
+ #2 ✓ source modules
45
+ #3 ✓ final summary
46
+ ```
47
+
48
+ Press `Esc` to cancel a running workflow. Active subagents are aborted and surfaced as skipped.
49
+
50
+ ## Workflow script shape
51
+
52
+ A workflow is plain JavaScript. The first statement must export literal metadata. `name` and `description` are required; `phases` is optional documentation for an expected outline. The live progress view is driven by `phase(...)` calls at runtime:
53
+
54
+ ```js
55
+ export const meta = {
56
+ name: 'inspect_project',
57
+ description: 'Inspect a repository and summarize the main modules',
58
+ phases: [
59
+ { title: 'Scan' },
60
+ { title: 'Analyze' },
61
+ ],
62
+ }
63
+
64
+ phase('Scan')
65
+ const inventory = await agent('Inspect the repository structure.', {
66
+ label: 'repo inventory',
67
+ })
68
+
69
+ phase('Analyze')
70
+ const summary = await agent(
71
+ 'Summarize the main modules from this inventory:\n' + inventory,
72
+ { label: 'module summary' },
73
+ )
74
+
75
+ return { inventory, summary }
76
+ ```
77
+
78
+ Phases are discovered as the script runs, so conditional and loop-created phases work naturally. If a branch is skipped, its phase does not show up as an empty progress row.
79
+
80
+ ### Editor IntelliSense
81
+
82
+ Reusable workflow files can opt into editor hints for workflow globals:
83
+
84
+ ```js
85
+ /// <reference types="pi-dynamic-workflows/workflow" />
86
+ ```
87
+
88
+ This declares `agent`, `parallel`, `pipeline`, `phase`, `log`, `args`, `cwd`, and `budget` for TypeScript-aware editors.
89
+
90
+ ### Available globals
91
+
92
+ | Global | Description |
93
+ | --- | --- |
94
+ | `agent(prompt, opts)` | Spawn an isolated subagent. Returns its final text or, with `opts.schema`, a validated object. |
95
+ | `parallel(thunks)` | Run an array of `() => agent(...)` thunks concurrently. Results are returned in input order. |
96
+ | `pipeline(items, ...stages)` | Run each item through sequential stages while items fan out. Each stage receives `(prev, original, index)`. |
97
+ | `phase(title)` | Mark the current phase. Used for grouping in the live progress view. |
98
+ | `log(message)` | Append a workflow-level log line. |
99
+ | `args` | Optional JSON value passed in via the tool's `args` parameter. |
100
+ | `cwd`, `process.cwd()` | Current working directory for subagents. |
101
+ | `budget` | `{ total, spent(), remaining() }` token budget tracker. |
102
+
103
+ ### Determinism rules
104
+
105
+ Workflow scripts are evaluated inside a Node `vm` sandbox. The following are intentionally unavailable:
106
+
107
+ - `Date.now()`, `new Date()`
108
+ - `Math.random()`
109
+ - `require`, `import`, `fs`, network APIs
110
+ - spreads, computed keys, template interpolation, function calls inside `meta`
111
+
112
+ This keeps `meta` parseable, runs reproducible, and the surface area small.
113
+
114
+ ### Structured subagent output
115
+
116
+ Pass a JSON Schema via `opts.schema` and the subagent will return a validated object:
117
+
118
+ ```js
119
+ const finding = await agent('Find security-sensitive files.', {
120
+ label: 'security scan',
121
+ schema: {
122
+ type: 'object',
123
+ properties: {
124
+ paths: { type: 'array', items: { type: 'string' } },
125
+ reason: { type: 'string' },
126
+ },
127
+ required: ['paths', 'reason'],
128
+ },
129
+ })
130
+ ```
131
+
132
+ Under the hood this is a Pi `structured_output` tool with `terminate: true`, so the subagent ends on that call without an extra assistant turn.
133
+
134
+ ## How it works
135
+
136
+ ```text
137
+ user prompt
138
+ → Pi model writes a workflow script
139
+ → workflow tool parses + runs script in a vm sandbox
140
+ → script calls agent(), parallel(), pipeline()
141
+ → each agent() spawns an in-memory Pi subagent session
142
+ → snapshots stream back as compact progress
143
+ → final structured result returned to the parent assistant
144
+ ```
145
+
146
+ Subagents run in fresh in-memory Pi sessions with the standard coding tools, so they can read files, run shell commands, and call structured output exactly like a normal Pi turn.
147
+
148
+ ## Library modules
149
+
150
+ | File | Purpose |
151
+ | --- | --- |
152
+ | `src/workflow.ts` | AST-validated parser and sandboxed workflow runtime. |
153
+ | `src/workflow-tool.ts` | The Pi `workflow` tool, prompt guidelines, rendering, abort handling. |
154
+ | `src/agent.ts` | `WorkflowAgent`, an in-memory Pi subagent runner. |
155
+ | `src/structured-output.ts` | Terminating structured-output tool backed by TypeBox/JSON Schema. |
156
+ | `src/display.ts` | Workflow snapshots and compact text renderers. |
157
+ | `extensions/workflow.ts` | The Pi extension entrypoint. |
158
+
159
+ ## Development
160
+
161
+ ```bash
162
+ npm install
163
+ npm test # biome check + tsc + unit tests
164
+ npm run dev
165
+ ```
166
+
167
+ Parser unit tests live in `tests/workflow-parser.test.ts` and cover both accepted and rejected script shapes.
168
+
169
+ ## Status
170
+
171
+ This is a prototype. It implements the core workflow primitive (script, subagents, parallel/pipeline, phases, abort, structured output) but does not yet implement persisted or resumable runs, or a `/workflows` manager.
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,29 @@
1
+ import { type CreateAgentSessionOptions, type ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import type { Static, TSchema } from "typebox";
3
+ export interface WorkflowAgentOptions {
4
+ cwd?: string;
5
+ /** Extra tools available to the subagent in addition to the structured output tool. */
6
+ tools?: ToolDefinition[];
7
+ /** Override any createAgentSession option (model, authStorage, resourceLoader, etc.). */
8
+ session?: Partial<CreateAgentSessionOptions>;
9
+ /** Extra system guidance prepended to every subagent task. */
10
+ instructions?: string;
11
+ }
12
+ export interface AgentRunOptions<TSchemaDef extends TSchema | undefined = undefined> {
13
+ label?: string;
14
+ schema?: TSchemaDef;
15
+ tools?: ToolDefinition[];
16
+ instructions?: string;
17
+ signal?: AbortSignal;
18
+ }
19
+ export type AgentRunResult<TSchemaDef extends TSchema | undefined> = TSchemaDef extends TSchema ? Static<TSchemaDef> : string;
20
+ export declare class WorkflowAgent {
21
+ private readonly cwd;
22
+ private readonly baseTools;
23
+ private readonly sessionOptions;
24
+ private readonly instructions?;
25
+ constructor(options?: WorkflowAgentOptions);
26
+ run<TSchemaDef extends TSchema | undefined = undefined>(prompt: string, options?: AgentRunOptions<TSchemaDef>): Promise<AgentRunResult<TSchemaDef>>;
27
+ private buildPrompt;
28
+ private lastAssistantText;
29
+ }
package/dist/agent.js ADDED
@@ -0,0 +1,86 @@
1
+ import { createAgentSession, createCodingTools, getAgentDir, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
2
+ import { createStructuredOutputTool } from "./structured-output.js";
3
+ export class WorkflowAgent {
4
+ cwd;
5
+ baseTools;
6
+ sessionOptions;
7
+ instructions;
8
+ constructor(options = {}) {
9
+ this.cwd = options.cwd ?? process.cwd();
10
+ this.baseTools = options.tools ?? createCodingTools(this.cwd);
11
+ this.sessionOptions = options.session ?? {};
12
+ this.instructions = options.instructions;
13
+ }
14
+ async run(prompt, options = {}) {
15
+ const capture = { called: false, value: undefined };
16
+ const customTools = [...this.baseTools, ...(options.tools ?? [])];
17
+ if (options.schema) {
18
+ customTools.push(createStructuredOutputTool({ schema: options.schema, capture }));
19
+ }
20
+ const agentDir = getAgentDir();
21
+ const { session } = await createAgentSession({
22
+ cwd: this.cwd,
23
+ agentDir,
24
+ sessionManager: SessionManager.inMemory(this.cwd),
25
+ settingsManager: SettingsManager.create(this.cwd, agentDir),
26
+ customTools,
27
+ ...this.sessionOptions,
28
+ });
29
+ let removeAbortListener;
30
+ try {
31
+ if (options.signal?.aborted)
32
+ throw new Error("Subagent was aborted");
33
+ if (options.signal) {
34
+ const onAbort = () => void session.abort();
35
+ options.signal.addEventListener("abort", onAbort, { once: true });
36
+ removeAbortListener = () => options.signal?.removeEventListener("abort", onAbort);
37
+ }
38
+ await session.prompt(this.buildPrompt(prompt, options, Boolean(options.schema)));
39
+ if (options.signal?.aborted)
40
+ throw new Error("Subagent was aborted");
41
+ if (options.schema) {
42
+ if (!capture.called) {
43
+ throw new Error("Subagent finished without calling structured_output");
44
+ }
45
+ return capture.value;
46
+ }
47
+ return this.lastAssistantText(session.messages);
48
+ }
49
+ finally {
50
+ removeAbortListener?.();
51
+ session.dispose();
52
+ }
53
+ }
54
+ buildPrompt(prompt, options, structured) {
55
+ const parts = [
56
+ this.instructions,
57
+ options.instructions,
58
+ options.label ? `Task label: ${options.label}` : undefined,
59
+ prompt,
60
+ ].filter(Boolean);
61
+ if (structured) {
62
+ parts.push([
63
+ "Final output contract:",
64
+ "- Your final action MUST be a structured_output tool call.",
65
+ "- The structured_output arguments are the return value of this subagent.",
66
+ "- Do not emit a prose final answer instead of structured_output.",
67
+ "- If you need to inspect files or run commands first, do so, then call structured_output exactly once.",
68
+ ].join("\n"));
69
+ }
70
+ return parts.join("\n\n");
71
+ }
72
+ lastAssistantText(messages) {
73
+ for (let i = messages.length - 1; i >= 0; i--) {
74
+ const message = messages[i];
75
+ if (message?.role !== "assistant" || !Array.isArray(message.content))
76
+ continue;
77
+ const text = message.content
78
+ .filter((part) => part.type === "text")
79
+ .map((part) => part.text)
80
+ .join("");
81
+ if (text.trim())
82
+ return text;
83
+ }
84
+ return "";
85
+ }
86
+ }
@@ -0,0 +1,54 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { WorkflowMeta } from "./workflow.js";
3
+ export type WorkflowAgentStatus = "queued" | "running" | "done" | "error" | "skipped";
4
+ export interface WorkflowAgentSnapshot {
5
+ id: number;
6
+ label: string;
7
+ phase?: string;
8
+ prompt: string;
9
+ status: WorkflowAgentStatus;
10
+ resultPreview?: string;
11
+ error?: string;
12
+ }
13
+ export interface WorkflowSnapshot {
14
+ name: string;
15
+ description?: string;
16
+ phases: string[];
17
+ currentPhase?: string;
18
+ logs: string[];
19
+ agents: WorkflowAgentSnapshot[];
20
+ agentCount: number;
21
+ runningCount: number;
22
+ doneCount: number;
23
+ errorCount: number;
24
+ durationMs?: number;
25
+ result?: unknown;
26
+ }
27
+ export interface WorkflowDisplay {
28
+ update(snapshot: WorkflowSnapshot): void;
29
+ complete(snapshot: WorkflowSnapshot): void;
30
+ clear(): void;
31
+ }
32
+ export interface WorkflowDisplayOptions {
33
+ key?: string;
34
+ placement?: "aboveEditor" | "belowEditor";
35
+ maxAgents?: number;
36
+ maxLogs?: number;
37
+ showStatus?: boolean;
38
+ showResultPreviews?: boolean;
39
+ }
40
+ export declare function createWorkflowSnapshot(meta: WorkflowMeta): WorkflowSnapshot;
41
+ export declare function recomputeWorkflowSnapshot(snapshot: WorkflowSnapshot): WorkflowSnapshot;
42
+ export declare function createWidgetWorkflowDisplay(ctx: Pick<ExtensionContext, "ui" | "hasUI">, options?: WorkflowDisplayOptions): WorkflowDisplay;
43
+ export declare function createToolUpdateWorkflowDisplay(onUpdate: ((result: {
44
+ content: Array<{
45
+ type: "text";
46
+ text: string;
47
+ }>;
48
+ details: unknown;
49
+ }) => void) | undefined, ctx?: Pick<ExtensionContext, "ui" | "hasUI">, options?: WorkflowDisplayOptions & {
50
+ streamToolUpdates?: boolean;
51
+ }): WorkflowDisplay;
52
+ export declare function renderWorkflowLines(snapshot: WorkflowSnapshot, options?: WorkflowDisplayOptions): string[];
53
+ export declare function renderWorkflowText(snapshot: WorkflowSnapshot, completed?: boolean, options?: WorkflowDisplayOptions): string;
54
+ export declare function preview(value: unknown, max?: number): string;
@@ -0,0 +1,169 @@
1
+ export function createWorkflowSnapshot(meta) {
2
+ return {
3
+ name: meta.name,
4
+ description: meta.description,
5
+ phases: [],
6
+ logs: [],
7
+ agents: [],
8
+ agentCount: 0,
9
+ runningCount: 0,
10
+ doneCount: 0,
11
+ errorCount: 0,
12
+ };
13
+ }
14
+ export function recomputeWorkflowSnapshot(snapshot) {
15
+ const runningCount = snapshot.agents.filter((agent) => agent.status === "running").length;
16
+ const doneCount = snapshot.agents.filter((agent) => agent.status === "done").length;
17
+ const errorCount = snapshot.agents.filter((agent) => agent.status === "error").length;
18
+ return { ...snapshot, agentCount: snapshot.agents.length, runningCount, doneCount, errorCount };
19
+ }
20
+ export function createWidgetWorkflowDisplay(ctx, options = {}) {
21
+ const key = options.key ?? "workflow";
22
+ const placement = options.placement ?? "belowEditor";
23
+ const showStatus = options.showStatus ?? false;
24
+ const render = (snapshot, completed = false) => {
25
+ if (!ctx.hasUI)
26
+ return;
27
+ if (showStatus)
28
+ ctx.ui.setStatus(key, statusLine(snapshot, completed));
29
+ ctx.ui.setWidget(key, renderWorkflowLines(snapshot, options), { placement });
30
+ };
31
+ return {
32
+ update(snapshot) {
33
+ render(snapshot, false);
34
+ },
35
+ complete(snapshot) {
36
+ render(snapshot, true);
37
+ },
38
+ clear() {
39
+ if (!ctx.hasUI)
40
+ return;
41
+ if (showStatus)
42
+ ctx.ui.setStatus(key, undefined);
43
+ ctx.ui.setWidget(key, undefined);
44
+ },
45
+ };
46
+ }
47
+ export function createToolUpdateWorkflowDisplay(onUpdate, ctx, options = {}) {
48
+ const widget = ctx ? createWidgetWorkflowDisplay(ctx, options) : undefined;
49
+ const streamToolUpdates = options.streamToolUpdates ?? !ctx?.hasUI;
50
+ const emit = (snapshot, completed = false) => {
51
+ if (streamToolUpdates) {
52
+ onUpdate?.({
53
+ content: [{ type: "text", text: renderWorkflowText(snapshot, completed, options) }],
54
+ details: snapshot,
55
+ });
56
+ }
57
+ if (completed)
58
+ widget?.complete(snapshot);
59
+ else
60
+ widget?.update(snapshot);
61
+ };
62
+ return {
63
+ update(snapshot) {
64
+ emit(snapshot, false);
65
+ },
66
+ complete(snapshot) {
67
+ emit(snapshot, true);
68
+ },
69
+ clear() {
70
+ widget?.clear();
71
+ },
72
+ };
73
+ }
74
+ export function renderWorkflowLines(snapshot, options = {}) {
75
+ const maxAgents = options.maxAgents ?? 8;
76
+ const maxLogs = options.maxLogs ?? 2;
77
+ const showResultPreviews = options.showResultPreviews ?? false;
78
+ const state = snapshot.errorCount > 0
79
+ ? `, ${snapshot.errorCount} errors`
80
+ : snapshot.runningCount > 0
81
+ ? `, ${snapshot.runningCount} running`
82
+ : "";
83
+ const lines = [`◆ Workflow: ${snapshot.name} (${snapshot.doneCount}/${snapshot.agentCount} done${state})`];
84
+ const agentPhaseNames = snapshot.agents
85
+ .map((agent) => agent.phase)
86
+ .filter((phase) => Boolean(phase));
87
+ const phaseNames = unique([
88
+ ...snapshot.phases,
89
+ ...(snapshot.currentPhase ? [snapshot.currentPhase] : []),
90
+ ...agentPhaseNames,
91
+ ]);
92
+ const rendered = new Set();
93
+ for (const phase of phaseNames) {
94
+ const agents = snapshot.agents.filter((agent) => agent.phase === phase);
95
+ if (agents.length === 0 && snapshot.currentPhase !== phase)
96
+ continue;
97
+ for (const agent of agents)
98
+ rendered.add(agent);
99
+ const done = agents.filter((agent) => agent.status === "done").length;
100
+ const running = agents.filter((agent) => agent.status === "running").length;
101
+ const errors = agents.filter((agent) => agent.status === "error").length;
102
+ const skipped = agents.filter((agent) => agent.status === "skipped").length;
103
+ const complete = agents.length > 0 && done + errors + skipped === agents.length;
104
+ const marker = running > 0 || (!complete && snapshot.currentPhase === phase) ? "▶" : complete ? "✓" : " ";
105
+ lines.push(` ${marker} ${phase} ${done}/${agents.length}${running ? ` · ${running} running` : ""}${errors ? ` · ${errors} errors` : ""}${skipped ? ` · ${skipped} skipped` : ""}`);
106
+ const visibleAgents = agents.slice(-maxAgents);
107
+ for (const agent of visibleAgents) {
108
+ const order = `#${agent.id}`;
109
+ const result = showResultPreviews && agent.resultPreview ? ` — ${agent.resultPreview}` : "";
110
+ lines.push(` ${order} ${statusIcon(agent.status)} ${shorten(agent.label, 48)}${result}`);
111
+ }
112
+ if (agents.length > visibleAgents.length)
113
+ lines.push(` … ${agents.length - visibleAgents.length} earlier agents`);
114
+ }
115
+ const unphased = snapshot.agents.filter((agent) => !rendered.has(agent));
116
+ if (unphased.length) {
117
+ lines.push(" Unphased");
118
+ for (const agent of unphased.slice(-maxAgents)) {
119
+ const result = showResultPreviews && agent.resultPreview ? ` — ${agent.resultPreview}` : "";
120
+ lines.push(` #${agent.id} ${statusIcon(agent.status)} ${shorten(agent.label, 48)}${result}`);
121
+ }
122
+ }
123
+ const visibleLogs = snapshot.logs.slice(-maxLogs);
124
+ if (visibleLogs.length) {
125
+ if (lines.length > 1)
126
+ lines.push("");
127
+ for (const log of visibleLogs)
128
+ lines.push(` log: ${log}`);
129
+ }
130
+ return lines;
131
+ }
132
+ export function renderWorkflowText(snapshot, completed = false, options = {}) {
133
+ const header = completed ? "Workflow completed" : "Workflow running";
134
+ return [header, ...renderWorkflowLines(snapshot, options)].join("\n");
135
+ }
136
+ function statusLine(snapshot, completed) {
137
+ if (completed)
138
+ return `workflow ✓ ${snapshot.name}: ${snapshot.doneCount}/${snapshot.agentCount}`;
139
+ if (snapshot.runningCount > 0)
140
+ return `workflow ${snapshot.name}: ${snapshot.runningCount} running, ${snapshot.doneCount}/${snapshot.agentCount} done`;
141
+ return `workflow ${snapshot.name}: ${snapshot.doneCount}/${snapshot.agentCount} done`;
142
+ }
143
+ function statusIcon(status) {
144
+ switch (status) {
145
+ case "queued":
146
+ return "○";
147
+ case "running":
148
+ return "●";
149
+ case "done":
150
+ return "✓";
151
+ case "error":
152
+ return "✗";
153
+ case "skipped":
154
+ return "-";
155
+ }
156
+ }
157
+ function unique(values) {
158
+ return [...new Set(values)];
159
+ }
160
+ function shorten(value, max) {
161
+ const text = value.replace(/\s+/g, " ").trim();
162
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
163
+ }
164
+ export function preview(value, max = 80) {
165
+ const text = typeof value === "string" ? value : JSON.stringify(value);
166
+ if (!text)
167
+ return "";
168
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
169
+ }
@@ -0,0 +1,10 @@
1
+ export type { AgentRunOptions, AgentRunResult, WorkflowAgentOptions } from "./agent.js";
2
+ export { WorkflowAgent } from "./agent.js";
3
+ export type { WorkflowAgentSnapshot, WorkflowAgentStatus, WorkflowDisplay, WorkflowDisplayOptions, WorkflowSnapshot, } from "./display.js";
4
+ export { createToolUpdateWorkflowDisplay, createWidgetWorkflowDisplay, createWorkflowSnapshot, preview, recomputeWorkflowSnapshot, renderWorkflowLines, renderWorkflowText, } from "./display.js";
5
+ export type { StructuredOutputCapture, StructuredOutputToolOptions } from "./structured-output.js";
6
+ export { createStructuredOutputTool } from "./structured-output.js";
7
+ export type { AgentOptions, WorkflowMeta, WorkflowMetaPhase, WorkflowRunOptions, WorkflowRunResult, } from "./workflow.js";
8
+ export { parseWorkflowScript, runWorkflow } from "./workflow.js";
9
+ export type { WorkflowToolInput, WorkflowToolOptions } from "./workflow-tool.js";
10
+ export { createWorkflowTool } from "./workflow-tool.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { WorkflowAgent } from "./agent.js";
2
+ export { createToolUpdateWorkflowDisplay, createWidgetWorkflowDisplay, createWorkflowSnapshot, preview, recomputeWorkflowSnapshot, renderWorkflowLines, renderWorkflowText, } from "./display.js";
3
+ export { createStructuredOutputTool } from "./structured-output.js";
4
+ export { parseWorkflowScript, runWorkflow } from "./workflow.js";
5
+ export { createWorkflowTool } from "./workflow-tool.js";
@@ -0,0 +1,19 @@
1
+ import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import type { Static, TSchema } from "typebox";
3
+ export interface StructuredOutputCapture<T = unknown> {
4
+ value: T | undefined;
5
+ called: boolean;
6
+ }
7
+ export interface StructuredOutputToolOptions<TSchemaDef extends TSchema> {
8
+ schema: TSchemaDef;
9
+ capture: StructuredOutputCapture<Static<TSchemaDef>>;
10
+ name?: string;
11
+ }
12
+ /**
13
+ * Create a terminating tool that captures validated params as the subagent result.
14
+ *
15
+ * Pi validates `params` against `schema` before execute() is called. Returning
16
+ * `terminate: true` lets the subagent finish on this tool call without paying for
17
+ * an extra assistant follow-up turn.
18
+ */
19
+ export declare function createStructuredOutputTool<TSchemaDef extends TSchema>({ schema, capture, name, }: StructuredOutputToolOptions<TSchemaDef>): ToolDefinition<TSchemaDef, Static<TSchemaDef>>;
@@ -0,0 +1,30 @@
1
+ import { defineTool } from "@earendil-works/pi-coding-agent";
2
+ /**
3
+ * Create a terminating tool that captures validated params as the subagent result.
4
+ *
5
+ * Pi validates `params` against `schema` before execute() is called. Returning
6
+ * `terminate: true` lets the subagent finish on this tool call without paying for
7
+ * an extra assistant follow-up turn.
8
+ */
9
+ export function createStructuredOutputTool({ schema, capture, name = "structured_output", }) {
10
+ return defineTool({
11
+ name,
12
+ label: "Structured Output",
13
+ description: "Return the final machine-readable result for this subagent task.",
14
+ promptSnippet: "Return final machine-readable output",
15
+ promptGuidelines: [
16
+ `${name} is the final answer channel for this task; call ${name} exactly once when done.`,
17
+ `Do not write a prose final answer after calling ${name}.`,
18
+ ],
19
+ parameters: schema,
20
+ async execute(_toolCallId, params) {
21
+ capture.value = params;
22
+ capture.called = true;
23
+ return {
24
+ content: [{ type: "text", text: "Structured output received." }],
25
+ details: params,
26
+ terminate: true,
27
+ };
28
+ },
29
+ });
30
+ }
@@ -0,0 +1,16 @@
1
+ import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import { Type } from "typebox";
3
+ declare const workflowToolSchema: Type.TObject<{
4
+ script: Type.TString;
5
+ args: Type.TOptional<Type.TAny>;
6
+ }>;
7
+ export type WorkflowToolInput = {
8
+ script: string;
9
+ args?: unknown;
10
+ };
11
+ export interface WorkflowToolOptions {
12
+ cwd?: string;
13
+ concurrency?: number;
14
+ }
15
+ export declare function createWorkflowTool(options?: WorkflowToolOptions): ToolDefinition<typeof workflowToolSchema, any>;
16
+ export {};