@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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for step executors — constants, usage aggregation,
|
|
3
|
+
* prompt building, schema resolution, state persistence, and template resolution.
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { writeState } from "./state.js";
|
|
7
|
+
import { renderTemplate, renderTemplateFile } from "./template.js";
|
|
8
|
+
import { createProgressWidget } from "./tui.js";
|
|
9
|
+
/** Grace period (ms) between SIGTERM and SIGKILL when killing subprocesses. */
|
|
10
|
+
export const SIGKILL_GRACE_MS = 3000;
|
|
11
|
+
/** Widget ID used for the workflow progress widget. */
|
|
12
|
+
export const WIDGET_ID = "workflow-progress";
|
|
13
|
+
/** Default max loop attempts when not specified. */
|
|
14
|
+
export const DEFAULT_MAX_ATTEMPTS = 3;
|
|
15
|
+
/**
|
|
16
|
+
* Helper that returns a StepUsage with all zeroes.
|
|
17
|
+
*/
|
|
18
|
+
export function zeroUsage() {
|
|
19
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Add step usage into a running total (mutates `total`).
|
|
23
|
+
*/
|
|
24
|
+
export function addUsage(total, step) {
|
|
25
|
+
total.input += step.input;
|
|
26
|
+
total.output += step.output;
|
|
27
|
+
total.cacheRead += step.cacheRead;
|
|
28
|
+
total.cacheWrite += step.cacheWrite;
|
|
29
|
+
total.cost += step.cost;
|
|
30
|
+
total.turns += step.turns;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a schema path to an absolute filesystem path.
|
|
34
|
+
*
|
|
35
|
+
* Three resolution modes:
|
|
36
|
+
* - Absolute paths: returned as-is
|
|
37
|
+
* - `block:<name>` prefix: resolves to `.project/schemas/<name>.schema.json` from cwd.
|
|
38
|
+
* This is the portable way to reference project block schemas from any workflow or
|
|
39
|
+
* agent spec regardless of package install location. Uses the user's actual schemas
|
|
40
|
+
* (which may be customized).
|
|
41
|
+
* - Relative paths: resolved against the directory containing the spec file
|
|
42
|
+
*/
|
|
43
|
+
export function resolveSchemaPath(schemaPath, specFilePath, cwd) {
|
|
44
|
+
if (path.isAbsolute(schemaPath))
|
|
45
|
+
return schemaPath;
|
|
46
|
+
const blockMatch = schemaPath.match(/^block:(.+)$/);
|
|
47
|
+
if (blockMatch) {
|
|
48
|
+
const resolvedCwd = cwd || process.cwd();
|
|
49
|
+
return path.join(resolvedCwd, ".project", "schemas", `${blockMatch[1]}.schema.json`);
|
|
50
|
+
}
|
|
51
|
+
return path.resolve(path.dirname(specFilePath), schemaPath);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the prompt string sent to the subprocess.
|
|
55
|
+
*
|
|
56
|
+
* The prompt includes:
|
|
57
|
+
* 1. The compiled task template (if set), or the resolved input as context
|
|
58
|
+
* 2. Output instructions (if schema-bound)
|
|
59
|
+
*/
|
|
60
|
+
export function buildPrompt(step, agentSpec, resolvedInput, runDir, stepName, cwd) {
|
|
61
|
+
const parts = [];
|
|
62
|
+
// Task template was compiled by compileAgentSpec — use it
|
|
63
|
+
if (agentSpec.taskTemplate) {
|
|
64
|
+
parts.push(agentSpec.taskTemplate);
|
|
65
|
+
}
|
|
66
|
+
else if (resolvedInput && typeof resolvedInput === "object" && Object.keys(resolvedInput).length > 0) {
|
|
67
|
+
// No task template — serialize input as JSON
|
|
68
|
+
parts.push("## Input\n");
|
|
69
|
+
parts.push("```json");
|
|
70
|
+
parts.push(JSON.stringify(resolvedInput, null, 2));
|
|
71
|
+
parts.push("```\n");
|
|
72
|
+
}
|
|
73
|
+
else if (typeof resolvedInput === "string") {
|
|
74
|
+
parts.push(resolvedInput);
|
|
75
|
+
}
|
|
76
|
+
// Output instructions (if schema-bound)
|
|
77
|
+
if (step.output?.format === "json" || step.output?.schema) {
|
|
78
|
+
const outputPath = path.join(runDir, "outputs", `${stepName}.json`);
|
|
79
|
+
parts.push("\n---");
|
|
80
|
+
parts.push(`**Output:** Write your result as valid JSON to: ${outputPath}`);
|
|
81
|
+
if (step.output.schema) {
|
|
82
|
+
parts.push(`The output must conform to the JSON Schema at: ${resolveSchemaPath(step.output.schema, "", cwd)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return parts.join("\n");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Persist step result to state and update TUI widget.
|
|
89
|
+
* Replaces the repeated writeState + setWidget pattern.
|
|
90
|
+
*/
|
|
91
|
+
export function persistStep(state, stepName, result, runDir, widgetState, ctx) {
|
|
92
|
+
state.steps[stepName] = result;
|
|
93
|
+
// Clear activity buffer for completed step
|
|
94
|
+
widgetState.activities?.delete(stepName);
|
|
95
|
+
try {
|
|
96
|
+
writeState(runDir, state);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (ctx.hasUI && ctx.ui.notify) {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
ctx.ui.notify(`State write failed after step '${stepName}': ${msg}`, "error");
|
|
102
|
+
}
|
|
103
|
+
throw err; // re-throw — state write failure is fatal
|
|
104
|
+
}
|
|
105
|
+
if (ctx.hasUI) {
|
|
106
|
+
ctx.ui.setWidget(WIDGET_ID, createProgressWidget(widgetState));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compile an agent spec: render system and task templates through Nunjucks.
|
|
111
|
+
*
|
|
112
|
+
* Every agent's prompts go through Nunjucks. Plain text without template
|
|
113
|
+
* tags renders to itself. The .md that pi receives is compiled output.
|
|
114
|
+
*/
|
|
115
|
+
export function compileAgentSpec(agentSpec, resolvedInput, templateEnv) {
|
|
116
|
+
if (!templateEnv)
|
|
117
|
+
return agentSpec;
|
|
118
|
+
const ctx = typeof resolvedInput === "object" && resolvedInput !== null ? resolvedInput : {};
|
|
119
|
+
let result = agentSpec;
|
|
120
|
+
// System prompt: file template or inline — always rendered
|
|
121
|
+
if (agentSpec.promptTemplate) {
|
|
122
|
+
const rendered = renderTemplateFile(templateEnv, agentSpec.promptTemplate, ctx);
|
|
123
|
+
result = { ...result, systemPrompt: rendered, promptTemplate: undefined };
|
|
124
|
+
}
|
|
125
|
+
else if (agentSpec.systemPrompt) {
|
|
126
|
+
const rendered = renderTemplate(templateEnv, agentSpec.systemPrompt, ctx);
|
|
127
|
+
result = { ...result, systemPrompt: rendered };
|
|
128
|
+
}
|
|
129
|
+
// Task prompt: file template — rendered from typed input
|
|
130
|
+
if (agentSpec.taskTemplate) {
|
|
131
|
+
const rendered = renderTemplateFile(templateEnv, agentSpec.taskTemplate, ctx);
|
|
132
|
+
result = { ...result, taskTemplate: rendered };
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=step-shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-shared.js","sourceRoot":"","sources":["../src/step-shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform step executor — produces output by resolving expressions in a mapping.
|
|
3
|
+
* No LLM call, no subprocess, no shell command — pure expression resolution.
|
|
4
|
+
*/
|
|
5
|
+
import type { StepResult, TransformSpec } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Execute a transform step: produces output by resolving expressions in the mapping.
|
|
8
|
+
* No LLM call, no subprocess, no shell command — pure expression resolution.
|
|
9
|
+
*/
|
|
10
|
+
export declare function executeTransform(transform: TransformSpec, stepName: string, scope: Record<string, unknown>, runDir?: string, outputPath?: string): StepResult;
|
|
11
|
+
//# sourceMappingURL=step-transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-transform.d.ts","sourceRoot":"","sources":["../src/step-transform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,gBAAgB,CAC/B,SAAS,EAAE,aAAa,EACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GACjB,UAAU,CA2BZ"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform step executor — produces output by resolving expressions in a mapping.
|
|
3
|
+
* No LLM call, no subprocess, no shell command — pure expression resolution.
|
|
4
|
+
*/
|
|
5
|
+
import { resolveExpressions } from "./expression.js";
|
|
6
|
+
import { persistStepOutput } from "./output.js";
|
|
7
|
+
import { zeroUsage } from "./step-shared.js";
|
|
8
|
+
/**
|
|
9
|
+
* Execute a transform step: produces output by resolving expressions in the mapping.
|
|
10
|
+
* No LLM call, no subprocess, no shell command — pure expression resolution.
|
|
11
|
+
*/
|
|
12
|
+
export function executeTransform(transform, stepName, scope, runDir, outputPath) {
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
try {
|
|
15
|
+
const output = resolveExpressions(transform.mapping, scope);
|
|
16
|
+
const result = {
|
|
17
|
+
step: stepName,
|
|
18
|
+
agent: "transform",
|
|
19
|
+
status: "completed",
|
|
20
|
+
output,
|
|
21
|
+
textOutput: JSON.stringify(output, null, 2),
|
|
22
|
+
usage: zeroUsage(),
|
|
23
|
+
durationMs: Date.now() - startTime,
|
|
24
|
+
};
|
|
25
|
+
if (runDir) {
|
|
26
|
+
result.outputPath = persistStepOutput(runDir, stepName, output, undefined, outputPath);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return {
|
|
32
|
+
step: stepName,
|
|
33
|
+
agent: "transform",
|
|
34
|
+
status: "failed",
|
|
35
|
+
usage: zeroUsage(),
|
|
36
|
+
durationMs: Date.now() - startTime,
|
|
37
|
+
error: err instanceof Error ? err.message : String(err),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=step-transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-transform.js","sourceRoot":"","sources":["../src/step-transform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG7C;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC/B,SAAwB,EACxB,QAAgB,EAChB,KAA8B,EAC9B,MAAe,EACf,UAAmB;IAEnB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAe;YAC1B,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,WAAW;YAClB,MAAM,EAAE,WAAW;YACnB,MAAM;YACN,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,KAAK,EAAE,SAAS,EAAE;YAClB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SAClC,CAAC;QACF,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,UAAU,GAAG,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QACxF,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,WAAW;YAClB,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,SAAS,EAAE;YAClB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YAClC,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACvD,CAAC;IACH,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nunjucks template rendering for agent prompts.
|
|
3
|
+
* Provides three-tier template resolution (project > user > builtin)
|
|
4
|
+
* and renders agent system prompts with step input as context.
|
|
5
|
+
*/
|
|
6
|
+
import nunjucks from "nunjucks";
|
|
7
|
+
/**
|
|
8
|
+
* Create a Nunjucks environment with three-tier template resolution.
|
|
9
|
+
*
|
|
10
|
+
* Search order (first match wins):
|
|
11
|
+
* 1. .pi/templates/ (project-level)
|
|
12
|
+
* 2. ~/.pi/agent/templates/ (user-level)
|
|
13
|
+
* 3. <package>/templates/ (builtin)
|
|
14
|
+
*
|
|
15
|
+
* Autoescape is disabled — we're rendering markdown, not HTML.
|
|
16
|
+
*
|
|
17
|
+
* @param cwd - project root directory
|
|
18
|
+
* @param builtinDir - optional path to builtin templates (defaults to templates/ relative to package root)
|
|
19
|
+
*/
|
|
20
|
+
export declare function createTemplateEnv(cwd: string, builtinDir?: string): nunjucks.Environment;
|
|
21
|
+
/**
|
|
22
|
+
* Render a template string through Nunjucks.
|
|
23
|
+
*
|
|
24
|
+
* Used for agent system prompts (the markdown body after frontmatter).
|
|
25
|
+
* The context object contains the step's resolved input fields as top-level keys.
|
|
26
|
+
*
|
|
27
|
+
* Workflow expressions (${{ }}) are protected from Nunjucks interpretation
|
|
28
|
+
* by escaping them before rendering and restoring them after.
|
|
29
|
+
*
|
|
30
|
+
* @param env - Nunjucks environment (from createTemplateEnv)
|
|
31
|
+
* @param templateStr - the template text to render
|
|
32
|
+
* @param context - variables available in the template
|
|
33
|
+
* @returns rendered string
|
|
34
|
+
*/
|
|
35
|
+
export declare function renderTemplate(env: nunjucks.Environment, templateStr: string, context: Record<string, unknown>): string;
|
|
36
|
+
/**
|
|
37
|
+
* Render a named template file through Nunjucks.
|
|
38
|
+
*
|
|
39
|
+
* Used when an agent spec references a template by path:
|
|
40
|
+
* prompt:
|
|
41
|
+
* system: analyzers/base-analyzer.md
|
|
42
|
+
*
|
|
43
|
+
* The file is resolved through the three-tier search (project > user > builtin).
|
|
44
|
+
*
|
|
45
|
+
* @param env - Nunjucks environment
|
|
46
|
+
* @param templateName - relative path to the template file
|
|
47
|
+
* @param context - variables available in the template
|
|
48
|
+
* @returns rendered string
|
|
49
|
+
*/
|
|
50
|
+
export declare function renderTemplateFile(env: nunjucks.Environment, templateName: string, context: Record<string, unknown>): string;
|
|
51
|
+
//# sourceMappingURL=template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAmBxF;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC7B,GAAG,EAAE,QAAQ,CAAC,WAAW,EACzB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAKR;AAOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CACjC,GAAG,EAAE,QAAQ,CAAC,WAAW,EACzB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAER"}
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* Provides three-tier template resolution (project > user > builtin)
|
|
4
4
|
* and renders agent system prompts with step input as context.
|
|
5
5
|
*/
|
|
6
|
-
import nunjucks from "nunjucks";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import os from "node:os";
|
|
9
6
|
import fs from "node:fs";
|
|
10
|
-
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import nunjucks from "nunjucks";
|
|
11
10
|
/**
|
|
12
11
|
* Create a Nunjucks environment with three-tier template resolution.
|
|
13
12
|
*
|
|
@@ -21,32 +20,28 @@ import fs from "node:fs";
|
|
|
21
20
|
* @param cwd - project root directory
|
|
22
21
|
* @param builtinDir - optional path to builtin templates (defaults to templates/ relative to package root)
|
|
23
22
|
*/
|
|
24
|
-
export function createTemplateEnv(cwd
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
? new nunjucks.FileSystemLoader(searchPaths)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
throwOnUndefined: false,
|
|
44
|
-
});
|
|
23
|
+
export function createTemplateEnv(cwd, builtinDir) {
|
|
24
|
+
const projectDir = path.join(cwd, ".pi", "templates");
|
|
25
|
+
const userDir = path.join(os.homedir(), ".pi", "agent", "templates");
|
|
26
|
+
const defaultBuiltinDir = builtinDir ?? path.resolve(import.meta.dirname, "..", "templates");
|
|
27
|
+
// Nunjucks FileSystemLoader searches directories in order — first match wins.
|
|
28
|
+
// Only include directories that exist to avoid noisy warnings.
|
|
29
|
+
const searchPaths = [];
|
|
30
|
+
if (fs.existsSync(projectDir))
|
|
31
|
+
searchPaths.push(projectDir);
|
|
32
|
+
if (fs.existsSync(userDir))
|
|
33
|
+
searchPaths.push(userDir);
|
|
34
|
+
if (fs.existsSync(defaultBuiltinDir))
|
|
35
|
+
searchPaths.push(defaultBuiltinDir);
|
|
36
|
+
// If no template directories exist, use a no-op loader — plain text passes through.
|
|
37
|
+
const loader = searchPaths.length > 0 ? new nunjucks.FileSystemLoader(searchPaths) : undefined;
|
|
38
|
+
return new nunjucks.Environment(loader, {
|
|
39
|
+
autoescape: false,
|
|
40
|
+
throwOnUndefined: false,
|
|
41
|
+
});
|
|
45
42
|
}
|
|
46
|
-
|
|
47
43
|
/** Sentinel used to protect ${{ }} workflow expressions from Nunjucks rendering. */
|
|
48
44
|
const WORKFLOW_EXPR_PLACEHOLDER = "\x00__PI_WORKFLOW_EXPR__";
|
|
49
|
-
|
|
50
45
|
/**
|
|
51
46
|
* Render a template string through Nunjucks.
|
|
52
47
|
*
|
|
@@ -61,18 +56,16 @@ const WORKFLOW_EXPR_PLACEHOLDER = "\x00__PI_WORKFLOW_EXPR__";
|
|
|
61
56
|
* @param context - variables available in the template
|
|
62
57
|
* @returns rendered string
|
|
63
58
|
*/
|
|
64
|
-
export function renderTemplate(env
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
export function renderTemplate(env, templateStr, context) {
|
|
60
|
+
// Protect ${{ }} workflow expressions from Nunjucks
|
|
61
|
+
const escaped = templateStr.replace(/\$\{\{/g, WORKFLOW_EXPR_PLACEHOLDER);
|
|
62
|
+
const rendered = env.renderString(escaped, context);
|
|
63
|
+
return rendered.replace(new RegExp(escapeRegExp(WORKFLOW_EXPR_PLACEHOLDER), "g"), "${{");
|
|
69
64
|
}
|
|
70
|
-
|
|
71
65
|
/** Escape special regex characters in a string. */
|
|
72
|
-
function escapeRegExp(s
|
|
73
|
-
|
|
66
|
+
function escapeRegExp(s) {
|
|
67
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
74
68
|
}
|
|
75
|
-
|
|
76
69
|
/**
|
|
77
70
|
* Render a named template file through Nunjucks.
|
|
78
71
|
*
|
|
@@ -87,6 +80,7 @@ function escapeRegExp(s: string): string {
|
|
|
87
80
|
* @param context - variables available in the template
|
|
88
81
|
* @returns rendered string
|
|
89
82
|
*/
|
|
90
|
-
export function renderTemplateFile(env
|
|
91
|
-
|
|
83
|
+
export function renderTemplateFile(env, templateName, context) {
|
|
84
|
+
return env.render(templateName, context);
|
|
92
85
|
}
|
|
86
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,UAAmB;IACjE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IACrE,MAAM,iBAAiB,GAAG,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAE7F,8EAA8E;IAC9E,+DAA+D;IAC/D,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5D,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,EAAE,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAAE,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAE1E,oFAAoF;IACpF,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE/F,OAAO,IAAI,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE;QACvC,UAAU,EAAE,KAAK;QACjB,gBAAgB,EAAE,KAAK;KACvB,CAAC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAC7B,GAAyB,EACzB,WAAmB,EACnB,OAAgC;IAEhC,oDAAoD;IACpD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACpD,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC,yBAAyB,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;AAC1F,CAAC;AAED,mDAAmD;AACnD,SAAS,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CACjC,GAAyB,EACzB,YAAoB,EACpB,OAAgC;IAEhC,OAAO,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { StepSpec, WorkflowSpec } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create a mock extension context for testing.
|
|
4
|
+
*/
|
|
5
|
+
export declare function mockCtx(cwd: string): any;
|
|
6
|
+
/**
|
|
7
|
+
* Create a mock pi API for testing.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mockPi(): any;
|
|
10
|
+
/**
|
|
11
|
+
* Create a minimal WorkflowSpec for testing.
|
|
12
|
+
* A fresh temp directory is created for filePath.
|
|
13
|
+
*/
|
|
14
|
+
export declare function makeSpec(overrides: Partial<WorkflowSpec> & {
|
|
15
|
+
steps: Record<string, StepSpec>;
|
|
16
|
+
}): WorkflowSpec;
|
|
17
|
+
//# sourceMappingURL=test-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzD;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAS7B,GAAG,CACR;AAED;;GAEG;AACH,wBAAgB,MAAM,IAKhB,GAAG,CACR;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAAE,GAAG,YAAY,CAU7G"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test helpers — mock factories for ctx, pi, and workflow specs.
|
|
3
|
+
*/
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
/**
|
|
8
|
+
* Create a mock extension context for testing.
|
|
9
|
+
*/
|
|
10
|
+
export function mockCtx(cwd) {
|
|
11
|
+
return {
|
|
12
|
+
cwd,
|
|
13
|
+
hasUI: false,
|
|
14
|
+
ui: {
|
|
15
|
+
setWidget: () => { },
|
|
16
|
+
notify: () => { },
|
|
17
|
+
setStatus: () => { },
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a mock pi API for testing.
|
|
23
|
+
*/
|
|
24
|
+
export function mockPi() {
|
|
25
|
+
const messages = [];
|
|
26
|
+
return {
|
|
27
|
+
sendMessage: (msg, opts) => messages.push({ msg, opts }),
|
|
28
|
+
_messages: messages,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a minimal WorkflowSpec for testing.
|
|
33
|
+
* A fresh temp directory is created for filePath.
|
|
34
|
+
*/
|
|
35
|
+
export function makeSpec(overrides) {
|
|
36
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-test-"));
|
|
37
|
+
return {
|
|
38
|
+
name: "test-workflow",
|
|
39
|
+
description: "Test workflow",
|
|
40
|
+
version: "1",
|
|
41
|
+
source: "project",
|
|
42
|
+
filePath: path.join(tmpDir, "test.workflow.yaml"),
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=test-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.js","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW;IAClC,OAAO;QACN,GAAG;QACH,KAAK,EAAE,KAAK;QACZ,EAAE,EAAE;YACH,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC;YACnB,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;YAChB,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC;SACnB;KACM,CAAC;AACV,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM;IACrB,MAAM,QAAQ,GAAU,EAAE,CAAC;IAC3B,OAAO;QACN,WAAW,EAAE,CAAC,GAAQ,EAAE,IAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QAClE,SAAS,EAAE,QAAQ;KACZ,CAAC;AACV,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAsE;IAC9F,MAAM,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;IAClE,OAAO;QACN,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,eAAe;QAC5B,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,SAAkB;QAC1B,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC;QACjD,GAAG,SAAS;KACZ,CAAC;AACH,CAAC"}
|
package/dist/tui.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI progress widget for workflow execution.
|
|
3
|
+
* Shows step status, timing, cost, and parallel execution indicators.
|
|
4
|
+
*/
|
|
5
|
+
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import type { Component, TUI } from "@mariozechner/pi-tui";
|
|
7
|
+
import type { ExecutionState, WorkflowSpec } from "./types.js";
|
|
8
|
+
export interface StepActivity {
|
|
9
|
+
tool: string;
|
|
10
|
+
preview: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
export interface StepOutputSummary {
|
|
14
|
+
tasks?: Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
status: string;
|
|
17
|
+
files?: string[];
|
|
18
|
+
}>;
|
|
19
|
+
testCount?: number;
|
|
20
|
+
note?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ProgressWidgetState {
|
|
23
|
+
spec: WorkflowSpec;
|
|
24
|
+
state: ExecutionState;
|
|
25
|
+
currentStep?: string;
|
|
26
|
+
startTime: number;
|
|
27
|
+
parallelSubSteps?: Record<string, import("./types.js").StepResult>;
|
|
28
|
+
resumedSteps?: number;
|
|
29
|
+
activities: Map<string, StepActivity[]>;
|
|
30
|
+
outputSummaries: Map<string, StepOutputSummary>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a widget factory for ctx.ui.setWidget().
|
|
34
|
+
* Returns a function that pi calls to get the component.
|
|
35
|
+
*
|
|
36
|
+
* The returned component renders a compact progress view:
|
|
37
|
+
* ─────────────────────────────────────
|
|
38
|
+
* ● bugfix step 2/3 1m32s
|
|
39
|
+
* ✓ diagnose 42s $0.03 12k tok
|
|
40
|
+
* ▸ fix 50s 8k tok...
|
|
41
|
+
* · verify
|
|
42
|
+
* ─────────────────────────────────────
|
|
43
|
+
*/
|
|
44
|
+
export declare function createProgressWidget(widgetState: ProgressWidgetState): (tui: TUI, theme: Theme) => Component & {
|
|
45
|
+
dispose?(): void;
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=tui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAc,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,cAAc,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,YAAY,EAAE,UAAU,CAAC,CAAC;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IACxC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CACnC,WAAW,EAAE,mBAAmB,GAC9B,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,KAAK,SAAS,GAAG;IAAE,OAAO,CAAC,IAAI,IAAI,CAAA;CAAE,CAuI9D"}
|
package/dist/tui.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { formatCost, formatDuration, formatTokens } from "./format.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create a widget factory for ctx.ui.setWidget().
|
|
4
|
+
* Returns a function that pi calls to get the component.
|
|
5
|
+
*
|
|
6
|
+
* The returned component renders a compact progress view:
|
|
7
|
+
* ─────────────────────────────────────
|
|
8
|
+
* ● bugfix step 2/3 1m32s
|
|
9
|
+
* ✓ diagnose 42s $0.03 12k tok
|
|
10
|
+
* ▸ fix 50s 8k tok...
|
|
11
|
+
* · verify
|
|
12
|
+
* ─────────────────────────────────────
|
|
13
|
+
*/
|
|
14
|
+
export function createProgressWidget(widgetState) {
|
|
15
|
+
return (tui, theme) => {
|
|
16
|
+
/** Pulse interval (ms) for the elapsed-time ticker. Balances update frequency vs overhead. */
|
|
17
|
+
const PULSE_INTERVAL_MS = 800;
|
|
18
|
+
let pulseOn = true;
|
|
19
|
+
const interval = setInterval(() => {
|
|
20
|
+
pulseOn = !pulseOn;
|
|
21
|
+
tui.requestRender();
|
|
22
|
+
}, PULSE_INTERVAL_MS);
|
|
23
|
+
return {
|
|
24
|
+
render(width) {
|
|
25
|
+
const lines = [];
|
|
26
|
+
const stepNames = Object.keys(widgetState.spec.steps);
|
|
27
|
+
const totalSteps = stepNames.length;
|
|
28
|
+
// Parse current steps (may be comma-separated for parallel)
|
|
29
|
+
const currentSteps = widgetState.currentStep?.split(", ") ?? [];
|
|
30
|
+
const parallelCount = currentSteps.length;
|
|
31
|
+
// Determine current step number
|
|
32
|
+
let currentStepNum = 0;
|
|
33
|
+
if (currentSteps.length > 0 && currentSteps[0]) {
|
|
34
|
+
const idx = stepNames.indexOf(currentSteps[0]);
|
|
35
|
+
currentStepNum = idx >= 0 ? idx + 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Count completed steps
|
|
39
|
+
currentStepNum = Object.values(widgetState.state.steps).filter((s) => s.status === "completed").length;
|
|
40
|
+
}
|
|
41
|
+
const elapsed = formatDuration(Date.now() - widgetState.startTime);
|
|
42
|
+
const workflowName = theme.bold(widgetState.spec.name);
|
|
43
|
+
const indicator = pulseOn ? theme.fg("accent", "\u25cf") : theme.fg("dim", "\u25cf");
|
|
44
|
+
const parallelTag = parallelCount > 1 ? ` [${parallelCount} parallel]` : "";
|
|
45
|
+
const headerLine = `${indicator} ${workflowName} step ${currentStepNum}/${totalSteps}${parallelTag} ${theme.fg("dim", elapsed)}`;
|
|
46
|
+
lines.push(headerLine.length > width ? headerLine.slice(0, width) : headerLine);
|
|
47
|
+
// Resumed indicator
|
|
48
|
+
if (widgetState.resumedSteps) {
|
|
49
|
+
const resumedLine = ` ${theme.fg("dim", "\u21bb")} Resumed: ${widgetState.resumedSteps} steps from prior run`;
|
|
50
|
+
lines.push(resumedLine.length > width ? resumedLine.slice(0, width) : resumedLine);
|
|
51
|
+
}
|
|
52
|
+
// Paused indicator
|
|
53
|
+
if (widgetState.state.status === "paused") {
|
|
54
|
+
const pausedLine = ` ${theme.fg("accent", "\u23f8")} Paused`;
|
|
55
|
+
lines.push(pausedLine.length > width ? pausedLine.slice(0, width) : pausedLine);
|
|
56
|
+
}
|
|
57
|
+
// Step lines
|
|
58
|
+
for (const stepName of stepNames) {
|
|
59
|
+
const stepResult = widgetState.state.steps[stepName];
|
|
60
|
+
let line;
|
|
61
|
+
if (stepResult && stepResult.status === "skipped") {
|
|
62
|
+
// Skipped step: ⊘ stepName [skipped]
|
|
63
|
+
line = ` ${theme.fg("dim", "\u2298")} ${stepName} ${theme.fg("dim", "[skipped]")}`;
|
|
64
|
+
}
|
|
65
|
+
else if (stepResult && stepResult.status === "completed") {
|
|
66
|
+
const dur = formatDuration(stepResult.durationMs);
|
|
67
|
+
const cost = formatCost(stepResult.usage.cost);
|
|
68
|
+
const tok = formatTokens(stepResult.usage.input + stepResult.usage.output);
|
|
69
|
+
// Show step type indicator for gate/transform
|
|
70
|
+
const typeTag = stepResult.agent === "gate" ? " [gate]" : stepResult.agent === "transform" ? " [transform]" : "";
|
|
71
|
+
const truncTag = stepResult.truncated ? ` ${theme.fg("warning", "[truncated]")}` : "";
|
|
72
|
+
line = ` ${theme.fg("success", "\u2713")} ${stepName}${typeTag} ${theme.fg("dim", dur)} ${theme.fg("dim", cost)} ${theme.fg("dim", tok)}${truncTag}`;
|
|
73
|
+
lines.push(line.length > width ? line.slice(0, width) : line);
|
|
74
|
+
// Render output summary sub-lines (capped at 3)
|
|
75
|
+
const summary = widgetState.outputSummaries?.get(stepName);
|
|
76
|
+
if (summary) {
|
|
77
|
+
let summaryLines = 0;
|
|
78
|
+
const MAX_SUMMARY_LINES = 3;
|
|
79
|
+
if (summary.tasks) {
|
|
80
|
+
for (const task of summary.tasks) {
|
|
81
|
+
if (summaryLines >= MAX_SUMMARY_LINES)
|
|
82
|
+
break;
|
|
83
|
+
const files = task.files?.join(", ") || "";
|
|
84
|
+
const statusIcon = task.status === "done" ? "\u2713" : task.status === "failed" ? "\u2717" : "\u00b7";
|
|
85
|
+
const taskLine = ` ${theme.fg("dim", statusIcon)} ${theme.fg("dim", task.name)}${files ? " " + theme.fg("dim", files) : ""}`;
|
|
86
|
+
lines.push(taskLine.length > width ? taskLine.slice(0, width) : taskLine);
|
|
87
|
+
summaryLines++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (summary.testCount && summaryLines < MAX_SUMMARY_LINES) {
|
|
91
|
+
const testLine = ` ${theme.fg("dim", `${summary.testCount} tests pass`)}`;
|
|
92
|
+
lines.push(testLine.length > width ? testLine.slice(0, width) : testLine);
|
|
93
|
+
summaryLines++;
|
|
94
|
+
}
|
|
95
|
+
if (summary.note && summaryLines < MAX_SUMMARY_LINES) {
|
|
96
|
+
const noteLine = ` ${theme.fg("dim", summary.note)}`;
|
|
97
|
+
lines.push(noteLine.length > width ? noteLine.slice(0, width) : noteLine);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
continue; // skip the push below, already pushed
|
|
101
|
+
}
|
|
102
|
+
else if (stepResult && stepResult.status === "failed") {
|
|
103
|
+
const dur = formatDuration(stepResult.durationMs);
|
|
104
|
+
const errorPreview = stepResult.error || "Unknown error";
|
|
105
|
+
line = ` ${theme.fg("error", "\u2717")} ${stepName} ${theme.fg("dim", dur)} ${errorPreview}`;
|
|
106
|
+
}
|
|
107
|
+
else if (stepResult && stepResult.agent === "parallel") {
|
|
108
|
+
// Completed parallel step — show with sub-step count
|
|
109
|
+
const dur = formatDuration(stepResult.durationMs);
|
|
110
|
+
const cost = formatCost(stepResult.usage.cost);
|
|
111
|
+
const tok = formatTokens(stepResult.usage.input + stepResult.usage.output);
|
|
112
|
+
line = ` ${theme.fg("success", "\u2713")} ${stepName} [parallel] ${theme.fg("dim", dur)} ${theme.fg("dim", cost)} ${theme.fg("dim", tok)}`;
|
|
113
|
+
}
|
|
114
|
+
else if (currentSteps.includes(stepName)) {
|
|
115
|
+
const stepElapsed = formatDuration(Date.now() - widgetState.startTime);
|
|
116
|
+
line = ` ${theme.fg("accent", "\u25b8")} ${theme.fg("accent", stepName)} ${theme.fg("dim", stepElapsed + "...")}`;
|
|
117
|
+
lines.push(line.length > width ? line.slice(0, width) : line);
|
|
118
|
+
// Render most recent tool activity under running step
|
|
119
|
+
const activities = widgetState.activities?.get(stepName);
|
|
120
|
+
if (activities && activities.length > 0) {
|
|
121
|
+
const latest = activities[activities.length - 1];
|
|
122
|
+
const actLine = ` ${theme.fg("dim", latest.tool)} ${theme.fg("dim", latest.preview)}`;
|
|
123
|
+
lines.push(actLine.length > width ? actLine.slice(0, width) : actLine);
|
|
124
|
+
}
|
|
125
|
+
continue; // skip the push below, already pushed
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
line = ` ${theme.fg("dim", "\u00b7")} ${stepName}`;
|
|
129
|
+
}
|
|
130
|
+
lines.push(line.length > width ? line.slice(0, width) : line);
|
|
131
|
+
}
|
|
132
|
+
return lines;
|
|
133
|
+
},
|
|
134
|
+
invalidate() {
|
|
135
|
+
/* no cached state to clear */
|
|
136
|
+
},
|
|
137
|
+
dispose() {
|
|
138
|
+
clearInterval(interval);
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=tui.js.map
|