@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 +22 -0
- package/README.md +175 -0
- package/dist/agent.d.ts +29 -0
- package/dist/agent.js +86 -0
- package/dist/display.d.ts +54 -0
- package/dist/display.js +169 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +5 -0
- package/dist/structured-output.d.ts +19 -0
- package/dist/structured-output.js +30 -0
- package/dist/workflow-tool.d.ts +16 -0
- package/dist/workflow-tool.js +183 -0
- package/dist/workflow.d.ts +53 -0
- package/dist/workflow.js +426 -0
- package/extensions/workflow.ts +14 -0
- package/package.json +71 -0
- package/src/agent.ts +131 -0
- package/src/display.ts +235 -0
- package/src/index.ts +30 -0
- package/src/structured-output.ts +47 -0
- package/src/workflow-tool.ts +209 -0
- package/src/workflow.ts +502 -0
- package/types/workflow.d.ts +95 -0
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@baryonlabs/pi-dynamic-workflows",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Claude-Code-style dynamic workflow orchestration for Pi (Baryon fork: crash-safe script errors + guarded sandbox).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./workflow": {
|
|
14
|
+
"types": "./types/workflow.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/",
|
|
19
|
+
"extensions/",
|
|
20
|
+
"src/",
|
|
21
|
+
"types/",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "npm run check && npm run build && npm run test:unit",
|
|
26
|
+
"test:unit": "tsx --test tests/**/*.test.ts",
|
|
27
|
+
"check": "biome check .",
|
|
28
|
+
"format": "biome format --write .",
|
|
29
|
+
"lint": "biome lint .",
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"dev": "tsx src/index.ts"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"pi-package",
|
|
35
|
+
"pi",
|
|
36
|
+
"workflow",
|
|
37
|
+
"agents"
|
|
38
|
+
],
|
|
39
|
+
"pi": {
|
|
40
|
+
"extensions": [
|
|
41
|
+
"extensions/workflow.ts"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/baryonlabs/pi-dynamic-workflows.git"
|
|
47
|
+
},
|
|
48
|
+
"author": "michaelliv",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"acorn": "^8.16.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@earendil-works/pi-ai": "^0.78.0",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "^0.78.0",
|
|
56
|
+
"@earendil-works/pi-tui": "^0.78.0",
|
|
57
|
+
"typebox": "*"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@biomejs/biome": "2.4.16",
|
|
61
|
+
"@earendil-works/pi-ai": "0.78.0",
|
|
62
|
+
"@earendil-works/pi-coding-agent": "0.78.0",
|
|
63
|
+
"@earendil-works/pi-tui": "0.78.0",
|
|
64
|
+
"tsx": "latest",
|
|
65
|
+
"typebox": "latest",
|
|
66
|
+
"typescript": "latest"
|
|
67
|
+
},
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
|
|
2
|
+
import {
|
|
3
|
+
type CreateAgentSessionOptions,
|
|
4
|
+
createAgentSession,
|
|
5
|
+
createCodingTools,
|
|
6
|
+
getAgentDir,
|
|
7
|
+
SessionManager,
|
|
8
|
+
SettingsManager,
|
|
9
|
+
type ToolDefinition,
|
|
10
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { Static, TSchema } from "typebox";
|
|
12
|
+
import { createStructuredOutputTool, type StructuredOutputCapture } from "./structured-output.js";
|
|
13
|
+
|
|
14
|
+
export interface WorkflowAgentOptions {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
/** Extra tools available to the subagent in addition to the structured output tool. */
|
|
17
|
+
tools?: ToolDefinition[];
|
|
18
|
+
/** Override any createAgentSession option (model, authStorage, resourceLoader, etc.). */
|
|
19
|
+
session?: Partial<CreateAgentSessionOptions>;
|
|
20
|
+
/** Extra system guidance prepended to every subagent task. */
|
|
21
|
+
instructions?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AgentRunOptions<TSchemaDef extends TSchema | undefined = undefined> {
|
|
25
|
+
label?: string;
|
|
26
|
+
schema?: TSchemaDef;
|
|
27
|
+
tools?: ToolDefinition[];
|
|
28
|
+
instructions?: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type AgentRunResult<TSchemaDef extends TSchema | undefined> = TSchemaDef extends TSchema
|
|
33
|
+
? Static<TSchemaDef>
|
|
34
|
+
: string;
|
|
35
|
+
|
|
36
|
+
export class WorkflowAgent {
|
|
37
|
+
private readonly cwd: string;
|
|
38
|
+
private readonly baseTools: ToolDefinition[];
|
|
39
|
+
private readonly sessionOptions: Partial<CreateAgentSessionOptions>;
|
|
40
|
+
private readonly instructions?: string;
|
|
41
|
+
|
|
42
|
+
constructor(options: WorkflowAgentOptions = {}) {
|
|
43
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
44
|
+
this.baseTools = options.tools ?? createCodingTools(this.cwd);
|
|
45
|
+
this.sessionOptions = options.session ?? {};
|
|
46
|
+
this.instructions = options.instructions;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async run<TSchemaDef extends TSchema | undefined = undefined>(
|
|
50
|
+
prompt: string,
|
|
51
|
+
options: AgentRunOptions<TSchemaDef> = {},
|
|
52
|
+
): Promise<AgentRunResult<TSchemaDef>> {
|
|
53
|
+
const capture: StructuredOutputCapture<any> = { called: false, value: undefined };
|
|
54
|
+
const customTools: ToolDefinition[] = [...this.baseTools, ...(options.tools ?? [])];
|
|
55
|
+
|
|
56
|
+
if (options.schema) {
|
|
57
|
+
customTools.push(createStructuredOutputTool({ schema: options.schema, capture }) as unknown as ToolDefinition);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const agentDir = getAgentDir();
|
|
61
|
+
const { session } = await createAgentSession({
|
|
62
|
+
cwd: this.cwd,
|
|
63
|
+
agentDir,
|
|
64
|
+
sessionManager: SessionManager.inMemory(this.cwd),
|
|
65
|
+
settingsManager: SettingsManager.create(this.cwd, agentDir),
|
|
66
|
+
customTools,
|
|
67
|
+
...this.sessionOptions,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let removeAbortListener: (() => void) | undefined;
|
|
71
|
+
try {
|
|
72
|
+
if (options.signal?.aborted) throw new Error("Subagent was aborted");
|
|
73
|
+
if (options.signal) {
|
|
74
|
+
const onAbort = () => void session.abort();
|
|
75
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
76
|
+
removeAbortListener = () => options.signal?.removeEventListener("abort", onAbort);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await session.prompt(this.buildPrompt(prompt, options as AgentRunOptions<any>, Boolean(options.schema)));
|
|
80
|
+
if (options.signal?.aborted) throw new Error("Subagent was aborted");
|
|
81
|
+
|
|
82
|
+
if (options.schema) {
|
|
83
|
+
if (!capture.called) {
|
|
84
|
+
throw new Error("Subagent finished without calling structured_output");
|
|
85
|
+
}
|
|
86
|
+
return capture.value as AgentRunResult<TSchemaDef>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return this.lastAssistantText(session.messages) as AgentRunResult<TSchemaDef>;
|
|
90
|
+
} finally {
|
|
91
|
+
removeAbortListener?.();
|
|
92
|
+
session.dispose();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private buildPrompt(prompt: string, options: AgentRunOptions<any>, structured: boolean): string {
|
|
97
|
+
const parts = [
|
|
98
|
+
this.instructions,
|
|
99
|
+
options.instructions,
|
|
100
|
+
options.label ? `Task label: ${options.label}` : undefined,
|
|
101
|
+
prompt,
|
|
102
|
+
].filter(Boolean);
|
|
103
|
+
|
|
104
|
+
if (structured) {
|
|
105
|
+
parts.push(
|
|
106
|
+
[
|
|
107
|
+
"Final output contract:",
|
|
108
|
+
"- Your final action MUST be a structured_output tool call.",
|
|
109
|
+
"- The structured_output arguments are the return value of this subagent.",
|
|
110
|
+
"- Do not emit a prose final answer instead of structured_output.",
|
|
111
|
+
"- If you need to inspect files or run commands first, do so, then call structured_output exactly once.",
|
|
112
|
+
].join("\n"),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return parts.join("\n\n");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private lastAssistantText(messages: unknown[]): string {
|
|
120
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
121
|
+
const message = messages[i] as Partial<AssistantMessage> | undefined;
|
|
122
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
123
|
+
const text = message.content
|
|
124
|
+
.filter((part): part is TextContent => part.type === "text")
|
|
125
|
+
.map((part) => part.text)
|
|
126
|
+
.join("");
|
|
127
|
+
if (text.trim()) return text;
|
|
128
|
+
}
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/display.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { WorkflowMeta } from "./workflow.js";
|
|
3
|
+
|
|
4
|
+
export type WorkflowAgentStatus = "queued" | "running" | "done" | "error" | "skipped";
|
|
5
|
+
|
|
6
|
+
export interface WorkflowAgentSnapshot {
|
|
7
|
+
id: number;
|
|
8
|
+
label: string;
|
|
9
|
+
phase?: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
status: WorkflowAgentStatus;
|
|
12
|
+
resultPreview?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface WorkflowSnapshot {
|
|
17
|
+
name: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
phases: string[];
|
|
20
|
+
currentPhase?: string;
|
|
21
|
+
logs: string[];
|
|
22
|
+
agents: WorkflowAgentSnapshot[];
|
|
23
|
+
agentCount: number;
|
|
24
|
+
runningCount: number;
|
|
25
|
+
doneCount: number;
|
|
26
|
+
errorCount: number;
|
|
27
|
+
durationMs?: number;
|
|
28
|
+
result?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface WorkflowDisplay {
|
|
32
|
+
update(snapshot: WorkflowSnapshot): void;
|
|
33
|
+
complete(snapshot: WorkflowSnapshot): void;
|
|
34
|
+
clear(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WorkflowDisplayOptions {
|
|
38
|
+
key?: string;
|
|
39
|
+
placement?: "aboveEditor" | "belowEditor";
|
|
40
|
+
maxAgents?: number;
|
|
41
|
+
maxLogs?: number;
|
|
42
|
+
showStatus?: boolean;
|
|
43
|
+
showResultPreviews?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createWorkflowSnapshot(meta: WorkflowMeta): WorkflowSnapshot {
|
|
47
|
+
return {
|
|
48
|
+
name: meta.name,
|
|
49
|
+
description: meta.description,
|
|
50
|
+
phases: [],
|
|
51
|
+
logs: [],
|
|
52
|
+
agents: [],
|
|
53
|
+
agentCount: 0,
|
|
54
|
+
runningCount: 0,
|
|
55
|
+
doneCount: 0,
|
|
56
|
+
errorCount: 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function recomputeWorkflowSnapshot(snapshot: WorkflowSnapshot): WorkflowSnapshot {
|
|
61
|
+
const runningCount = snapshot.agents.filter((agent) => agent.status === "running").length;
|
|
62
|
+
const doneCount = snapshot.agents.filter((agent) => agent.status === "done").length;
|
|
63
|
+
const errorCount = snapshot.agents.filter((agent) => agent.status === "error").length;
|
|
64
|
+
return { ...snapshot, agentCount: snapshot.agents.length, runningCount, doneCount, errorCount };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createWidgetWorkflowDisplay(
|
|
68
|
+
ctx: Pick<ExtensionContext, "ui" | "hasUI">,
|
|
69
|
+
options: WorkflowDisplayOptions = {},
|
|
70
|
+
): WorkflowDisplay {
|
|
71
|
+
const key = options.key ?? "workflow";
|
|
72
|
+
const placement = options.placement ?? "belowEditor";
|
|
73
|
+
const showStatus = options.showStatus ?? false;
|
|
74
|
+
|
|
75
|
+
const render = (snapshot: WorkflowSnapshot, completed = false) => {
|
|
76
|
+
if (!ctx.hasUI) return;
|
|
77
|
+
if (showStatus) ctx.ui.setStatus(key, statusLine(snapshot, completed));
|
|
78
|
+
ctx.ui.setWidget(key, renderWorkflowLines(snapshot, options), { placement });
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
update(snapshot) {
|
|
83
|
+
render(snapshot, false);
|
|
84
|
+
},
|
|
85
|
+
complete(snapshot) {
|
|
86
|
+
render(snapshot, true);
|
|
87
|
+
},
|
|
88
|
+
clear() {
|
|
89
|
+
if (!ctx.hasUI) return;
|
|
90
|
+
if (showStatus) ctx.ui.setStatus(key, undefined);
|
|
91
|
+
ctx.ui.setWidget(key, undefined);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createToolUpdateWorkflowDisplay(
|
|
97
|
+
onUpdate: ((result: { content: Array<{ type: "text"; text: string }>; details: unknown }) => void) | undefined,
|
|
98
|
+
ctx?: Pick<ExtensionContext, "ui" | "hasUI">,
|
|
99
|
+
options: WorkflowDisplayOptions & { streamToolUpdates?: boolean } = {},
|
|
100
|
+
): WorkflowDisplay {
|
|
101
|
+
const widget = ctx ? createWidgetWorkflowDisplay(ctx, options) : undefined;
|
|
102
|
+
const streamToolUpdates = options.streamToolUpdates ?? !ctx?.hasUI;
|
|
103
|
+
|
|
104
|
+
const emit = (snapshot: WorkflowSnapshot, completed = false) => {
|
|
105
|
+
if (streamToolUpdates) {
|
|
106
|
+
onUpdate?.({
|
|
107
|
+
content: [{ type: "text", text: renderWorkflowText(snapshot, completed, options) }],
|
|
108
|
+
details: snapshot,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (completed) widget?.complete(snapshot);
|
|
112
|
+
else widget?.update(snapshot);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
update(snapshot) {
|
|
117
|
+
emit(snapshot, false);
|
|
118
|
+
},
|
|
119
|
+
complete(snapshot) {
|
|
120
|
+
emit(snapshot, true);
|
|
121
|
+
},
|
|
122
|
+
clear() {
|
|
123
|
+
widget?.clear();
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function renderWorkflowLines(snapshot: WorkflowSnapshot, options: WorkflowDisplayOptions = {}): string[] {
|
|
129
|
+
const maxAgents = options.maxAgents ?? 8;
|
|
130
|
+
const maxLogs = options.maxLogs ?? 2;
|
|
131
|
+
const showResultPreviews = options.showResultPreviews ?? false;
|
|
132
|
+
const state =
|
|
133
|
+
snapshot.errorCount > 0
|
|
134
|
+
? `, ${snapshot.errorCount} errors`
|
|
135
|
+
: snapshot.runningCount > 0
|
|
136
|
+
? `, ${snapshot.runningCount} running`
|
|
137
|
+
: "";
|
|
138
|
+
const lines = [`◆ Workflow: ${snapshot.name} (${snapshot.doneCount}/${snapshot.agentCount} done${state})`];
|
|
139
|
+
|
|
140
|
+
const agentPhaseNames = snapshot.agents
|
|
141
|
+
.map((agent) => agent.phase)
|
|
142
|
+
.filter((phase): phase is string => Boolean(phase));
|
|
143
|
+
const phaseNames = unique([
|
|
144
|
+
...snapshot.phases,
|
|
145
|
+
...(snapshot.currentPhase ? [snapshot.currentPhase] : []),
|
|
146
|
+
...agentPhaseNames,
|
|
147
|
+
]);
|
|
148
|
+
const rendered = new Set<WorkflowAgentSnapshot>();
|
|
149
|
+
|
|
150
|
+
for (const phase of phaseNames) {
|
|
151
|
+
const agents = snapshot.agents.filter((agent) => agent.phase === phase);
|
|
152
|
+
if (agents.length === 0 && snapshot.currentPhase !== phase) continue;
|
|
153
|
+
for (const agent of agents) rendered.add(agent);
|
|
154
|
+
const done = agents.filter((agent) => agent.status === "done").length;
|
|
155
|
+
const running = agents.filter((agent) => agent.status === "running").length;
|
|
156
|
+
const errors = agents.filter((agent) => agent.status === "error").length;
|
|
157
|
+
const skipped = agents.filter((agent) => agent.status === "skipped").length;
|
|
158
|
+
const complete = agents.length > 0 && done + errors + skipped === agents.length;
|
|
159
|
+
const marker = running > 0 || (!complete && snapshot.currentPhase === phase) ? "▶" : complete ? "✓" : " ";
|
|
160
|
+
lines.push(
|
|
161
|
+
` ${marker} ${phase} ${done}/${agents.length}${running ? ` · ${running} running` : ""}${errors ? ` · ${errors} errors` : ""}${skipped ? ` · ${skipped} skipped` : ""}`,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const visibleAgents = agents.slice(-maxAgents);
|
|
165
|
+
for (const agent of visibleAgents) {
|
|
166
|
+
const order = `#${agent.id}`;
|
|
167
|
+
const result = showResultPreviews && agent.resultPreview ? ` — ${agent.resultPreview}` : "";
|
|
168
|
+
lines.push(` ${order} ${statusIcon(agent.status)} ${shorten(agent.label, 48)}${result}`);
|
|
169
|
+
}
|
|
170
|
+
if (agents.length > visibleAgents.length)
|
|
171
|
+
lines.push(` … ${agents.length - visibleAgents.length} earlier agents`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const unphased = snapshot.agents.filter((agent) => !rendered.has(agent));
|
|
175
|
+
if (unphased.length) {
|
|
176
|
+
lines.push(" Unphased");
|
|
177
|
+
for (const agent of unphased.slice(-maxAgents)) {
|
|
178
|
+
const result = showResultPreviews && agent.resultPreview ? ` — ${agent.resultPreview}` : "";
|
|
179
|
+
lines.push(` #${agent.id} ${statusIcon(agent.status)} ${shorten(agent.label, 48)}${result}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const visibleLogs = snapshot.logs.slice(-maxLogs);
|
|
184
|
+
if (visibleLogs.length) {
|
|
185
|
+
if (lines.length > 1) lines.push("");
|
|
186
|
+
for (const log of visibleLogs) lines.push(` log: ${log}`);
|
|
187
|
+
}
|
|
188
|
+
return lines;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function renderWorkflowText(
|
|
192
|
+
snapshot: WorkflowSnapshot,
|
|
193
|
+
completed = false,
|
|
194
|
+
options: WorkflowDisplayOptions = {},
|
|
195
|
+
): string {
|
|
196
|
+
const header = completed ? "Workflow completed" : "Workflow running";
|
|
197
|
+
return [header, ...renderWorkflowLines(snapshot, options)].join("\n");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function statusLine(snapshot: WorkflowSnapshot, completed: boolean): string {
|
|
201
|
+
if (completed) return `workflow ✓ ${snapshot.name}: ${snapshot.doneCount}/${snapshot.agentCount}`;
|
|
202
|
+
if (snapshot.runningCount > 0)
|
|
203
|
+
return `workflow ${snapshot.name}: ${snapshot.runningCount} running, ${snapshot.doneCount}/${snapshot.agentCount} done`;
|
|
204
|
+
return `workflow ${snapshot.name}: ${snapshot.doneCount}/${snapshot.agentCount} done`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function statusIcon(status: WorkflowAgentStatus): string {
|
|
208
|
+
switch (status) {
|
|
209
|
+
case "queued":
|
|
210
|
+
return "○";
|
|
211
|
+
case "running":
|
|
212
|
+
return "●";
|
|
213
|
+
case "done":
|
|
214
|
+
return "✓";
|
|
215
|
+
case "error":
|
|
216
|
+
return "✗";
|
|
217
|
+
case "skipped":
|
|
218
|
+
return "-";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function unique(values: string[]): string[] {
|
|
223
|
+
return [...new Set(values)];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function shorten(value: string, max: number): string {
|
|
227
|
+
const text = value.replace(/\s+/g, " ").trim();
|
|
228
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function preview(value: unknown, max = 80): string {
|
|
232
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
233
|
+
if (!text) return "";
|
|
234
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
235
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type { AgentRunOptions, AgentRunResult, WorkflowAgentOptions } from "./agent.js";
|
|
2
|
+
export { WorkflowAgent } from "./agent.js";
|
|
3
|
+
export type {
|
|
4
|
+
WorkflowAgentSnapshot,
|
|
5
|
+
WorkflowAgentStatus,
|
|
6
|
+
WorkflowDisplay,
|
|
7
|
+
WorkflowDisplayOptions,
|
|
8
|
+
WorkflowSnapshot,
|
|
9
|
+
} from "./display.js";
|
|
10
|
+
export {
|
|
11
|
+
createToolUpdateWorkflowDisplay,
|
|
12
|
+
createWidgetWorkflowDisplay,
|
|
13
|
+
createWorkflowSnapshot,
|
|
14
|
+
preview,
|
|
15
|
+
recomputeWorkflowSnapshot,
|
|
16
|
+
renderWorkflowLines,
|
|
17
|
+
renderWorkflowText,
|
|
18
|
+
} from "./display.js";
|
|
19
|
+
export type { StructuredOutputCapture, StructuredOutputToolOptions } from "./structured-output.js";
|
|
20
|
+
export { createStructuredOutputTool } from "./structured-output.js";
|
|
21
|
+
export type {
|
|
22
|
+
AgentOptions,
|
|
23
|
+
WorkflowMeta,
|
|
24
|
+
WorkflowMetaPhase,
|
|
25
|
+
WorkflowRunOptions,
|
|
26
|
+
WorkflowRunResult,
|
|
27
|
+
} from "./workflow.js";
|
|
28
|
+
export { parseWorkflowScript, runWorkflow } from "./workflow.js";
|
|
29
|
+
export type { WorkflowToolInput, WorkflowToolOptions } from "./workflow-tool.js";
|
|
30
|
+
export { createWorkflowTool } from "./workflow-tool.js";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineTool, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Static, TSchema } from "typebox";
|
|
3
|
+
|
|
4
|
+
export interface StructuredOutputCapture<T = unknown> {
|
|
5
|
+
value: T | undefined;
|
|
6
|
+
called: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface StructuredOutputToolOptions<TSchemaDef extends TSchema> {
|
|
10
|
+
schema: TSchemaDef;
|
|
11
|
+
capture: StructuredOutputCapture<Static<TSchemaDef>>;
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a terminating tool that captures validated params as the subagent result.
|
|
17
|
+
*
|
|
18
|
+
* Pi validates `params` against `schema` before execute() is called. Returning
|
|
19
|
+
* `terminate: true` lets the subagent finish on this tool call without paying for
|
|
20
|
+
* an extra assistant follow-up turn.
|
|
21
|
+
*/
|
|
22
|
+
export function createStructuredOutputTool<TSchemaDef extends TSchema>({
|
|
23
|
+
schema,
|
|
24
|
+
capture,
|
|
25
|
+
name = "structured_output",
|
|
26
|
+
}: StructuredOutputToolOptions<TSchemaDef>): ToolDefinition<TSchemaDef, Static<TSchemaDef>> {
|
|
27
|
+
return defineTool({
|
|
28
|
+
name,
|
|
29
|
+
label: "Structured Output",
|
|
30
|
+
description: "Return the final machine-readable result for this subagent task.",
|
|
31
|
+
promptSnippet: "Return final machine-readable output",
|
|
32
|
+
promptGuidelines: [
|
|
33
|
+
`${name} is the final answer channel for this task; call ${name} exactly once when done.`,
|
|
34
|
+
`Do not write a prose final answer after calling ${name}.`,
|
|
35
|
+
],
|
|
36
|
+
parameters: schema,
|
|
37
|
+
async execute(_toolCallId, params) {
|
|
38
|
+
capture.value = params;
|
|
39
|
+
capture.called = true;
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: "Structured output received." }],
|
|
42
|
+
details: params,
|
|
43
|
+
terminate: true,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|