@osolmaz/pi-workflows 0.13.4 → 0.15.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 (237) hide show
  1. package/README.md +146 -160
  2. package/dist/builtins/autoimplement.workflow.js +3 -1
  3. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  4. package/dist/builtins/change-verification.workflow.js +7 -2
  5. package/dist/builtins/change-verification.workflow.js.map +1 -1
  6. package/dist/builtins/metadata.d.ts +22 -0
  7. package/dist/builtins/metadata.js +10 -0
  8. package/dist/builtins/metadata.js.map +1 -0
  9. package/dist/builtins/monitor.workflow.js +4 -1
  10. package/dist/builtins/monitor.workflow.js.map +1 -1
  11. package/dist/builtins/sanity-check.workflow.js +4 -1
  12. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  13. package/dist/builtins/workspace-preparation.workflow.js +3 -1
  14. package/dist/builtins/workspace-preparation.workflow.js.map +1 -1
  15. package/dist/controllers/index.d.ts +1 -2
  16. package/dist/controllers/index.js +0 -1
  17. package/dist/controllers/index.js.map +1 -1
  18. package/dist/controllers/sqlite.d.ts +85 -31
  19. package/dist/controllers/sqlite.js +541 -126
  20. package/dist/controllers/sqlite.js.map +1 -1
  21. package/dist/controllers/store.d.ts +1 -0
  22. package/dist/extension/controller-command.d.ts +22 -0
  23. package/dist/extension/controller-command.js +32 -0
  24. package/dist/extension/controller-command.js.map +1 -0
  25. package/dist/extension/index.d.ts +2 -9
  26. package/dist/extension/index.js +876 -2928
  27. package/dist/extension/index.js.map +1 -1
  28. package/dist/extension/message-card.d.ts +13 -0
  29. package/dist/extension/message-card.js +46 -0
  30. package/dist/extension/message-card.js.map +1 -0
  31. package/dist/extension/step-message.d.ts +5 -6
  32. package/dist/extension/step-message.js +41 -52
  33. package/dist/extension/step-message.js.map +1 -1
  34. package/dist/host/child-worker-supervisor.d.ts +50 -0
  35. package/dist/host/child-worker-supervisor.js +159 -0
  36. package/dist/host/child-worker-supervisor.js.map +1 -0
  37. package/dist/host/client.d.ts +48 -0
  38. package/dist/host/client.js +216 -0
  39. package/dist/host/client.js.map +1 -0
  40. package/dist/host/controller-worker-entry.d.ts +2 -0
  41. package/dist/host/controller-worker-entry.js +244 -0
  42. package/dist/host/controller-worker-entry.js.map +1 -0
  43. package/dist/host/controller-worker-protocol.d.ts +36 -0
  44. package/dist/host/controller-worker-protocol.js +49 -0
  45. package/dist/host/controller-worker-protocol.js.map +1 -0
  46. package/dist/host/controller-worker-supervisor.d.ts +21 -0
  47. package/dist/host/controller-worker-supervisor.js +54 -0
  48. package/dist/host/controller-worker-supervisor.js.map +1 -0
  49. package/dist/host/host-entry.d.ts +2 -0
  50. package/dist/host/host-entry.js +23 -0
  51. package/dist/host/host-entry.js.map +1 -0
  52. package/dist/host/processes.d.ts +17 -12
  53. package/dist/host/processes.js +154 -50
  54. package/dist/host/processes.js.map +1 -1
  55. package/dist/host/protocol.d.ts +38 -0
  56. package/dist/host/protocol.js +156 -0
  57. package/dist/host/protocol.js.map +1 -0
  58. package/dist/host/resolver-entry.d.ts +49 -0
  59. package/dist/host/resolver-entry.js +149 -0
  60. package/dist/host/resolver-entry.js.map +1 -0
  61. package/dist/host/rpc-executor.d.ts +14 -3
  62. package/dist/host/rpc-executor.js +63 -30
  63. package/dist/host/rpc-executor.js.map +1 -1
  64. package/dist/host/runner.d.ts +95 -29
  65. package/dist/host/runner.js +2311 -348
  66. package/dist/host/runner.js.map +1 -1
  67. package/dist/host/state.d.ts +174 -0
  68. package/dist/host/state.js +689 -0
  69. package/dist/host/state.js.map +1 -0
  70. package/dist/host/worker-entry.d.ts +10 -0
  71. package/dist/host/worker-entry.js +446 -0
  72. package/dist/host/worker-entry.js.map +1 -0
  73. package/dist/host/worker-protocol.d.ts +31 -0
  74. package/dist/host/worker-protocol.js +122 -0
  75. package/dist/host/worker-protocol.js.map +1 -0
  76. package/dist/host/worker-store.d.ts +80 -0
  77. package/dist/host/worker-store.js +91 -0
  78. package/dist/host/worker-store.js.map +1 -0
  79. package/dist/host/worker-supervisor.d.ts +22 -0
  80. package/dist/host/worker-supervisor.js +54 -0
  81. package/dist/host/worker-supervisor.js.map +1 -0
  82. package/dist/render/canvas.d.ts +2 -1
  83. package/dist/render/canvas.js +40 -15
  84. package/dist/render/canvas.js.map +1 -1
  85. package/dist/render/graph-render.d.ts +7 -3
  86. package/dist/render/graph-render.js +125 -74
  87. package/dist/render/graph-render.js.map +1 -1
  88. package/dist/state/database.js +3 -1
  89. package/dist/state/database.js.map +1 -1
  90. package/dist/state/index.d.ts +1 -0
  91. package/dist/state/index.js +1 -0
  92. package/dist/state/index.js.map +1 -1
  93. package/dist/state/prune.js +36 -10
  94. package/dist/state/prune.js.map +1 -1
  95. package/dist/state/schema.d.ts +1 -1
  96. package/dist/state/schema.js +171 -3
  97. package/dist/state/schema.js.map +1 -1
  98. package/dist/state/viewer.d.ts +46 -0
  99. package/dist/state/viewer.js +249 -0
  100. package/dist/state/viewer.js.map +1 -0
  101. package/dist/viewer/cli.d.ts +1 -1
  102. package/dist/viewer/cli.js +47 -18
  103. package/dist/viewer/cli.js.map +1 -1
  104. package/dist/workflows/composition.js +25 -2
  105. package/dist/workflows/composition.js.map +1 -1
  106. package/dist/workflows/definition.d.ts +3 -1
  107. package/dist/workflows/definition.js +25 -0
  108. package/dist/workflows/definition.js.map +1 -1
  109. package/dist/workflows/engine.d.ts +1 -0
  110. package/dist/workflows/engine.js +157 -42
  111. package/dist/workflows/engine.js.map +1 -1
  112. package/dist/workflows/errors.d.ts +3 -1
  113. package/dist/workflows/errors.js +4 -7
  114. package/dist/workflows/errors.js.map +1 -1
  115. package/dist/workflows/human-decision.d.ts +3 -0
  116. package/dist/workflows/human-decision.js +31 -0
  117. package/dist/workflows/human-decision.js.map +1 -1
  118. package/dist/workflows/index.d.ts +1 -1
  119. package/dist/workflows/index.js +1 -1
  120. package/dist/workflows/index.js.map +1 -1
  121. package/dist/workflows/schema.js +17 -6
  122. package/dist/workflows/schema.js.map +1 -1
  123. package/dist/{viewer → workflows}/session-reducer.d.ts +3 -1
  124. package/dist/{viewer → workflows}/session-reducer.js +4 -0
  125. package/dist/workflows/session-reducer.js.map +1 -0
  126. package/dist/workflows/store.d.ts +87 -1
  127. package/dist/workflows/store.js +892 -93
  128. package/dist/workflows/store.js.map +1 -1
  129. package/dist/workflows/tool-input.d.ts +0 -22
  130. package/dist/workflows/tool-input.js +1 -44
  131. package/dist/workflows/tool-input.js.map +1 -1
  132. package/dist/workflows/types.d.ts +36 -4
  133. package/docs/2026-08-25-workflow-follow-ups.md +8 -6
  134. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +353 -0
  135. package/docs/CONTROLLERS.md +11 -11
  136. package/docs/DEFERRED_TURNS.md +61 -27
  137. package/docs/HUMAN_DECISIONS.md +12 -4
  138. package/docs/SQLITE_STATE.md +42 -8
  139. package/docs/WORKFLOW_HOST.md +452 -0
  140. package/docs/development.md +46 -30
  141. package/docs/live-replay-protocol.md +129 -100
  142. package/docs/plans/2026-08-19-human-decision-gates-plan.md +34 -8
  143. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +399 -0
  144. package/docs/plans/2026-08-28-piw-incremental-viewer-plan.md +293 -0
  145. package/docs/plans/piw-viewer-experience-implementation-plan.md +11 -1
  146. package/docs/plans/tui-viewer-implementation-plan.md +5 -0
  147. package/docs/tui-viewer.md +18 -3
  148. package/docs/workflows.md +164 -156
  149. package/examples/workflows/command-batch.workflow.ts +2 -0
  150. package/examples/workflows/shell.workflow.ts +2 -1
  151. package/herdr-plugin.toml +1 -1
  152. package/package.json +1 -1
  153. package/skills/autodoc/SKILL.md +1 -1
  154. package/skills/autoimplement/SKILL.md +1 -1
  155. package/skills/autoplan/SKILL.md +1 -1
  156. package/skills/pi-workflows/SKILL.md +2 -0
  157. package/src/builtins/autoimplement.workflow.ts +3 -0
  158. package/src/builtins/change-verification.workflow.ts +7 -2
  159. package/src/builtins/metadata.ts +9 -0
  160. package/src/builtins/monitor.workflow.ts +4 -0
  161. package/src/builtins/sanity-check.workflow.ts +4 -0
  162. package/src/builtins/workspace-preparation.workflow.ts +3 -1
  163. package/src/controllers/index.ts +3 -5
  164. package/src/controllers/sqlite.ts +929 -225
  165. package/src/controllers/store.ts +1 -0
  166. package/src/extension/controller-command.ts +45 -0
  167. package/src/extension/index.ts +948 -3506
  168. package/src/extension/message-card.ts +61 -0
  169. package/src/extension/step-message.ts +58 -63
  170. package/src/host/child-worker-supervisor.ts +183 -0
  171. package/src/host/client.ts +293 -0
  172. package/src/host/controller-worker-entry.ts +311 -0
  173. package/src/host/controller-worker-protocol.ts +104 -0
  174. package/src/host/controller-worker-supervisor.ts +79 -0
  175. package/src/host/host-entry.ts +23 -0
  176. package/src/host/processes.ts +171 -54
  177. package/src/host/protocol.ts +196 -0
  178. package/src/host/resolver-entry.ts +241 -0
  179. package/src/host/rpc-executor.ts +76 -34
  180. package/src/host/runner.ts +2813 -422
  181. package/src/host/state.ts +1160 -0
  182. package/src/host/worker-entry.ts +533 -0
  183. package/src/host/worker-protocol.ts +165 -0
  184. package/src/host/worker-store.ts +229 -0
  185. package/src/host/worker-supervisor.ts +74 -0
  186. package/src/render/canvas.ts +44 -10
  187. package/src/render/graph-render.ts +145 -90
  188. package/src/state/database.ts +2 -1
  189. package/src/state/index.ts +14 -0
  190. package/src/state/prune.ts +35 -9
  191. package/src/state/schema.ts +171 -3
  192. package/src/state/viewer.ts +356 -0
  193. package/src/viewer/cli.ts +49 -17
  194. package/src/workflows/composition.ts +36 -2
  195. package/src/workflows/definition.ts +32 -0
  196. package/src/workflows/engine.ts +157 -54
  197. package/src/workflows/errors.ts +11 -2
  198. package/src/workflows/human-decision.ts +49 -0
  199. package/src/workflows/index.ts +2 -0
  200. package/src/workflows/schema.ts +19 -6
  201. package/src/{viewer → workflows}/session-reducer.ts +11 -1
  202. package/src/workflows/store.ts +1316 -108
  203. package/src/workflows/tool-input.ts +2 -60
  204. package/src/workflows/types.ts +32 -4
  205. package/dist/controllers/workflow-engine-scheduler.d.ts +0 -29
  206. package/dist/controllers/workflow-engine-scheduler.js +0 -175
  207. package/dist/controllers/workflow-engine-scheduler.js.map +0 -1
  208. package/dist/extension/controller-host.d.ts +0 -48
  209. package/dist/extension/controller-host.js +0 -110
  210. package/dist/extension/controller-host.js.map +0 -1
  211. package/dist/extension/deferred-turn-coordinator.d.ts +0 -32
  212. package/dist/extension/deferred-turn-coordinator.js +0 -143
  213. package/dist/extension/deferred-turn-coordinator.js.map +0 -1
  214. package/dist/extension/deferred-turn.d.ts +0 -44
  215. package/dist/extension/deferred-turn.js +0 -110
  216. package/dist/extension/deferred-turn.js.map +0 -1
  217. package/dist/extension/executor.d.ts +0 -86
  218. package/dist/extension/executor.js +0 -311
  219. package/dist/extension/executor.js.map +0 -1
  220. package/dist/extension/follow-up-coordinator.d.ts +0 -27
  221. package/dist/extension/follow-up-coordinator.js +0 -131
  222. package/dist/extension/follow-up-coordinator.js.map +0 -1
  223. package/dist/extension/recorder.d.ts +0 -84
  224. package/dist/extension/recorder.js +0 -528
  225. package/dist/extension/recorder.js.map +0 -1
  226. package/dist/extension/session-events.d.ts +0 -133
  227. package/dist/extension/session-events.js +0 -61
  228. package/dist/extension/session-events.js.map +0 -1
  229. package/dist/viewer/session-reducer.js.map +0 -1
  230. package/src/controllers/workflow-engine-scheduler.ts +0 -246
  231. package/src/extension/controller-host.ts +0 -167
  232. package/src/extension/deferred-turn-coordinator.ts +0 -171
  233. package/src/extension/deferred-turn.ts +0 -166
  234. package/src/extension/executor.ts +0 -411
  235. package/src/extension/follow-up-coordinator.ts +0 -151
  236. package/src/extension/recorder.ts +0 -655
  237. package/src/extension/session-events.ts +0 -121
