@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.
Files changed (216) hide show
  1. package/CHANGELOG.md +7 -4
  2. package/README.md +12 -2
  3. package/agents/architecture-designer.agent.yaml +58 -0
  4. package/agents/architecture-inferrer.agent.yaml +60 -0
  5. package/agents/gap-identifier.agent.yaml +66 -0
  6. package/agents/handoff-writer.agent.yaml +64 -0
  7. package/agents/plan-creator.agent.yaml +63 -0
  8. package/agents/project-definer.agent.yaml +53 -0
  9. package/agents/project-inferrer.agent.yaml +63 -0
  10. package/agents/requirements-gatherer.agent.yaml +58 -0
  11. package/dist/agent-spec.d.ts +27 -0
  12. package/dist/agent-spec.d.ts.map +1 -0
  13. package/dist/agent-spec.js +112 -0
  14. package/dist/agent-spec.js.map +1 -0
  15. package/dist/checkpoint.d.ts +50 -0
  16. package/dist/checkpoint.d.ts.map +1 -0
  17. package/dist/checkpoint.js +109 -0
  18. package/dist/checkpoint.js.map +1 -0
  19. package/dist/completion.d.ts +18 -0
  20. package/dist/completion.d.ts.map +1 -0
  21. package/dist/completion.js +58 -0
  22. package/dist/completion.js.map +1 -0
  23. package/dist/dag.d.ts +56 -0
  24. package/dist/dag.d.ts.map +1 -0
  25. package/dist/dag.js +178 -0
  26. package/dist/dag.js.map +1 -0
  27. package/dist/dispatch.d.ts +35 -0
  28. package/dist/dispatch.d.ts.map +1 -0
  29. package/dist/dispatch.js +311 -0
  30. package/dist/dispatch.js.map +1 -0
  31. package/dist/expression.d.ts +64 -0
  32. package/dist/expression.d.ts.map +1 -0
  33. package/dist/expression.js +294 -0
  34. package/dist/expression.js.map +1 -0
  35. package/dist/format.d.ts +19 -0
  36. package/dist/format.d.ts.map +1 -0
  37. package/dist/format.js +42 -0
  38. package/dist/format.js.map +1 -0
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +724 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/output.d.ts +12 -0
  44. package/dist/output.d.ts.map +1 -0
  45. package/dist/output.js +59 -0
  46. package/dist/output.js.map +1 -0
  47. package/dist/state.d.ts +66 -0
  48. package/dist/state.d.ts.map +1 -0
  49. package/dist/state.js +227 -0
  50. package/dist/state.js.map +1 -0
  51. package/dist/step-agent.d.ts +35 -0
  52. package/dist/step-agent.d.ts.map +1 -0
  53. package/dist/step-agent.js +171 -0
  54. package/dist/step-agent.js.map +1 -0
  55. package/dist/step-command.d.ts +21 -0
  56. package/dist/step-command.d.ts.map +1 -0
  57. package/dist/step-command.js +150 -0
  58. package/dist/step-command.js.map +1 -0
  59. package/dist/step-foreach.d.ts +33 -0
  60. package/dist/step-foreach.d.ts.map +1 -0
  61. package/dist/step-foreach.js +112 -0
  62. package/dist/step-foreach.js.map +1 -0
  63. package/dist/step-gate.d.ts +18 -0
  64. package/dist/step-gate.d.ts.map +1 -0
  65. package/dist/step-gate.js +137 -0
  66. package/dist/step-gate.js.map +1 -0
  67. package/dist/step-loop.d.ts +32 -0
  68. package/dist/step-loop.d.ts.map +1 -0
  69. package/dist/step-loop.js +278 -0
  70. package/dist/step-loop.js.map +1 -0
  71. package/dist/step-monitor.d.ts +56 -0
  72. package/dist/step-monitor.d.ts.map +1 -0
  73. package/dist/step-monitor.js +317 -0
  74. package/dist/step-monitor.js.map +1 -0
  75. package/dist/step-parallel.d.ts +43 -0
  76. package/dist/step-parallel.d.ts.map +1 -0
  77. package/dist/step-parallel.js +122 -0
  78. package/dist/step-parallel.js.map +1 -0
  79. package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
  80. package/dist/step-pause.d.ts.map +1 -0
  81. package/dist/step-pause.js +22 -0
  82. package/dist/step-pause.js.map +1 -0
  83. package/dist/step-shared.d.ts +63 -0
  84. package/dist/step-shared.d.ts.map +1 -0
  85. package/dist/step-shared.js +136 -0
  86. package/dist/step-shared.js.map +1 -0
  87. package/dist/step-transform.d.ts +11 -0
  88. package/dist/step-transform.d.ts.map +1 -0
  89. package/dist/step-transform.js +41 -0
  90. package/dist/step-transform.js.map +1 -0
  91. package/dist/template.d.ts +51 -0
  92. package/dist/template.d.ts.map +1 -0
  93. package/{src/template.ts → dist/template.js} +32 -38
  94. package/dist/template.js.map +1 -0
  95. package/dist/test-helpers.d.ts +17 -0
  96. package/dist/test-helpers.d.ts.map +1 -0
  97. package/dist/test-helpers.js +46 -0
  98. package/dist/test-helpers.js.map +1 -0
  99. package/dist/tui.d.ts +47 -0
  100. package/dist/tui.d.ts.map +1 -0
  101. package/dist/tui.js +143 -0
  102. package/dist/tui.js.map +1 -0
  103. package/dist/types.d.ts +165 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +3 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/workflow-discovery.d.ts +23 -0
  108. package/dist/workflow-discovery.d.ts.map +1 -0
  109. package/dist/workflow-discovery.js +103 -0
  110. package/dist/workflow-discovery.js.map +1 -0
  111. package/dist/workflow-executor.d.ts +50 -0
  112. package/dist/workflow-executor.d.ts.map +1 -0
  113. package/dist/workflow-executor.js +738 -0
  114. package/dist/workflow-executor.js.map +1 -0
  115. package/dist/workflow-sdk.d.ts +44 -0
  116. package/dist/workflow-sdk.d.ts.map +1 -0
  117. package/dist/workflow-sdk.js +457 -0
  118. package/dist/workflow-sdk.js.map +1 -0
  119. package/dist/workflow-spec.d.ts +33 -0
  120. package/dist/workflow-spec.d.ts.map +1 -0
  121. package/dist/workflow-spec.js +476 -0
  122. package/dist/workflow-spec.js.map +1 -0
  123. package/dist/workflows-dir.d.ts +3 -0
  124. package/dist/workflows-dir.d.ts.map +1 -0
  125. package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
  126. package/dist/workflows-dir.js.map +1 -0
  127. package/package.json +53 -38
  128. package/schemas/decomposition-specs.schema.json +48 -48
  129. package/schemas/execution-results.schema.json +48 -48
  130. package/schemas/investigation-findings.schema.json +43 -43
  131. package/schemas/pattern-analysis.schema.json +41 -41
  132. package/schemas/phase.schema.json +48 -0
  133. package/schemas/plan-breakdown.schema.json +20 -20
  134. package/schemas/quality-analysis.schema.json +33 -33
  135. package/schemas/research-findings.schema.json +42 -42
  136. package/schemas/structure-analysis.schema.json +40 -40
  137. package/schemas/synthesis.schema.json +31 -31
  138. package/schemas/verifier-output.schema.json +94 -94
  139. package/skill-narrative.md +107 -0
  140. package/skills/pi-workflows/SKILL.md +185 -0
  141. package/skills/pi-workflows/references/bundled-resources.md +87 -0
  142. package/templates/architecture-designer/task.md +117 -0
  143. package/templates/architecture-inferrer/task.md +61 -0
  144. package/templates/gap-identifier/task.md +103 -0
  145. package/templates/handoff-writer/task.md +91 -0
  146. package/templates/plan-creator/task.md +143 -0
  147. package/templates/project-definer/task.md +67 -0
  148. package/templates/project-inferrer/task.md +56 -0
  149. package/templates/requirements-gatherer/task.md +90 -0
  150. package/workflows/analyze-existing-project.workflow.yaml +94 -0
  151. package/workflows/create-handoff.workflow.yaml +66 -0
  152. package/workflows/create-phase.workflow.yaml +1 -1
  153. package/workflows/do-gap.workflow.yaml +4 -4
  154. package/workflows/fix-audit.workflow.yaml +1 -1
  155. package/workflows/gap-to-phase.workflow.yaml +1 -1
  156. package/workflows/init-new-project.workflow.yaml +81 -0
  157. package/workflows/plan-from-requirements.workflow.yaml +59 -0
  158. package/workflows/self-implement.workflow.yaml +3 -3
  159. package/workflows/typed-analysis.workflow.yaml +4 -4
  160. package/src/agent-spec.test.ts +0 -289
  161. package/src/agent-spec.ts +0 -122
  162. package/src/block-validation.test.ts +0 -350
  163. package/src/checkpoint.test.ts +0 -237
  164. package/src/checkpoint.ts +0 -140
  165. package/src/completion.test.ts +0 -143
  166. package/src/completion.ts +0 -68
  167. package/src/dag.test.ts +0 -350
  168. package/src/dag.ts +0 -219
  169. package/src/dispatch.test.ts +0 -475
  170. package/src/dispatch.ts +0 -353
  171. package/src/expression.test.ts +0 -353
  172. package/src/expression.ts +0 -332
  173. package/src/format.test.ts +0 -80
  174. package/src/format.ts +0 -41
  175. package/src/graduated-failure.test.ts +0 -556
  176. package/src/index.test.ts +0 -114
  177. package/src/index.ts +0 -516
  178. package/src/loop.test.ts +0 -551
  179. package/src/output.test.ts +0 -213
  180. package/src/output.ts +0 -70
  181. package/src/parallel-integration.test.ts +0 -175
  182. package/src/resume.test.ts +0 -192
  183. package/src/state.test.ts +0 -192
  184. package/src/state.ts +0 -254
  185. package/src/step-agent.test.ts +0 -327
  186. package/src/step-agent.ts +0 -178
  187. package/src/step-command.test.ts +0 -330
  188. package/src/step-command.ts +0 -132
  189. package/src/step-foreach.test.ts +0 -647
  190. package/src/step-foreach.ts +0 -148
  191. package/src/step-gate.test.ts +0 -185
  192. package/src/step-gate.ts +0 -116
  193. package/src/step-loop.test.ts +0 -626
  194. package/src/step-loop.ts +0 -323
  195. package/src/step-parallel.test.ts +0 -475
  196. package/src/step-parallel.ts +0 -168
  197. package/src/step-pause.test.ts +0 -30
  198. package/src/step-shared.test.ts +0 -355
  199. package/src/step-shared.ts +0 -157
  200. package/src/step-transform.test.ts +0 -155
  201. package/src/step-transform.ts +0 -47
  202. package/src/template-integration.test.ts +0 -72
  203. package/src/template.test.ts +0 -162
  204. package/src/test-helpers.ts +0 -51
  205. package/src/tui.test.ts +0 -355
  206. package/src/tui.ts +0 -182
  207. package/src/types.ts +0 -195
  208. package/src/verifier-schema.test.ts +0 -226
  209. package/src/workflow-discovery.test.ts +0 -105
  210. package/src/workflow-discovery.ts +0 -114
  211. package/src/workflow-executor.test.ts +0 -1532
  212. package/src/workflow-executor.ts +0 -810
  213. package/src/workflow-sdk.test.ts +0 -238
  214. package/src/workflow-sdk.ts +0 -262
  215. package/src/workflow-spec.test.ts +0 -576
  216. 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: string, builtinDir?: string): nunjucks.Environment {
25
- const projectDir = path.join(cwd, ".pi", "templates");
26
- const userDir = path.join(os.homedir(), ".pi", "agent", "templates");
27
- const defaultBuiltinDir = builtinDir ?? path.resolve(import.meta.dirname, "..", "templates");
28
-
29
- // Nunjucks FileSystemLoader searches directories in order first match wins.
30
- // Only include directories that exist to avoid noisy warnings.
31
- const searchPaths: string[] = [];
32
- if (fs.existsSync(projectDir)) searchPaths.push(projectDir);
33
- if (fs.existsSync(userDir)) searchPaths.push(userDir);
34
- if (fs.existsSync(defaultBuiltinDir)) searchPaths.push(defaultBuiltinDir);
35
-
36
- // If no template directories exist, use a no-op loader — plain text passes through.
37
- const loader = searchPaths.length > 0
38
- ? new nunjucks.FileSystemLoader(searchPaths)
39
- : undefined;
40
-
41
- return new nunjucks.Environment(loader, {
42
- autoescape: false,
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: nunjucks.Environment, templateStr: string, context: Record<string, unknown>): string {
65
- // Protect ${{ }} workflow expressions from Nunjucks
66
- const escaped = templateStr.replace(/\$\{\{/g, WORKFLOW_EXPR_PLACEHOLDER);
67
- const rendered = env.renderString(escaped, context);
68
- return rendered.replace(new RegExp(escapeRegExp(WORKFLOW_EXPR_PLACEHOLDER), "g"), "${{");
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: string): string {
73
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
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: nunjucks.Environment, templateName: string, context: Record<string, unknown>): string {
91
- return env.render(templateName, context);
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