@davidorex/pi-workflows 0.2.0 → 0.3.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/CHANGELOG.md +7 -4
- package/README.md +12 -2
- package/agents/architecture-designer.agent.yaml +58 -0
- package/agents/architecture-inferrer.agent.yaml +60 -0
- package/agents/gap-identifier.agent.yaml +66 -0
- package/agents/handoff-writer.agent.yaml +64 -0
- package/agents/plan-creator.agent.yaml +63 -0
- package/agents/project-definer.agent.yaml +53 -0
- package/agents/project-inferrer.agent.yaml +63 -0
- package/agents/requirements-gatherer.agent.yaml +58 -0
- package/dist/agent-spec.d.ts +27 -0
- package/dist/agent-spec.d.ts.map +1 -0
- package/dist/agent-spec.js +112 -0
- package/dist/agent-spec.js.map +1 -0
- package/dist/checkpoint.d.ts +50 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +109 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/completion.d.ts +18 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +58 -0
- package/dist/completion.js.map +1 -0
- package/dist/dag.d.ts +56 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +178 -0
- package/dist/dag.js.map +1 -0
- package/dist/dispatch.d.ts +35 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/expression.d.ts +64 -0
- package/dist/expression.d.ts.map +1 -0
- package/dist/expression.js +294 -0
- package/dist/expression.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +42 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +59 -0
- package/dist/output.js.map +1 -0
- package/dist/state.d.ts +66 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +227 -0
- package/dist/state.js.map +1 -0
- package/dist/step-agent.d.ts +35 -0
- package/dist/step-agent.d.ts.map +1 -0
- package/dist/step-agent.js +171 -0
- package/dist/step-agent.js.map +1 -0
- package/dist/step-command.d.ts +21 -0
- package/dist/step-command.d.ts.map +1 -0
- package/dist/step-command.js +150 -0
- package/dist/step-command.js.map +1 -0
- package/dist/step-foreach.d.ts +33 -0
- package/dist/step-foreach.d.ts.map +1 -0
- package/dist/step-foreach.js +112 -0
- package/dist/step-foreach.js.map +1 -0
- package/dist/step-gate.d.ts +18 -0
- package/dist/step-gate.d.ts.map +1 -0
- package/dist/step-gate.js +137 -0
- package/dist/step-gate.js.map +1 -0
- package/dist/step-loop.d.ts +32 -0
- package/dist/step-loop.d.ts.map +1 -0
- package/dist/step-loop.js +278 -0
- package/dist/step-loop.js.map +1 -0
- package/dist/step-monitor.d.ts +56 -0
- package/dist/step-monitor.d.ts.map +1 -0
- package/dist/step-monitor.js +317 -0
- package/dist/step-monitor.js.map +1 -0
- package/dist/step-parallel.d.ts +43 -0
- package/dist/step-parallel.d.ts.map +1 -0
- package/dist/step-parallel.js +122 -0
- package/dist/step-parallel.js.map +1 -0
- package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
- package/dist/step-pause.d.ts.map +1 -0
- package/dist/step-pause.js +22 -0
- package/dist/step-pause.js.map +1 -0
- package/dist/step-shared.d.ts +63 -0
- package/dist/step-shared.d.ts.map +1 -0
- package/dist/step-shared.js +136 -0
- package/dist/step-shared.js.map +1 -0
- package/dist/step-transform.d.ts +11 -0
- package/dist/step-transform.d.ts.map +1 -0
- package/dist/step-transform.js +41 -0
- package/dist/step-transform.js.map +1 -0
- package/dist/template.d.ts +51 -0
- package/dist/template.d.ts.map +1 -0
- package/{src/template.ts → dist/template.js} +32 -38
- package/dist/template.js.map +1 -0
- package/dist/test-helpers.d.ts +17 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +46 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/tui.d.ts +47 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +143 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-discovery.d.ts +23 -0
- package/dist/workflow-discovery.d.ts.map +1 -0
- package/dist/workflow-discovery.js +103 -0
- package/dist/workflow-discovery.js.map +1 -0
- package/dist/workflow-executor.d.ts +50 -0
- package/dist/workflow-executor.d.ts.map +1 -0
- package/dist/workflow-executor.js +738 -0
- package/dist/workflow-executor.js.map +1 -0
- package/dist/workflow-sdk.d.ts +44 -0
- package/dist/workflow-sdk.d.ts.map +1 -0
- package/dist/workflow-sdk.js +457 -0
- package/dist/workflow-sdk.js.map +1 -0
- package/dist/workflow-spec.d.ts +33 -0
- package/dist/workflow-spec.d.ts.map +1 -0
- package/dist/workflow-spec.js +476 -0
- package/dist/workflow-spec.js.map +1 -0
- package/dist/workflows-dir.d.ts +3 -0
- package/dist/workflows-dir.d.ts.map +1 -0
- package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
- package/dist/workflows-dir.js.map +1 -0
- package/package.json +53 -38
- package/schemas/decomposition-specs.schema.json +48 -48
- package/schemas/execution-results.schema.json +48 -48
- package/schemas/investigation-findings.schema.json +43 -43
- package/schemas/pattern-analysis.schema.json +41 -41
- package/schemas/phase.schema.json +48 -0
- package/schemas/plan-breakdown.schema.json +20 -20
- package/schemas/quality-analysis.schema.json +33 -33
- package/schemas/research-findings.schema.json +42 -42
- package/schemas/structure-analysis.schema.json +40 -40
- package/schemas/synthesis.schema.json +31 -31
- package/schemas/verifier-output.schema.json +94 -94
- package/skill-narrative.md +107 -0
- package/skills/pi-workflows/SKILL.md +185 -0
- package/skills/pi-workflows/references/bundled-resources.md +87 -0
- package/templates/architecture-designer/task.md +117 -0
- package/templates/architecture-inferrer/task.md +61 -0
- package/templates/gap-identifier/task.md +103 -0
- package/templates/handoff-writer/task.md +91 -0
- package/templates/plan-creator/task.md +143 -0
- package/templates/project-definer/task.md +67 -0
- package/templates/project-inferrer/task.md +56 -0
- package/templates/requirements-gatherer/task.md +90 -0
- package/workflows/analyze-existing-project.workflow.yaml +94 -0
- package/workflows/create-handoff.workflow.yaml +66 -0
- package/workflows/create-phase.workflow.yaml +1 -1
- package/workflows/do-gap.workflow.yaml +4 -4
- package/workflows/fix-audit.workflow.yaml +1 -1
- package/workflows/gap-to-phase.workflow.yaml +1 -1
- package/workflows/init-new-project.workflow.yaml +81 -0
- package/workflows/plan-from-requirements.workflow.yaml +59 -0
- package/workflows/self-implement.workflow.yaml +3 -3
- package/workflows/typed-analysis.workflow.yaml +4 -4
- package/src/agent-spec.test.ts +0 -289
- package/src/agent-spec.ts +0 -122
- package/src/block-validation.test.ts +0 -350
- package/src/checkpoint.test.ts +0 -237
- package/src/checkpoint.ts +0 -140
- package/src/completion.test.ts +0 -143
- package/src/completion.ts +0 -68
- package/src/dag.test.ts +0 -350
- package/src/dag.ts +0 -219
- package/src/dispatch.test.ts +0 -475
- package/src/dispatch.ts +0 -353
- package/src/expression.test.ts +0 -353
- package/src/expression.ts +0 -332
- package/src/format.test.ts +0 -80
- package/src/format.ts +0 -41
- package/src/graduated-failure.test.ts +0 -556
- package/src/index.test.ts +0 -114
- package/src/index.ts +0 -516
- package/src/loop.test.ts +0 -551
- package/src/output.test.ts +0 -213
- package/src/output.ts +0 -70
- package/src/parallel-integration.test.ts +0 -175
- package/src/resume.test.ts +0 -192
- package/src/state.test.ts +0 -192
- package/src/state.ts +0 -254
- package/src/step-agent.test.ts +0 -327
- package/src/step-agent.ts +0 -178
- package/src/step-command.test.ts +0 -330
- package/src/step-command.ts +0 -132
- package/src/step-foreach.test.ts +0 -647
- package/src/step-foreach.ts +0 -148
- package/src/step-gate.test.ts +0 -185
- package/src/step-gate.ts +0 -116
- package/src/step-loop.test.ts +0 -626
- package/src/step-loop.ts +0 -323
- package/src/step-parallel.test.ts +0 -475
- package/src/step-parallel.ts +0 -168
- package/src/step-pause.test.ts +0 -30
- package/src/step-shared.test.ts +0 -355
- package/src/step-shared.ts +0 -157
- package/src/step-transform.test.ts +0 -155
- package/src/step-transform.ts +0 -47
- package/src/template-integration.test.ts +0 -72
- package/src/template.test.ts +0 -162
- package/src/test-helpers.ts +0 -51
- package/src/tui.test.ts +0 -355
- package/src/tui.ts +0 -182
- package/src/types.ts +0 -195
- package/src/verifier-schema.test.ts +0 -226
- package/src/workflow-discovery.test.ts +0 -105
- package/src/workflow-discovery.ts +0 -114
- package/src/workflow-executor.test.ts +0 -1532
- package/src/workflow-executor.ts +0 -810
- package/src/workflow-sdk.test.ts +0 -238
- package/src/workflow-sdk.ts +0 -262
- package/src/workflow-spec.test.ts +0 -576
- package/src/workflow-spec.ts +0 -471
package/dist/index.js
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension entry point — registers workflow tools and the `/workflow` command
|
|
3
|
+
* for discovering, executing, and managing multi-step workflow runs.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { Key } from "@mariozechner/pi-tui";
|
|
8
|
+
import { Type } from "@sinclair/typebox";
|
|
9
|
+
import { createAgentLoader } from "./agent-spec.js";
|
|
10
|
+
import { findIncompleteRun, formatIncompleteRun, validateResumeCompatibility } from "./checkpoint.js";
|
|
11
|
+
import { discoverWorkflows, findWorkflow } from "./workflow-discovery.js";
|
|
12
|
+
import { executeWorkflow, requestPause } from "./workflow-executor.js";
|
|
13
|
+
import { availableAgents, availableSchemas, availableTemplates, filterNames, stepTypes, validateWorkflow, } from "./workflow-sdk.js";
|
|
14
|
+
import { WORKFLOWS_DIR } from "./workflows-dir.js";
|
|
15
|
+
// ── Helper functions ────────────────────────────────────────────────────────
|
|
16
|
+
function listWorkflowNames(cwd) {
|
|
17
|
+
const workflows = discoverWorkflows(cwd);
|
|
18
|
+
if (workflows.length === 0)
|
|
19
|
+
return "(none)";
|
|
20
|
+
return workflows.map((w) => w.name).join(", ");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Summarize a JSON Schema's expected shape for error messages.
|
|
24
|
+
* Produces something like: { path: string (required), question?: string }
|
|
25
|
+
*/
|
|
26
|
+
function summarizeInputSchema(schema) {
|
|
27
|
+
if (!schema)
|
|
28
|
+
return "(any)";
|
|
29
|
+
const props = schema.properties;
|
|
30
|
+
if (!props)
|
|
31
|
+
return JSON.stringify(schema);
|
|
32
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
33
|
+
const fields = Object.entries(props).map(([key, val]) => {
|
|
34
|
+
const type = val?.type || "unknown";
|
|
35
|
+
const req = required.has(key);
|
|
36
|
+
return req ? `${key}: ${type} (required)` : `${key}?: ${type}`;
|
|
37
|
+
});
|
|
38
|
+
return `{ ${fields.join(", ")} }`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Prompt for workflow input fields. Supports `source` field for select-from-file.
|
|
42
|
+
* Returns the input object, or null if user cancelled.
|
|
43
|
+
*/
|
|
44
|
+
async function promptForInput(spec, ctx) {
|
|
45
|
+
const schema = spec.input;
|
|
46
|
+
if (!schema)
|
|
47
|
+
return {};
|
|
48
|
+
const props = schema.properties;
|
|
49
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
50
|
+
if (!props)
|
|
51
|
+
return {};
|
|
52
|
+
const inputObj = {};
|
|
53
|
+
for (const [key, val] of Object.entries(props)) {
|
|
54
|
+
if (!required.has(key) || val?.default !== undefined)
|
|
55
|
+
continue;
|
|
56
|
+
if (!ctx.hasUI) {
|
|
57
|
+
ctx.ui.notify("Workflow input prompts require interactive mode.", "warning");
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const source = val?.source;
|
|
61
|
+
if (source?.file && source?.array) {
|
|
62
|
+
// Source-based select: load options from a JSON file
|
|
63
|
+
const filePath = String(source.file);
|
|
64
|
+
const arrayField = String(source.array);
|
|
65
|
+
const labelField = String(source.label || "id");
|
|
66
|
+
const valueField = String(source.value || "id");
|
|
67
|
+
const filter = source.filter;
|
|
68
|
+
try {
|
|
69
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
70
|
+
let items = data[arrayField];
|
|
71
|
+
if (filter) {
|
|
72
|
+
items = items.filter((item) => Object.entries(filter).every(([k, v]) => item[k] === v));
|
|
73
|
+
}
|
|
74
|
+
if (items.length === 0) {
|
|
75
|
+
ctx.ui.notify(`No items found in ${filePath} matching filter.`, "warning");
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const options = items.map((item) => String(item[labelField] ?? ""));
|
|
79
|
+
const desc = val?.description || key;
|
|
80
|
+
const selected = await ctx.ui.select(desc, options);
|
|
81
|
+
if (selected == null)
|
|
82
|
+
return null;
|
|
83
|
+
// Map label back to value
|
|
84
|
+
const selectedItem = items.find((item) => String(item[labelField]) === selected);
|
|
85
|
+
inputObj[key] = selectedItem ? String(selectedItem[valueField]) : selected;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
ctx.ui.notify(`Failed to load options from ${filePath}`, "warning");
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Standard text input
|
|
94
|
+
const type = val?.type || "string";
|
|
95
|
+
const desc = val?.description || "";
|
|
96
|
+
const prompt = desc ? `${key} (${type}): ${desc}` : `${key} (${type})`;
|
|
97
|
+
const value = await ctx.ui.input(prompt);
|
|
98
|
+
if (value == null)
|
|
99
|
+
return null;
|
|
100
|
+
if (type === "number") {
|
|
101
|
+
inputObj[key] = Number(value);
|
|
102
|
+
}
|
|
103
|
+
else if (type === "array" || type === "object") {
|
|
104
|
+
try {
|
|
105
|
+
inputObj[key] = JSON.parse(value);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
inputObj[key] = value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
inputObj[key] = value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return inputObj;
|
|
117
|
+
}
|
|
118
|
+
function formatToolResult(result) {
|
|
119
|
+
const status = result.status === "completed" ? "completed" : "failed";
|
|
120
|
+
const stepSummary = Object.entries(result.steps)
|
|
121
|
+
.map(([name, s]) => `${s.status === "completed" ? "\u2713" : "\u2717"} ${name}`)
|
|
122
|
+
.join(", ");
|
|
123
|
+
return `Workflow '${result.workflow}' ${status}: ${stepSummary}. Run dir: ${result.runDir}`;
|
|
124
|
+
}
|
|
125
|
+
// ── Shared logic (used by both tools and commands) ──────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* Validate one or all workflow specs. Returns structured results suitable
|
|
128
|
+
* for both the /workflow validate command and the workflow-validate tool.
|
|
129
|
+
*/
|
|
130
|
+
function runValidation(cwd, name) {
|
|
131
|
+
const workflows = name
|
|
132
|
+
? [findWorkflow(name, cwd)].filter(Boolean)
|
|
133
|
+
: discoverWorkflows(cwd);
|
|
134
|
+
if (workflows.length === 0)
|
|
135
|
+
return { found: false, results: [] };
|
|
136
|
+
const results = workflows.map((spec) => {
|
|
137
|
+
const result = validateWorkflow(spec, cwd);
|
|
138
|
+
return { name: spec.name, valid: result.valid, issues: result.issues };
|
|
139
|
+
});
|
|
140
|
+
return { found: true, results };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Aggregate workflow vocabulary — step types, filters, available agents,
|
|
144
|
+
* workflows, schemas, templates. Shared between the /workflow status
|
|
145
|
+
* command and the workflow-status tool.
|
|
146
|
+
*/
|
|
147
|
+
function gatherWorkflowStatus(cwd) {
|
|
148
|
+
const workflows = discoverWorkflows(cwd);
|
|
149
|
+
const agents = availableAgents(cwd);
|
|
150
|
+
const schemas = availableSchemas(cwd);
|
|
151
|
+
const templates = availableTemplates(cwd);
|
|
152
|
+
const types = stepTypes();
|
|
153
|
+
const filters = filterNames();
|
|
154
|
+
return {
|
|
155
|
+
stepTypes: types.map((t) => t.name),
|
|
156
|
+
filters,
|
|
157
|
+
workflows: workflows.map((w) => ({ name: w.name, description: w.description, source: w.source })),
|
|
158
|
+
agents: agents.map((a) => ({
|
|
159
|
+
name: a.name,
|
|
160
|
+
role: a.role,
|
|
161
|
+
description: a.description,
|
|
162
|
+
model: a.model,
|
|
163
|
+
tools: a.tools,
|
|
164
|
+
outputFormat: a.outputFormat,
|
|
165
|
+
})),
|
|
166
|
+
schemas: schemas.length,
|
|
167
|
+
templates: templates.length,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Initialize .workflows/ directory structure. Returns list of created
|
|
172
|
+
* directory paths (relative to cwd). Shared between the /workflow init
|
|
173
|
+
* command and the workflow-init tool.
|
|
174
|
+
*/
|
|
175
|
+
function initWorkflowDirs(cwd) {
|
|
176
|
+
const workflowsDir = path.join(cwd, WORKFLOWS_DIR);
|
|
177
|
+
const runsDir = path.join(workflowsDir, "runs");
|
|
178
|
+
const created = [];
|
|
179
|
+
for (const dir of [workflowsDir, runsDir]) {
|
|
180
|
+
if (!fs.existsSync(dir)) {
|
|
181
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
182
|
+
created.push(path.relative(cwd, dir) + "/");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { created };
|
|
186
|
+
}
|
|
187
|
+
// ── Command handlers ────────────────────────────────────────────────────────
|
|
188
|
+
async function handleList(ctx, pi) {
|
|
189
|
+
const workflows = discoverWorkflows(ctx.cwd);
|
|
190
|
+
if (workflows.length === 0) {
|
|
191
|
+
ctx.ui.notify("No workflows found in .workflows/ or ~/.pi/agent/workflows/", "info");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const options = workflows.map((w) => {
|
|
195
|
+
const source = w.source === "project" ? "[project]" : "[user]";
|
|
196
|
+
const desc = w.description ? ` — ${w.description}` : "";
|
|
197
|
+
return `${w.name} ${source}${desc}`;
|
|
198
|
+
});
|
|
199
|
+
if (!ctx.hasUI) {
|
|
200
|
+
ctx.ui.notify("Workflow list requires interactive mode.", "warning");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const selected = await ctx.ui.select("Run workflow", options);
|
|
204
|
+
if (!selected)
|
|
205
|
+
return; // user cancelled
|
|
206
|
+
const name = selected.split(" ")[0];
|
|
207
|
+
const spec = findWorkflow(name, ctx.cwd);
|
|
208
|
+
if (!spec) {
|
|
209
|
+
ctx.ui.notify(`Workflow '${name}' not found.`, "warning");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// Prompt for required input fields
|
|
213
|
+
const input = await promptForInput(spec, ctx);
|
|
214
|
+
if (input === null)
|
|
215
|
+
return;
|
|
216
|
+
const inputJson = Object.keys(input).length > 0 ? JSON.stringify(input) : undefined;
|
|
217
|
+
const rawArgs = inputJson ? `${name} --input '${inputJson}'` : name;
|
|
218
|
+
await handleRun(rawArgs, ctx, pi);
|
|
219
|
+
}
|
|
220
|
+
async function handleRun(rawArgs, ctx, pi) {
|
|
221
|
+
// Extract workflow name (first token) and --input value (everything after --input flag)
|
|
222
|
+
const inputFlagIdx = rawArgs.indexOf("--input");
|
|
223
|
+
let namePart;
|
|
224
|
+
let inputJson;
|
|
225
|
+
if (inputFlagIdx !== -1) {
|
|
226
|
+
namePart = rawArgs.slice(0, inputFlagIdx).trim();
|
|
227
|
+
inputJson = rawArgs.slice(inputFlagIdx + "--input".length).trim();
|
|
228
|
+
// Strip surrounding single or double quotes
|
|
229
|
+
if ((inputJson.startsWith("'") && inputJson.endsWith("'")) ||
|
|
230
|
+
(inputJson.startsWith('"') && inputJson.endsWith('"'))) {
|
|
231
|
+
inputJson = inputJson.slice(1, -1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
namePart = rawArgs.trim();
|
|
236
|
+
}
|
|
237
|
+
const name = namePart.split(/\s+/)[0];
|
|
238
|
+
if (!name) {
|
|
239
|
+
ctx.ui.notify("Usage: /workflow run <name> [--input '<json>']", "warning");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const spec = findWorkflow(name, ctx.cwd);
|
|
243
|
+
if (!spec) {
|
|
244
|
+
ctx.ui.notify(`Workflow '${name}' not found.`, "warning");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Check for resumable run before starting fresh
|
|
248
|
+
const incomplete = findIncompleteRun(ctx.cwd, spec.name);
|
|
249
|
+
if (incomplete) {
|
|
250
|
+
const compat = validateResumeCompatibility(incomplete.state, spec);
|
|
251
|
+
if (!compat) {
|
|
252
|
+
const summary = formatIncompleteRun(incomplete, spec);
|
|
253
|
+
if (!ctx.hasUI) {
|
|
254
|
+
// Non-interactive mode: auto-resume incomplete run
|
|
255
|
+
try {
|
|
256
|
+
await executeWorkflow(spec, incomplete.state.input, {
|
|
257
|
+
ctx,
|
|
258
|
+
pi,
|
|
259
|
+
loadAgent: createAgentLoader(ctx.cwd),
|
|
260
|
+
resume: {
|
|
261
|
+
runId: incomplete.runId,
|
|
262
|
+
runDir: incomplete.runDir,
|
|
263
|
+
state: incomplete.state,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
ctx.ui.notify(`Resume failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const choice = await ctx.ui.select(`${summary}\n\nResume this run?`, [
|
|
273
|
+
"Yes — resume from checkpoint",
|
|
274
|
+
"No — start fresh",
|
|
275
|
+
]);
|
|
276
|
+
if (choice === "Yes — resume from checkpoint") {
|
|
277
|
+
try {
|
|
278
|
+
await executeWorkflow(spec, incomplete.state.input, {
|
|
279
|
+
ctx,
|
|
280
|
+
pi,
|
|
281
|
+
loadAgent: createAgentLoader(ctx.cwd),
|
|
282
|
+
resume: {
|
|
283
|
+
runId: incomplete.runId,
|
|
284
|
+
runDir: incomplete.runDir,
|
|
285
|
+
state: incomplete.state,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
ctx.ui.notify(`Resume failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
// User chose fresh — fall through to normal execution
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Parse input: --input JSON, or infer single required field from positional arg
|
|
298
|
+
let input = {};
|
|
299
|
+
if (inputJson) {
|
|
300
|
+
try {
|
|
301
|
+
input = JSON.parse(inputJson);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
ctx.ui.notify(`Invalid JSON for --input: ${inputJson}`, "warning");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else if (spec.input) {
|
|
309
|
+
const prompted = await promptForInput(spec, ctx);
|
|
310
|
+
if (prompted === null)
|
|
311
|
+
return;
|
|
312
|
+
input = prompted;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
await executeWorkflow(spec, input, {
|
|
316
|
+
ctx,
|
|
317
|
+
pi,
|
|
318
|
+
loadAgent: createAgentLoader(ctx.cwd),
|
|
319
|
+
});
|
|
320
|
+
// Result is injected into conversation by executeWorkflow via sendMessage
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
ctx.ui.notify(`Workflow '${name}' failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function handleResume(rawArgs, ctx, pi) {
|
|
327
|
+
const name = rawArgs.trim().split(/\s+/)[0];
|
|
328
|
+
if (!name) {
|
|
329
|
+
ctx.ui.notify("Usage: /workflow resume <name>", "warning");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const spec = findWorkflow(name, ctx.cwd);
|
|
333
|
+
if (!spec) {
|
|
334
|
+
ctx.ui.notify(`Workflow '${name}' not found.`, "warning");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const incomplete = findIncompleteRun(ctx.cwd, spec.name);
|
|
338
|
+
if (!incomplete) {
|
|
339
|
+
ctx.ui.notify(`No incomplete runs found for '${name}'.`, "info");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
// Validate compatibility
|
|
343
|
+
const compat = validateResumeCompatibility(incomplete.state, spec);
|
|
344
|
+
if (compat) {
|
|
345
|
+
ctx.ui.notify(`Cannot resume: ${compat}`, "warning");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// Show summary and confirm
|
|
349
|
+
const summary = formatIncompleteRun(incomplete, spec);
|
|
350
|
+
if (!ctx.hasUI) {
|
|
351
|
+
// Non-interactive mode: auto-resume without confirmation
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const choice = await ctx.ui.select(`${summary}\n\nResume this run?`, ["Yes — resume", "No — cancel"]);
|
|
355
|
+
if (choice !== "Yes — resume")
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
await executeWorkflow(spec, incomplete.state.input, {
|
|
360
|
+
ctx,
|
|
361
|
+
pi,
|
|
362
|
+
loadAgent: createAgentLoader(ctx.cwd),
|
|
363
|
+
resume: {
|
|
364
|
+
runId: incomplete.runId,
|
|
365
|
+
runDir: incomplete.runDir,
|
|
366
|
+
state: incomplete.state,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
ctx.ui.notify(`Resume failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function handleValidate(args, ctx) {
|
|
375
|
+
const name = args.trim() || undefined;
|
|
376
|
+
const { found, results } = runValidation(ctx.cwd, name);
|
|
377
|
+
if (name && !found) {
|
|
378
|
+
ctx.ui.notify(`Workflow '${name}' not found.`, "warning");
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (!found) {
|
|
382
|
+
ctx.ui.notify("No workflows found.", "info");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const lines = [];
|
|
386
|
+
let totalErrors = 0;
|
|
387
|
+
let totalWarnings = 0;
|
|
388
|
+
for (const r of results) {
|
|
389
|
+
const errors = r.issues.filter((i) => i.severity === "error").length;
|
|
390
|
+
const warnings = r.issues.filter((i) => i.severity === "warning").length;
|
|
391
|
+
totalErrors += errors;
|
|
392
|
+
totalWarnings += warnings;
|
|
393
|
+
const icon = r.valid ? "\u2713" : "\u2717";
|
|
394
|
+
lines.push(`${icon} ${r.name} (${errors} errors, ${warnings} warnings)`);
|
|
395
|
+
for (const issue of r.issues) {
|
|
396
|
+
lines.push(` ${issue.severity === "error" ? "\u2717" : "\u26a0"} ${issue.field}: ${issue.message}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
lines.push("");
|
|
400
|
+
lines.push(`${results.length} workflow(s), ${totalErrors} error(s), ${totalWarnings} warning(s)`);
|
|
401
|
+
ctx.ui.notify(lines.join("\n"), totalErrors > 0 ? "error" : "info");
|
|
402
|
+
}
|
|
403
|
+
function handleWorkflowInit(ctx) {
|
|
404
|
+
const { created } = initWorkflowDirs(ctx.cwd);
|
|
405
|
+
if (created.length > 0) {
|
|
406
|
+
ctx.ui.notify(`Workflows initialized: created ${created.join(", ")}`, "info");
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
ctx.ui.notify("Workflows already initialized — nothing to do.", "info");
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// ── Extension factory ───────────────────────────────────────────────────────
|
|
413
|
+
const extension = (pi) => {
|
|
414
|
+
// ── Tool: workflow ──────────────────────────────────────────────────────
|
|
415
|
+
pi.registerTool({
|
|
416
|
+
name: "workflow",
|
|
417
|
+
label: "Workflow",
|
|
418
|
+
description: "Run a named workflow with typed input. Discovers workflows from .workflows/ and ~/.pi/agent/workflows/.",
|
|
419
|
+
promptSnippet: "Run a multi-step workflow with typed data flow between agents",
|
|
420
|
+
parameters: Type.Object({
|
|
421
|
+
workflow: Type.String({ description: "Name of the workflow to run" }),
|
|
422
|
+
input: Type.Optional(Type.Unknown({ description: "Input data for the workflow (validated against workflow's input schema)" })),
|
|
423
|
+
fresh: Type.Optional(Type.String({ description: "Set to 'true' to start a fresh run, ignoring any incomplete prior runs" })),
|
|
424
|
+
}),
|
|
425
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
426
|
+
const spec = findWorkflow(params.workflow, ctx.cwd);
|
|
427
|
+
if (!spec) {
|
|
428
|
+
throw new Error(`Workflow '${params.workflow}' not found. Available workflows: ${listWorkflowNames(ctx.cwd)}`);
|
|
429
|
+
}
|
|
430
|
+
// Defensive: if input arrives as a JSON string (e.g. from Type.Unknown()),
|
|
431
|
+
// parse it into an object.
|
|
432
|
+
let input = params.input ?? {};
|
|
433
|
+
if (typeof input === "string") {
|
|
434
|
+
try {
|
|
435
|
+
input = JSON.parse(input);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
// leave as string — validation will catch it if schema expects object
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Check for resumable run (unless explicitly requesting fresh)
|
|
442
|
+
let resumeOpts;
|
|
443
|
+
if (params.fresh !== "true") {
|
|
444
|
+
const incomplete = findIncompleteRun(ctx.cwd, spec.name);
|
|
445
|
+
if (incomplete) {
|
|
446
|
+
const compat = validateResumeCompatibility(incomplete.state, spec);
|
|
447
|
+
if (!compat) {
|
|
448
|
+
resumeOpts = {
|
|
449
|
+
runId: incomplete.runId,
|
|
450
|
+
runDir: incomplete.runDir,
|
|
451
|
+
state: incomplete.state,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
// If incompatible, silently start fresh
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const result = await executeWorkflow(spec, input, {
|
|
458
|
+
ctx,
|
|
459
|
+
pi,
|
|
460
|
+
signal,
|
|
461
|
+
loadAgent: createAgentLoader(ctx.cwd),
|
|
462
|
+
resume: resumeOpts,
|
|
463
|
+
});
|
|
464
|
+
return {
|
|
465
|
+
content: [{ type: "text", text: formatToolResult(result) }],
|
|
466
|
+
details: result,
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
// ── Tool: workflow-list ─────────────────────────────────────────────────
|
|
471
|
+
pi.registerTool({
|
|
472
|
+
name: "workflow-list",
|
|
473
|
+
label: "Workflow List",
|
|
474
|
+
description: "List available workflows with names, descriptions, and sources.",
|
|
475
|
+
promptSnippet: "List available workflows with names, descriptions, and sources",
|
|
476
|
+
parameters: Type.Object({}),
|
|
477
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
478
|
+
const workflows = discoverWorkflows(ctx.cwd);
|
|
479
|
+
const items = workflows.map((w) => ({
|
|
480
|
+
name: w.name,
|
|
481
|
+
description: w.description,
|
|
482
|
+
source: w.source,
|
|
483
|
+
}));
|
|
484
|
+
return {
|
|
485
|
+
details: undefined,
|
|
486
|
+
content: [{ type: "text", text: JSON.stringify(items, null, 2) }],
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
// ── Tool: workflow-agents ──────────────────────────────────────────────
|
|
491
|
+
pi.registerTool({
|
|
492
|
+
name: "workflow-agents",
|
|
493
|
+
label: "Workflow Agents",
|
|
494
|
+
description: "List available agents with full specs, or inspect a single agent by name. Returns role, description, model, tools, output format/schema, prompt template paths.",
|
|
495
|
+
promptSnippet: "List available agents with specs, or inspect a single agent by name",
|
|
496
|
+
parameters: Type.Object({
|
|
497
|
+
name: Type.Optional(Type.String({ description: "Agent name to inspect (omit to list all)" })),
|
|
498
|
+
}),
|
|
499
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
500
|
+
const agents = availableAgents(ctx.cwd);
|
|
501
|
+
if (params.name) {
|
|
502
|
+
const agent = agents.find((a) => a.name === params.name);
|
|
503
|
+
if (!agent) {
|
|
504
|
+
throw new Error(`Agent '${params.name}' not found. Available: ${agents.map((a) => a.name).join(", ")}`);
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
details: undefined,
|
|
508
|
+
content: [{ type: "text", text: JSON.stringify(agent, null, 2) }],
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const items = agents.map((a) => ({
|
|
512
|
+
name: a.name,
|
|
513
|
+
role: a.role,
|
|
514
|
+
description: a.description,
|
|
515
|
+
model: a.model,
|
|
516
|
+
tools: a.tools,
|
|
517
|
+
extensions: a.extensions,
|
|
518
|
+
skills: a.skills,
|
|
519
|
+
outputFormat: a.outputFormat,
|
|
520
|
+
outputSchema: a.outputSchema,
|
|
521
|
+
promptTemplate: a.promptTemplate,
|
|
522
|
+
taskTemplate: a.taskTemplate,
|
|
523
|
+
}));
|
|
524
|
+
return {
|
|
525
|
+
details: undefined,
|
|
526
|
+
content: [{ type: "text", text: JSON.stringify(items, null, 2) }],
|
|
527
|
+
};
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
// ── Tool: workflow-validate ─────────────────────────────────────────────
|
|
531
|
+
pi.registerTool({
|
|
532
|
+
name: "workflow-validate",
|
|
533
|
+
label: "Workflow Validate",
|
|
534
|
+
description: "Validate workflow specs — check agents, schemas, step references, and filters.",
|
|
535
|
+
promptSnippet: "Validate workflow specs — check agents, schemas, step references, filters",
|
|
536
|
+
parameters: Type.Object({
|
|
537
|
+
name: Type.Optional(Type.String({ description: "Workflow name to validate (omit to validate all)" })),
|
|
538
|
+
}),
|
|
539
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
540
|
+
const { found, results } = runValidation(ctx.cwd, params.name);
|
|
541
|
+
if (params.name && !found) {
|
|
542
|
+
throw new Error(`Workflow '${params.name}' not found. Available workflows: ${listWorkflowNames(ctx.cwd)}`);
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
details: undefined,
|
|
546
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
547
|
+
};
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
// ── Tool: workflow-status ───────────────────────────────────────────────
|
|
551
|
+
pi.registerTool({
|
|
552
|
+
name: "workflow-status",
|
|
553
|
+
label: "Workflow Status",
|
|
554
|
+
description: "Get workflow vocabulary — step types, filters, available agents, workflows, schemas, templates.",
|
|
555
|
+
promptSnippet: "Get workflow vocabulary — step types, filters, available agents, workflows, schemas",
|
|
556
|
+
parameters: Type.Object({}),
|
|
557
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
558
|
+
const status = gatherWorkflowStatus(ctx.cwd);
|
|
559
|
+
return {
|
|
560
|
+
details: undefined,
|
|
561
|
+
content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
|
|
562
|
+
};
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
// ── Tool: workflow-init ─────────────────────────────────────────────────
|
|
566
|
+
pi.registerTool({
|
|
567
|
+
name: "workflow-init",
|
|
568
|
+
label: "Workflow Init",
|
|
569
|
+
description: "Initialize .workflows/ directory for workflow run state.",
|
|
570
|
+
promptSnippet: "Initialize .workflows/ directory for workflow run state",
|
|
571
|
+
parameters: Type.Object({}),
|
|
572
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
573
|
+
const result = initWorkflowDirs(ctx.cwd);
|
|
574
|
+
return {
|
|
575
|
+
details: undefined,
|
|
576
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
577
|
+
};
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
const workflowNameCompletions = (argPrefix) => {
|
|
581
|
+
const workflows = discoverWorkflows(process.cwd());
|
|
582
|
+
return workflows
|
|
583
|
+
.filter((w) => w.name.startsWith(argPrefix))
|
|
584
|
+
.map((w) => ({ value: w.name, label: w.name, description: w.description || "" }));
|
|
585
|
+
};
|
|
586
|
+
const WORKFLOW_SUBCOMMANDS = {
|
|
587
|
+
init: {
|
|
588
|
+
description: "Initialize .workflows/ directory",
|
|
589
|
+
handler: (_args, ctx) => handleWorkflowInit(ctx),
|
|
590
|
+
},
|
|
591
|
+
list: {
|
|
592
|
+
description: "List and select a workflow to run",
|
|
593
|
+
handler: (_args, ctx) => handleList(ctx, pi),
|
|
594
|
+
},
|
|
595
|
+
run: {
|
|
596
|
+
description: "Run a workflow by name",
|
|
597
|
+
handler: (args, ctx) => handleRun(args, ctx, pi),
|
|
598
|
+
getCompletions: workflowNameCompletions,
|
|
599
|
+
},
|
|
600
|
+
resume: {
|
|
601
|
+
description: "Resume an incomplete workflow run",
|
|
602
|
+
handler: (args, ctx) => handleResume(args, ctx, pi),
|
|
603
|
+
getCompletions: workflowNameCompletions,
|
|
604
|
+
},
|
|
605
|
+
validate: {
|
|
606
|
+
description: "Validate workflow specs",
|
|
607
|
+
handler: (args, ctx) => handleValidate(args, ctx),
|
|
608
|
+
getCompletions: workflowNameCompletions,
|
|
609
|
+
},
|
|
610
|
+
status: {
|
|
611
|
+
description: "Show workflow vocabulary and discovery",
|
|
612
|
+
handler: (_args, ctx) => {
|
|
613
|
+
const status = gatherWorkflowStatus(ctx.cwd);
|
|
614
|
+
const lines = [];
|
|
615
|
+
lines.push(`Step types: ${status.stepTypes.join(", ")}`);
|
|
616
|
+
lines.push(`Filters: ${status.filters.join(", ")}`);
|
|
617
|
+
const wfs = status.workflows;
|
|
618
|
+
lines.push(`Workflows: ${wfs.length} (${wfs.map((w) => w.name).join(", ")})`);
|
|
619
|
+
const ags = status.agents;
|
|
620
|
+
lines.push(`Agents: ${ags.length} (${ags.map((a) => a.name).join(", ")})`);
|
|
621
|
+
lines.push(`Schemas: ${status.schemas}`);
|
|
622
|
+
lines.push(`Templates: ${status.templates}`);
|
|
623
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
help: {
|
|
627
|
+
description: "Show available subcommands",
|
|
628
|
+
handler: (_args, ctx) => {
|
|
629
|
+
const lines = ["Usage: /workflow <subcommand> [args]", ""];
|
|
630
|
+
for (const [name, entry] of Object.entries(WORKFLOW_SUBCOMMANDS)) {
|
|
631
|
+
lines.push(` ${name.padEnd(12)} ${entry.description}`);
|
|
632
|
+
}
|
|
633
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
pi.registerCommand("workflow", {
|
|
638
|
+
description: "List and run workflows",
|
|
639
|
+
getArgumentCompletions: (prefix) => {
|
|
640
|
+
const tokens = prefix.split(/\s+/);
|
|
641
|
+
const partial = tokens[tokens.length - 1];
|
|
642
|
+
// Level 0: completing the subcommand name
|
|
643
|
+
if (tokens.length <= 1) {
|
|
644
|
+
return Object.entries(WORKFLOW_SUBCOMMANDS)
|
|
645
|
+
.filter(([name]) => name.startsWith(partial))
|
|
646
|
+
.map(([name, entry]) => ({ value: name, label: name, description: entry.description }));
|
|
647
|
+
}
|
|
648
|
+
// Level 1+: delegate to subcommand's getCompletions
|
|
649
|
+
const subName = tokens[0];
|
|
650
|
+
const sub = WORKFLOW_SUBCOMMANDS[subName];
|
|
651
|
+
if (sub?.getCompletions) {
|
|
652
|
+
const argPrefix = tokens.slice(1).join(" ");
|
|
653
|
+
const items = sub.getCompletions(argPrefix);
|
|
654
|
+
if (items) {
|
|
655
|
+
return items.map((item) => ({ ...item, value: `${subName} ${item.value}` }));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return null;
|
|
659
|
+
},
|
|
660
|
+
async handler(args, ctx) {
|
|
661
|
+
const trimmed = args.trim();
|
|
662
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
663
|
+
const subcommand = spaceIdx === -1 ? trimmed || "list" : trimmed.slice(0, spaceIdx);
|
|
664
|
+
const rest = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1);
|
|
665
|
+
const entry = WORKFLOW_SUBCOMMANDS[subcommand];
|
|
666
|
+
if (!entry) {
|
|
667
|
+
const names = Object.keys(WORKFLOW_SUBCOMMANDS).join(", ");
|
|
668
|
+
ctx.ui.notify(`Unknown subcommand: ${subcommand}. Available: ${names}`, "warning");
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
await entry.handler(rest, ctx);
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
// ── Keybindings ──
|
|
675
|
+
if (Key) {
|
|
676
|
+
pi.registerShortcut(Key.ctrl("h"), {
|
|
677
|
+
description: "Pause running workflow",
|
|
678
|
+
handler: async (ctx) => {
|
|
679
|
+
requestPause();
|
|
680
|
+
ctx.ui.notify("Pause requested — workflow will pause after current step completes.", "info");
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
pi.registerShortcut(Key.ctrl("j"), {
|
|
684
|
+
description: "Resume paused workflow",
|
|
685
|
+
handler: async (ctx) => {
|
|
686
|
+
const workflows = discoverWorkflows(ctx.cwd);
|
|
687
|
+
let found = null;
|
|
688
|
+
for (const wfSpec of workflows) {
|
|
689
|
+
const incomplete = findIncompleteRun(ctx.cwd, wfSpec.name);
|
|
690
|
+
if (incomplete) {
|
|
691
|
+
const compat = validateResumeCompatibility(incomplete.state, wfSpec);
|
|
692
|
+
if (!compat) {
|
|
693
|
+
found = { spec: wfSpec, incomplete };
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (!found) {
|
|
699
|
+
ctx.ui.notify("No paused or incomplete workflows to resume.", "info");
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const summary = formatIncompleteRun(found.incomplete, found.spec);
|
|
703
|
+
ctx.ui.notify(`Resuming: ${summary}`, "info");
|
|
704
|
+
try {
|
|
705
|
+
await executeWorkflow(found.spec, found.incomplete.state.input, {
|
|
706
|
+
ctx,
|
|
707
|
+
pi,
|
|
708
|
+
loadAgent: createAgentLoader(ctx.cwd),
|
|
709
|
+
resume: {
|
|
710
|
+
runId: found.incomplete.runId,
|
|
711
|
+
runDir: found.incomplete.runDir,
|
|
712
|
+
state: found.incomplete.state,
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
catch (err) {
|
|
717
|
+
ctx.ui.notify(`Resume failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
export default extension;
|
|
724
|
+
//# sourceMappingURL=index.js.map
|