@@ -0,0 +1,229 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { JsonValue } from "../state/json.js";
3
+ import type { WorkflowSettingsScopeRecord } from "../workflows/settings.js";
4
+ import {
5
+ createDefinitionSnapshot,
6
+ type InitializeWorkflowRunOptions,
7
+ type LoadedWorkflowRun,
8
+ type ReadWorkflowRunOptions,
9
+ type WorkflowExecutionStore,
10
+ } from "../workflows/store.js";
11
+ import type {
12
+ HumanDecisionRequest,
13
+ ResolvedHumanDecision,
14
+ WorkflowDefinition,
15
+ WorkflowEffectRecovery,
16
+ WorkflowEffectReservation,
17
+ WorkflowNotificationReceipt,
18
+ WorkflowNotificationRequest,
19
+ WorkflowRunState,
20
+ WorkflowTraceEvent,
21
+ WorkflowTraceEventDraft,
22
+ WorkflowUpdateInput,
23
+ WorkflowUpdateRecord,
24
+ } from "../workflows/types.js";
25
+ import type { WorkerMessageKind, WorkerStoreOperation } from "./worker-protocol.js";
26
+ import { workerKindForOperation } from "./worker-protocol.js";
27
+
28
+ export interface WorkerStoreTransport {
29
+ request(options: {
30
+ messageId: string;
31
+ operation: WorkerStoreOperation;
32
+ kind: WorkerMessageKind;
33
+ expectedRevision: number;
34
+ attemptId?: string;
35
+ payload: JsonValue;
36
+ }): Promise<{ result?: JsonValue; revision?: number }>;
37
+ }
38
+
39
+ /** A worker-side store that can only propose state changes to its host. */
40
+ export class HostBackedWorkflowStore implements WorkflowExecutionStore {
41
+ readonly databasePath = "host://pi-workflows-state";
42
+ private revision: number;
43
+
44
+ constructor(
45
+ private readonly runId: string,
46
+ private readonly transport: WorkerStoreTransport,
47
+ initialRevision = 0,
48
+ ) {
49
+ this.revision = initialRevision;
50
+ }
51
+
52
+ async initializeRun(
53
+ workflow: WorkflowDefinition,
54
+ state: WorkflowRunState,
55
+ options: InitializeWorkflowRunOptions = {},
56
+ ): Promise<string> {
57
+ return await this.call<string>("store.initializeRun", {
58
+ snapshot: createDefinitionSnapshot(workflow),
59
+ workflowName: workflow.name,
60
+ state,
61
+ options,
62
+ });
63
+ }
64
+
65
+ async prepareRunResume(runId: string): Promise<LoadedWorkflowRun> {
66
+ return await this.call<LoadedWorkflowRun>("store.prepareRunResume", { runId });
67
+ }
68
+
69
+ async readRun(
70
+ runId: string,
71
+ options: ReadWorkflowRunOptions = {},
72
+ ): Promise<LoadedWorkflowRun | null> {
73
+ return await this.call<LoadedWorkflowRun | null>("store.readRun", { runId, options });
74
+ }
75
+
76
+ async writeSnapshot(
77
+ runId: string,
78
+ state: WorkflowRunState,
79
+ event: WorkflowTraceEventDraft,
80
+ ): Promise<WorkflowTraceEvent> {
81
+ return await this.call<WorkflowTraceEvent>(
82
+ "store.writeSnapshot",
83
+ { runId, state, event },
84
+ event.attemptId,
85
+ );
86
+ }
87
+
88
+ async publishUpdate(
89
+ runId: string,
90
+ state: WorkflowRunState,
91
+ nodeId: string,
92
+ attemptId: string,
93
+ update: WorkflowUpdateInput,
94
+ ): Promise<{ event: WorkflowTraceEvent; record: WorkflowUpdateRecord }> {
95
+ return await this.call<{ event: WorkflowTraceEvent; record: WorkflowUpdateRecord }>(
96
+ "store.publishUpdate",
97
+ { runId, state, nodeId, attemptId, update },
98
+ attemptId,
99
+ );
100
+ }
101
+
102
+ async findSettingsScope(
103
+ runId: string,
104
+ mountPath: string,
105
+ invocation: number,
106
+ ): Promise<WorkflowSettingsScopeRecord | undefined> {
107
+ return await this.call<WorkflowSettingsScopeRecord | undefined>("store.findSettingsScope", {
108
+ runId,
109
+ mountPath,
110
+ invocation,
111
+ });
112
+ }
113
+
114
+ async ensureSettingsScope(options: {
115
+ runId: string;
116
+ mountPath: string;
117
+ invocation: number;
118
+ settings: JsonValue;
119
+ }): Promise<WorkflowSettingsScopeRecord> {
120
+ return await this.call<WorkflowSettingsScopeRecord>("store.ensureSettingsScope", { options });
121
+ }
122
+
123
+ async getSettingsScopeAtChange(
124
+ scopeId: string,
125
+ changeNumber: number,
126
+ ): Promise<WorkflowSettingsScopeRecord | undefined> {
127
+ return await this.call<WorkflowSettingsScopeRecord | undefined>(
128
+ "store.getSettingsScopeAtChange",
129
+ { scopeId, changeNumber },
130
+ );
131
+ }
132
+
133
+ async createHumanDecisionRequest(request: HumanDecisionRequest): Promise<"created" | "adopted"> {
134
+ return await this.call<"created" | "adopted">(
135
+ "store.createHumanDecisionRequest",
136
+ { request },
137
+ request.attemptId,
138
+ );
139
+ }
140
+
141
+ async readResolvedHumanDecision(decisionId: string): Promise<ResolvedHumanDecision | null> {
142
+ return await this.call<ResolvedHumanDecision | null>("store.readResolvedHumanDecision", {
143
+ decisionId,
144
+ });
145
+ }
146
+
147
+ async reserveEffect(options: {
148
+ runId: string;
149
+ attemptId: string;
150
+ effectType: string;
151
+ idempotencyKey: string;
152
+ request: JsonValue;
153
+ recovery: WorkflowEffectRecovery;
154
+ }): Promise<WorkflowEffectReservation> {
155
+ return await this.call<WorkflowEffectReservation>(
156
+ "store.reserveEffect",
157
+ { options },
158
+ options.attemptId,
159
+ );
160
+ }
161
+
162
+ async settleEffect(options: {
163
+ runId: string;
164
+ effectId: string;
165
+ attemptNumber: number;
166
+ outcome: "applied" | "rejected" | "ambiguous" | "cancelled";
167
+ result?: JsonValue;
168
+ error?: string;
169
+ }): Promise<void> {
170
+ await this.call("store.settleEffect", { options });
171
+ }
172
+
173
+ async requestInteraction(options: {
174
+ attemptId: string;
175
+ kind: "agent" | "assistant" | "decision";
176
+ contract: JsonValue;
177
+ }): Promise<void> {
178
+ await this.call("interaction.request", { runId: this.runId, ...options }, options.attemptId);
179
+ }
180
+
181
+ async acceptInteraction(options: {
182
+ requestId: string;
183
+ submissionId: string;
184
+ attemptId: string;
185
+ value: JsonValue;
186
+ }): Promise<void> {
187
+ await this.call("interaction.accept", options, options.attemptId);
188
+ }
189
+
190
+ async rejectInteraction(options: {
191
+ requestId: string;
192
+ submissionId: string;
193
+ attemptId: string;
194
+ error: string;
195
+ }): Promise<void> {
196
+ await this.call("interaction.reject", options, options.attemptId);
197
+ }
198
+
199
+ async requestNotification(
200
+ request: WorkflowNotificationRequest,
201
+ ): Promise<WorkflowNotificationReceipt> {
202
+ return await this.call<WorkflowNotificationReceipt>(
203
+ "notification.request",
204
+ { request },
205
+ request.attemptId,
206
+ );
207
+ }
208
+
209
+ async requestPresentation(instructions: string): Promise<void> {
210
+ await this.call("presentation.request", { instructions });
211
+ }
212
+
213
+ private async call<T>(
214
+ operation: WorkerStoreOperation,
215
+ payload: Record<string, unknown>,
216
+ attemptId?: string,
217
+ ): Promise<T> {
218
+ const response = await this.transport.request({
219
+ messageId: randomUUID(),
220
+ operation,
221
+ kind: workerKindForOperation(operation, payload),
222
+ expectedRevision: this.revision,
223
+ ...(attemptId === undefined ? {} : { attemptId }),
224
+ payload: payload as JsonValue,
225
+ });
226
+ if (response.revision !== undefined) this.revision = response.revision;
227
+ return response.result as T;
228
+ }
229
+ }
@@ -0,0 +1,74 @@
1
+ import fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { fileURLToPath } from "node:url";
4
+ import { canonicalJson } from "../state/json.js";
5
+ import { ChildWorkerSupervisor, type SupervisedWorkerResult } from "./child-worker-supervisor.js";
6
+ import { HostProcessRegistry, type ProcessIdentity } from "./processes.js";
7
+ import type { WorkerLaunchEnvelope, WorkerOutcome } from "./state.js";
8
+ import {
9
+ encodeWorkerLine,
10
+ parseWorkerMessage,
11
+ type WorkerMessage,
12
+ type WorkerResponse,
13
+ } from "./worker-protocol.js";
14
+
15
+ export type WorkflowWorkerResult = SupervisedWorkerResult;
16
+
17
+ export class WorkflowWorkerSupervisor {
18
+ private readonly supervisor: ChildWorkerSupervisor<WorkerMessage, WorkerResponse>;
19
+
20
+ constructor(
21
+ readonly envelope: WorkerLaunchEnvelope,
22
+ private readonly options: {
23
+ registry: HostProcessRegistry;
24
+ onMessage: (message: WorkerMessage) => Promise<WorkerResponse>;
25
+ env?: Record<string, string>;
26
+ startupTimeoutMs?: number;
27
+ workerEntryPath?: string;
28
+ onSpawn?: (identity: ProcessIdentity) => void;
29
+ onDiagnostic?: (message: string) => void;
30
+ },
31
+ ) {
32
+ this.supervisor = new ChildWorkerSupervisor({
33
+ label: "Workflow worker",
34
+ registry: options.registry,
35
+ onMessage: options.onMessage,
36
+ parseMessage: parseWorkerMessage,
37
+ encodeResponse: encodeWorkerLine,
38
+ isReady: (message) => message.kind === "worker.ready",
39
+ isTerminal: (message) => message.kind === "worker.exiting",
40
+ ...(options.startupTimeoutMs === undefined
41
+ ? {}
42
+ : { startupTimeoutMs: options.startupTimeoutMs }),
43
+ ...(options.onSpawn === undefined ? {} : { onSpawn: options.onSpawn }),
44
+ ...(options.onDiagnostic === undefined ? {} : { onDiagnostic: options.onDiagnostic }),
45
+ });
46
+ }
47
+
48
+ async start(): Promise<void> {
49
+ const builtEntry = fileURLToPath(new URL("./worker-entry.js", import.meta.url));
50
+ const sourceEntry = fileURLToPath(new URL("./worker-entry.ts", import.meta.url));
51
+ const workerEntry = this.options.workerEntryPath ?? builtEntry;
52
+ const args =
53
+ this.options.workerEntryPath === undefined && !fs.existsSync(builtEntry)
54
+ ? ["--import", createRequire(import.meta.url).resolve("tsx"), sourceEntry]
55
+ : [workerEntry];
56
+ await this.supervisor.start({
57
+ args,
58
+ cwd: this.envelope.projectPath,
59
+ env: {
60
+ ...process.env,
61
+ ...this.options.env,
62
+ PI_WORKFLOWS_WORKER_LAUNCH: Buffer.from(canonicalJson(this.envelope)).toString("base64url"),
63
+ },
64
+ });
65
+ }
66
+
67
+ async wait(): Promise<WorkflowWorkerResult> {
68
+ return await this.supervisor.wait();
69
+ }
70
+
71
+ async stop(outcome: WorkerOutcome = "cancelled"): Promise<void> {
72
+ await this.supervisor.stop(outcome);
73
+ }
74
+ }
@@ -1,3 +1,5 @@
1
+ import { visibleWidth } from "@earendil-works/pi-tui";
2
+
1
3
  /**
2
4
  * A sparse character grid for drawing the workflow graph. Box-drawing
3
5
  * characters merge by connectivity (│ crossing ─ becomes ┼) so overlapping
@@ -94,6 +96,9 @@ export class CharCanvas {
94
96
  return;
95
97
  }
96
98
  if (existing) {
99
+ if (existing.char === "") {
100
+ return;
101
+ }
97
102
  const existingMask = CHAR_TO_MASK[existing.char];
98
103
  const incomingMask = CHAR_TO_MASK[char];
99
104
  if (existingMask !== undefined && incomingMask !== undefined) {
@@ -112,11 +117,40 @@ export class CharCanvas {
112
117
  row.set(x, { char, style });
113
118
  }
114
119
 
115
- /** Write a text run left to right (labels, node lines). */
116
- text(x: number, y: number, value: string, style: CanvasStyle = "plain"): void {
117
- for (const [index, char] of [...value].entries()) {
118
- this.put(x + index, y, char, style);
120
+ private writeText(
121
+ x: number,
122
+ y: number,
123
+ value: string,
124
+ style: CanvasStyle,
125
+ preserveSpaces: boolean,
126
+ ): void {
127
+ const row = this.row(y);
128
+ let cursor = x;
129
+ for (const char of value) {
130
+ const width = visibleWidth(char);
131
+ if (width === 0) {
132
+ let anchor = cursor - 1;
133
+ while (row.get(anchor)?.char === "") anchor -= 1;
134
+ const cell = row.get(anchor);
135
+ if (cell !== undefined && cell.char !== " ") cell.char += char;
136
+ continue;
137
+ }
138
+ if (char === " ") {
139
+ if (preserveSpaces) row.set(cursor, { char, style });
140
+ } else {
141
+ this.put(cursor, y, char, style);
142
+ }
143
+ for (let offset = 1; offset < width; offset += 1) {
144
+ row.set(cursor + offset, { char: "", style });
145
+ }
146
+ cursor += width;
119
147
  }
148
+ this.maxX = Math.max(this.maxX, cursor - 1);
149
+ }
150
+
151
+ /** Write a text run left to right in terminal display cells. */
152
+ text(x: number, y: number, value: string, style: CanvasStyle = "plain"): void {
153
+ this.writeText(x, y, value, style, false);
120
154
  }
121
155
 
122
156
  /**
@@ -129,7 +163,7 @@ export class CharCanvas {
129
163
  return false;
130
164
  }
131
165
  const row = this.cells.get(y);
132
- const width = [...value].length;
166
+ const width = visibleWidth(value);
133
167
  for (let index = 0; index < width; index += 1) {
134
168
  if (row?.has(x + index)) {
135
169
  return false;
@@ -150,17 +184,17 @@ export class CharCanvas {
150
184
  if (x < 1 || y < 0) {
151
185
  return false;
152
186
  }
153
- const chars = [...value];
187
+ const width = visibleWidth(value);
154
188
  const row = this.cells.get(y);
155
- for (let index = -1; index <= chars.length; index += 1) {
189
+ for (let index = -1; index <= width; index += 1) {
156
190
  if (row?.get(x + index)?.char !== "─") {
157
191
  return false;
158
192
  }
159
193
  }
160
- for (const [index, char] of chars.entries()) {
161
- this.row(y).set(x + index, { char, style });
194
+ for (let index = 0; index < width; index += 1) {
195
+ this.row(y).delete(x + index);
162
196
  }
163
- this.maxX = Math.max(this.maxX, x + chars.length - 1);
197
+ this.writeText(x, y, value, style, true);
164
198
  return true;
165
199
  }
166
200