@osolmaz/pi-workflows 0.1.0

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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/dist/extension/executor.d.ts +58 -0
  4. package/dist/extension/executor.js +201 -0
  5. package/dist/extension/executor.js.map +1 -0
  6. package/dist/extension/index.d.ts +17 -0
  7. package/dist/extension/index.js +504 -0
  8. package/dist/extension/index.js.map +1 -0
  9. package/dist/extension/widget.d.ts +21 -0
  10. package/dist/extension/widget.js +142 -0
  11. package/dist/extension/widget.js.map +1 -0
  12. package/dist/render/ansi.d.ts +16 -0
  13. package/dist/render/ansi.js +42 -0
  14. package/dist/render/ansi.js.map +1 -0
  15. package/dist/render/canvas.d.ts +40 -0
  16. package/dist/render/canvas.js +177 -0
  17. package/dist/render/canvas.js.map +1 -0
  18. package/dist/render/format.d.ts +3 -0
  19. package/dist/render/format.js +17 -0
  20. package/dist/render/format.js.map +1 -0
  21. package/dist/render/graph-render.d.ts +22 -0
  22. package/dist/render/graph-render.js +520 -0
  23. package/dist/render/graph-render.js.map +1 -0
  24. package/dist/render/graph.d.ts +46 -0
  25. package/dist/render/graph.js +272 -0
  26. package/dist/render/graph.js.map +1 -0
  27. package/dist/viewer/cli.d.ts +10 -0
  28. package/dist/viewer/cli.js +132 -0
  29. package/dist/viewer/cli.js.map +1 -0
  30. package/dist/viewer/render.d.ts +19 -0
  31. package/dist/viewer/render.js +162 -0
  32. package/dist/viewer/render.js.map +1 -0
  33. package/dist/viewer/tui.d.ts +11 -0
  34. package/dist/viewer/tui.js +140 -0
  35. package/dist/viewer/tui.js.map +1 -0
  36. package/dist/viewer/watch.d.ts +9 -0
  37. package/dist/viewer/watch.js +46 -0
  38. package/dist/viewer/watch.js.map +1 -0
  39. package/dist/workflows/decision.d.ts +25 -0
  40. package/dist/workflows/decision.js +96 -0
  41. package/dist/workflows/decision.js.map +1 -0
  42. package/dist/workflows/definition.d.ts +9 -0
  43. package/dist/workflows/definition.js +61 -0
  44. package/dist/workflows/definition.js.map +1 -0
  45. package/dist/workflows/engine.d.ts +65 -0
  46. package/dist/workflows/engine.js +574 -0
  47. package/dist/workflows/engine.js.map +1 -0
  48. package/dist/workflows/errors.d.ts +9 -0
  49. package/dist/workflows/errors.js +24 -0
  50. package/dist/workflows/errors.js.map +1 -0
  51. package/dist/workflows/graph.d.ts +17 -0
  52. package/dist/workflows/graph.js +127 -0
  53. package/dist/workflows/graph.js.map +1 -0
  54. package/dist/workflows/index.d.ts +11 -0
  55. package/dist/workflows/index.js +11 -0
  56. package/dist/workflows/index.js.map +1 -0
  57. package/dist/workflows/json.d.ts +14 -0
  58. package/dist/workflows/json.js +134 -0
  59. package/dist/workflows/json.js.map +1 -0
  60. package/dist/workflows/loader.d.ts +28 -0
  61. package/dist/workflows/loader.js +94 -0
  62. package/dist/workflows/loader.js.map +1 -0
  63. package/dist/workflows/schema.d.ts +7 -0
  64. package/dist/workflows/schema.js +176 -0
  65. package/dist/workflows/schema.js.map +1 -0
  66. package/dist/workflows/shell.d.ts +9 -0
  67. package/dist/workflows/shell.js +177 -0
  68. package/dist/workflows/shell.js.map +1 -0
  69. package/dist/workflows/store.d.ts +35 -0
  70. package/dist/workflows/store.js +181 -0
  71. package/dist/workflows/store.js.map +1 -0
  72. package/dist/workflows/text.d.ts +10 -0
  73. package/dist/workflows/text.js +32 -0
  74. package/dist/workflows/text.js.map +1 -0
  75. package/dist/workflows/types.d.ts +280 -0
  76. package/dist/workflows/types.js +2 -0
  77. package/dist/workflows/types.js.map +1 -0
  78. package/docs/development.md +130 -0
  79. package/docs/run-bundles.md +114 -0
  80. package/docs/workflows.md +311 -0
  81. package/examples/workflows/autoimplement.workflow.ts +92 -0
  82. package/examples/workflows/autoresearch.workflow.ts +139 -0
  83. package/examples/workflows/branch.workflow.ts +63 -0
  84. package/examples/workflows/echo.workflow.ts +23 -0
  85. package/examples/workflows/elegant-solution.workflow.ts +95 -0
  86. package/examples/workflows/shell.workflow.ts +31 -0
  87. package/examples/workflows/two-turn.workflow.ts +64 -0
  88. package/package.json +80 -0
  89. package/src/extension/executor.ts +251 -0
  90. package/src/extension/index.ts +627 -0
  91. package/src/extension/widget.ts +183 -0
  92. package/src/render/ansi.ts +47 -0
  93. package/src/render/canvas.ts +196 -0
  94. package/src/render/format.ts +19 -0
  95. package/src/render/graph-render.ts +738 -0
  96. package/src/render/graph.ts +341 -0
  97. package/src/viewer/cli.ts +150 -0
  98. package/src/viewer/render.ts +236 -0
  99. package/src/viewer/tui.ts +159 -0
  100. package/src/viewer/watch.ts +55 -0
  101. package/src/workflows/decision.ts +127 -0
  102. package/src/workflows/definition.ts +104 -0
  103. package/src/workflows/engine.ts +793 -0
  104. package/src/workflows/errors.ts +27 -0
  105. package/src/workflows/graph.ts +161 -0
  106. package/src/workflows/index.ts +76 -0
  107. package/src/workflows/json.ts +155 -0
  108. package/src/workflows/loader.ts +123 -0
  109. package/src/workflows/schema.ts +218 -0
  110. package/src/workflows/shell.ts +199 -0
  111. package/src/workflows/store.ts +234 -0
  112. package/src/workflows/text.ts +34 -0
  113. package/src/workflows/types.ts +318 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Onur Solmaz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # pi-workflows
2
+
3
+ pi-workflows is a workflow extension for the [pi coding agent](https://pi.dev).
4
+ It lets you define multi-step agent workflows as TypeScript graphs, trigger
5
+ them at any point in a pi conversation with `/workflow`, and watch them run
6
+ live in a standalone terminal viewer.
7
+
8
+ The workflow model is a port of [openclaw/acpx](https://github.com/openclaw/acpx)
9
+ flows into pi itself. Agent steps run inside your current pi conversation, so
10
+ the model keeps everything it already knows from the discussion. The model
11
+ completes each step by calling a JSON `workflow` tool, which gives the engine
12
+ structured, validated output to route on.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pi install npm:@osolmaz/pi-workflows
18
+ ```
19
+
20
+ You can also install directly from GitHub:
21
+
22
+ ```bash
23
+ pi install git:github.com/osolmaz/pi-workflows
24
+ ```
25
+
26
+ Or try the npm package without installing it:
27
+
28
+ ```bash
29
+ pi -e npm:@osolmaz/pi-workflows
30
+ ```
31
+
32
+ The `pi-workflows` viewer binary is part of the same package. To get it on
33
+ your PATH, clone the repo and run `npm install && npm run build && npm link`,
34
+ or run it in place with `npx tsx src/viewer/cli.ts`.
35
+
36
+ ## Quick start
37
+
38
+ Put a workflow file in `.pi/workflows/` (project) or `~/.pi/agent/workflows/`
39
+ (global):
40
+
41
+ ```typescript
42
+ // .pi/workflows/echo.workflow.ts
43
+ import { agent, defineWorkflow } from "@osolmaz/pi-workflows";
44
+
45
+ export default defineWorkflow({
46
+ name: "echo",
47
+ presentationPrompt: "Give the user the concise reply from the workflow result.",
48
+ startAt: "reply",
49
+ nodes: {
50
+ reply: agent({
51
+ prompt: ({ input }) => `Answer concisely: ${(input as { task?: string }).task}`,
52
+ expectedOutput: `{ "reply": "your concise answer" }`,
53
+ }),
54
+ },
55
+ edges: [],
56
+ });
57
+ ```
58
+
59
+ Then, from any pi conversation:
60
+
61
+ ```
62
+ /workflow echo summarize this repository
63
+ ```
64
+
65
+ `/workflow` with no arguments lists discovered workflows. `/workflow pause`
66
+ lets the current step finish and then holds the run before the next node —
67
+ useful when you want to interject in the conversation mid-workflow —
68
+ and `/workflow resume` continues it. Pressing escape to interrupt a turn
69
+ pauses the workflow automatically, so the run never nudges the model while
70
+ you have taken the conversation back; `/workflow resume` re-delivers the
71
+ pending step prompt. `/workflow cancel` stops the active run; if the last run
72
+ already ended (for example parked at a checkpoint), it clears the leftover
73
+ widget instead. Trailing text becomes `{ task: "..." }`; pass arbitrary input
74
+ with `--input-json {"key": "value"}`. The names `cancel`, `list`, `pause`,
75
+ and `resume` are reserved and rejected as workflow names.
76
+
77
+ While a run is on screen, the footer status bar shows a compact
78
+ `wf <name> [status] <node>` indicator alongside the widget.
79
+
80
+ `presentationPrompt` is optional. When present, pi-workflows uses it after the
81
+ structured run ends to request one normal, human-readable assistant response.
82
+ Workflows without it remain silent after their final structured output, which
83
+ keeps shell-only and machine-consumed workflows model-free.
84
+
85
+ Because the workflow runs in your current conversation, you can have a long
86
+ discussion first and then trigger a workflow that builds on it. The
87
+ `elegant-solution` example does exactly that. It asks the model for the most
88
+ elegant long-term production-ready solution to the problem you discussed, then
89
+ for the holy grail, then whether the two are the same (y/n). On `y` it routes
90
+ straight into implementation, and on `n` it asks the model to reconcile the
91
+ gap and pauses at a checkpoint for you to decide. In either case, its
92
+ `presentationPrompt` turns the final structured result into a plain assistant
93
+ response.
94
+
95
+ ## Watching a run
96
+
97
+ Runs persist to `~/.pi/agent/workflows/runs/` as they execute. The viewer
98
+ tails that directory and re-renders on every state change:
99
+
100
+ ```bash
101
+ pi-workflows view # interactive picker, live updates
102
+ pi-workflows view <runId> # jump straight to one run
103
+ pi-workflows runs # plain list of recent runs
104
+ pi-workflows view --once # print a snapshot and exit (good for scripts)
105
+ ```
106
+
107
+ The run detail view draws the workflow as a boxed graph, like the acpx replay
108
+ viewer: every node sits in a box (heavy border for the active node), branches
109
+ carry their case labels, the taken path is highlighted, and loops route
110
+ through a gutter on the right back into their target from above. `←/→` scrubs
111
+ backwards and forwards through the recorded steps and re-derives every node's
112
+ status as of that step, with the selected step's full output shown below;
113
+ scrubbing to the end snaps back to following the run live.
114
+
115
+ ```
116
+ │ ┌──────────────────┐
117
+ ▼ ▼ │
118
+ ┌──────────────────────────┐ │
119
+ │ ✓ verify [action] 8.0s ×2 │ │
120
+ └──────────────────────────┘ │
121
+ │ │
122
+ ▼ │
123
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │
124
+ ┃ ◐ review [agent] 12s ×2 ┃ │
125
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │
126
+ ┌─ clean ─┘ └─ issues ─┐ │
127
+ ▼ ▼ │
128
+ ┌───────────┐ ┌───────────────┐ │
129
+ │ · done │ │ ✓ fix [agent] │ │
130
+ └───────────┘ └───────────────┘ │
131
+ └──────────┘
132
+ ```
133
+
134
+ Inside pi, a widget above the editor shows the same boxed graph while a
135
+ workflow is running, windowed around the active node when it is taller than
136
+ pi's widget budget. Scroll the window with `shift+↑` / `shift+↓`; it snaps back
137
+ to following the active node whenever the workflow advances a step.
138
+
139
+ ## Node types
140
+
141
+ A workflow is a graph of named nodes with exactly one entry point. Each node
142
+ finishes with a JSON output, and edges decide what runs next.
143
+
144
+ An `agent` node sends a prompt into the pi conversation and waits for the
145
+ model to submit its output through the `workflow` tool. A `compute` node runs
146
+ a pure TypeScript function. An `action` node performs a side effect, either a
147
+ TypeScript function (`action({ run })`) or a runtime-owned shell command
148
+ (`shell({ exec, parse })`). A `checkpoint` node ends the run in a `waiting`
149
+ state so a human can pick it up. On top of `agent`, the `decision` helper asks
150
+ the model to pick from a fixed set of choices and validates the answer, and
151
+ `decisionEdge` routes on the result with compile-time case checking.
152
+
153
+ See [docs/workflows.md](docs/workflows.md) for the full authoring reference
154
+ and [docs/run-bundles.md](docs/run-bundles.md) for the on-disk run format.
155
+
156
+ ## Examples
157
+
158
+ The [examples/workflows/](examples/workflows/) directory mirrors the acpx
159
+ example set. Copy any of them into `.pi/workflows/` to use them:
160
+
161
+ - `echo` is the smallest possible workflow, one agent step.
162
+ - `branch` classifies a task with a `decision` and routes to either a
163
+ continue lane or a clarification checkpoint.
164
+ - `shell` runs a runtime-owned shell command and parses its output, with no
165
+ agent step at all.
166
+ - `two-turn` chains three agent steps that build on each other's outputs in
167
+ the same conversation.
168
+ - `elegant-solution` is the mid-conversation trigger described above.
169
+ - `autoimplement` runs an implement, verify, review loop where the review
170
+ decision routes `issues_found` back to a fix step until it comes back
171
+ `clean`, bounded by `maxSteps`.
172
+ - `autoresearch` runs an iterative feature-search loop in the style of
173
+ [karpathy/autoresearch](https://github.com/karpathy/autoresearch): setup
174
+ creates a frozen evaluation harness, one editable feature file, and a
175
+ journal; each loop iteration runs one generation of experiments and
176
+ journals every result; an assess decision keeps looping until a kept
177
+ result plateaus or a diverse generation all fails, then conclusions are
178
+ written before the winner is promoted out of the loop directory.
179
+
180
+ ## License
181
+
182
+ [MIT](LICENSE)
@@ -0,0 +1,58 @@
1
+ import type { AgentStepExecutor, AgentStepRequest, AgentStepSubmission } from "../workflows/types.js";
2
+ export type SubmissionResult = {
3
+ accepted: true;
4
+ message: string;
5
+ } | {
6
+ accepted: false;
7
+ message: string;
8
+ };
9
+ export type PromptDelivery = {
10
+ prompt: string;
11
+ /** True when the agent is known to be mid-run, so delivery must be queued. */
12
+ streaming: boolean;
13
+ };
14
+ export type ConversationStepExecutorOptions = {
15
+ /** Deliver a prompt into the pi conversation. */
16
+ sendPrompt: (delivery: PromptDelivery) => void;
17
+ /** Reminders sent when the agent settles without submitting. Default 2. */
18
+ maxNudges?: number;
19
+ };
20
+ /**
21
+ * AgentStepExecutor that runs steps inside the current pi conversation. The
22
+ * engine hands it a prompt; it delivers the prompt as a user message and
23
+ * resolves once the model submits an accepted output through the `workflow`
24
+ * tool. If the agent settles without submitting, it nudges the model a
25
+ * bounded number of times before failing the step.
26
+ */
27
+ export declare class ConversationStepExecutor implements AgentStepExecutor {
28
+ private readonly sendPrompt;
29
+ private readonly maxNudges;
30
+ private pending;
31
+ private streaming;
32
+ private heldByUser;
33
+ constructor(options: ConversationStepExecutorOptions);
34
+ /** Track agent streaming state (wire to agent_start / agent_settled). */
35
+ setStreaming(streaming: boolean): void;
36
+ get pendingStepId(): string | null;
37
+ /**
38
+ * Hold the pending step for the user: no nudges are sent while held, so an
39
+ * escape-interrupted conversation stays quiet until the user resumes.
40
+ */
41
+ hold(): void;
42
+ get held(): boolean;
43
+ /**
44
+ * Release a user hold. When a step is still pending, its prompt is
45
+ * re-delivered so the model picks the step back up.
46
+ */
47
+ release(): void;
48
+ runAgentStep(request: AgentStepRequest, signal: AbortSignal): Promise<AgentStepSubmission>;
49
+ /** Called by the `workflow` tool when the model submits a step output. */
50
+ submit(stepId: string, attemptId: string, output: unknown): Promise<SubmissionResult>;
51
+ /**
52
+ * Called when the agent settles. Returns true when a nudge was sent, false
53
+ * when there was nothing to do. Fails the pending step once the nudge
54
+ * budget is exhausted.
55
+ */
56
+ handleAgentSettled(): boolean;
57
+ private clearPending;
58
+ }
@@ -0,0 +1,201 @@
1
+ const DEFAULT_MAX_NUDGES = 2;
2
+ /**
3
+ * AgentStepExecutor that runs steps inside the current pi conversation. The
4
+ * engine hands it a prompt; it delivers the prompt as a user message and
5
+ * resolves once the model submits an accepted output through the `workflow`
6
+ * tool. If the agent settles without submitting, it nudges the model a
7
+ * bounded number of times before failing the step.
8
+ */
9
+ export class ConversationStepExecutor {
10
+ sendPrompt;
11
+ maxNudges;
12
+ pending = null;
13
+ streaming = false;
14
+ heldByUser = false;
15
+ constructor(options) {
16
+ this.sendPrompt = options.sendPrompt;
17
+ this.maxNudges = options.maxNudges ?? DEFAULT_MAX_NUDGES;
18
+ }
19
+ /** Track agent streaming state (wire to agent_start / agent_settled). */
20
+ setStreaming(streaming) {
21
+ this.streaming = streaming;
22
+ }
23
+ get pendingStepId() {
24
+ return this.pending?.request.contract.nodeId ?? null;
25
+ }
26
+ /**
27
+ * Hold the pending step for the user: no nudges are sent while held, so an
28
+ * escape-interrupted conversation stays quiet until the user resumes.
29
+ */
30
+ hold() {
31
+ this.heldByUser = true;
32
+ }
33
+ get held() {
34
+ return this.heldByUser;
35
+ }
36
+ /**
37
+ * Release a user hold. When a step is still pending, its prompt is
38
+ * re-delivered so the model picks the step back up.
39
+ */
40
+ release() {
41
+ if (!this.heldByUser) {
42
+ return;
43
+ }
44
+ this.heldByUser = false;
45
+ const pending = this.pending;
46
+ if (!pending) {
47
+ return;
48
+ }
49
+ try {
50
+ this.sendPrompt({ prompt: pending.request.prompt, streaming: this.streaming });
51
+ }
52
+ catch (error) {
53
+ this.clearPending();
54
+ pending.reject(error);
55
+ }
56
+ }
57
+ async runAgentStep(request, signal) {
58
+ if (this.pending) {
59
+ throw new Error("Another workflow step is already awaiting output");
60
+ }
61
+ return await new Promise((resolve, reject) => {
62
+ const onAbort = () => {
63
+ const reason = signal.reason ?? new Error("Workflow step aborted");
64
+ this.clearPending();
65
+ reject(reason);
66
+ };
67
+ signal.addEventListener("abort", onAbort, { once: true });
68
+ let markCleared;
69
+ const cleared = new Promise((resolveCleared) => {
70
+ markCleared = resolveCleared;
71
+ });
72
+ this.pending = {
73
+ request,
74
+ resolve,
75
+ reject,
76
+ nudgesSent: 0,
77
+ cleanup: () => signal.removeEventListener("abort", onAbort),
78
+ cleared,
79
+ markCleared,
80
+ };
81
+ if (signal.aborted) {
82
+ onAbort();
83
+ return;
84
+ }
85
+ try {
86
+ this.sendPrompt({ prompt: request.prompt, streaming: this.streaming });
87
+ }
88
+ catch (error) {
89
+ // A failed delivery must not leave the step installed, or every
90
+ // subsequent agent node would fail with "already awaiting output".
91
+ this.clearPending();
92
+ reject(error);
93
+ }
94
+ });
95
+ }
96
+ /** Called by the `workflow` tool when the model submits a step output. */
97
+ async submit(stepId, attemptId, output) {
98
+ const pending = this.pending;
99
+ if (!pending) {
100
+ return {
101
+ accepted: false,
102
+ message: "No workflow step is awaiting output. Do not call the workflow tool outside an active workflow step.",
103
+ };
104
+ }
105
+ const expected = pending.request.contract.nodeId;
106
+ if (stepId !== expected) {
107
+ return {
108
+ accepted: false,
109
+ message: `Wrong step id ${JSON.stringify(stepId)}; the pending step is ${JSON.stringify(expected)}.`,
110
+ };
111
+ }
112
+ // Loops revisit the same node id, so a delayed duplicate submission from
113
+ // an earlier attempt would otherwise be accepted as this attempt's output.
114
+ const expectedAttempt = pending.request.contract.attemptId;
115
+ if (attemptId !== expectedAttempt) {
116
+ return {
117
+ accepted: false,
118
+ message: `Stale attempt id ${JSON.stringify(attemptId)} for step ${JSON.stringify(stepId)}; the pending attempt is ${JSON.stringify(expectedAttempt)}. Use the attempt id from the latest step contract.`,
119
+ };
120
+ }
121
+ // Race validation against the step being cleared: a hung `validate`
122
+ // callback must not leave this tool call (and therefore pi) blocked after
123
+ // a timeout or cancel already resolved the run.
124
+ const result = await Promise.race([
125
+ pending.request.accept(output),
126
+ pending.cleared.then(() => null),
127
+ ]);
128
+ // The step may have timed out or been cancelled (and a newer step
129
+ // installed) while validation was awaited; a stale submission must not
130
+ // clear or resolve the newer pending step.
131
+ if (result === null || this.pending !== pending) {
132
+ return {
133
+ accepted: false,
134
+ message: `Step ${JSON.stringify(stepId)} is no longer awaiting output.`,
135
+ };
136
+ }
137
+ if (!result.ok) {
138
+ return {
139
+ accepted: false,
140
+ message: `Output rejected for step ${JSON.stringify(stepId)}: ${result.error}`,
141
+ };
142
+ }
143
+ this.clearPending();
144
+ pending.resolve({ output: result.value });
145
+ return {
146
+ accepted: true,
147
+ message: [
148
+ `Output accepted for step ${JSON.stringify(stepId)}.`,
149
+ "If the workflow continues, the next step arrives as a new user message. End your turn now.",
150
+ ].join(" "),
151
+ };
152
+ }
153
+ /**
154
+ * Called when the agent settles. Returns true when a nudge was sent, false
155
+ * when there was nothing to do. Fails the pending step once the nudge
156
+ * budget is exhausted.
157
+ */
158
+ handleAgentSettled() {
159
+ const pending = this.pending;
160
+ if (!pending) {
161
+ return false;
162
+ }
163
+ if (this.heldByUser) {
164
+ // The user interrupted deliberately; reminding the model now would
165
+ // steal the conversation back. The step waits for an explicit resume.
166
+ return false;
167
+ }
168
+ if (pending.nudgesSent >= this.maxNudges) {
169
+ this.clearPending();
170
+ pending.reject(new Error(`Agent settled ${pending.nudgesSent + 1} times without submitting step ${JSON.stringify(pending.request.contract.nodeId)} via the workflow tool`));
171
+ return false;
172
+ }
173
+ pending.nudgesSent += 1;
174
+ const { nodeId, attemptId } = pending.request.contract;
175
+ try {
176
+ this.sendPrompt({
177
+ prompt: [
178
+ `Reminder: workflow step ${JSON.stringify(nodeId)} is still awaiting your output.`,
179
+ "Complete it by calling the `workflow` tool with:",
180
+ `{"step": ${JSON.stringify(nodeId)}, "attempt": ${JSON.stringify(attemptId)}, "output": <your result>}`,
181
+ `Expected output: ${pending.request.contract.expectedOutput ?? "a JSON object with your result"}`,
182
+ ].join("\n"),
183
+ streaming: this.streaming,
184
+ });
185
+ }
186
+ catch (error) {
187
+ // No reminder turn was started, so nothing would settle the step; fail
188
+ // it promptly instead of waiting out the node timeout.
189
+ this.clearPending();
190
+ pending.reject(error);
191
+ return false;
192
+ }
193
+ return true;
194
+ }
195
+ clearPending() {
196
+ this.pending?.cleanup();
197
+ this.pending?.markCleared();
198
+ this.pending = null;
199
+ }
200
+ }
201
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/extension/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export type ParsedWorkflowArgs = {
3
+ kind: "list";
4
+ } | {
5
+ kind: "cancel";
6
+ } | {
7
+ kind: "pause";
8
+ } | {
9
+ kind: "resume";
10
+ } | {
11
+ kind: "run";
12
+ ref: string;
13
+ input: unknown;
14
+ };
15
+ /** Parse `/workflow` arguments. Exported for tests. */
16
+ export declare function parseWorkflowArgs(args: string): ParsedWorkflowArgs;
17
+ export default function piWorkflows(pi: ExtensionAPI): void;