@osolmaz/pi-workflows 0.5.2 → 0.6.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 (81) hide show
  1. package/README.md +13 -3
  2. package/dist/builtins/catalog.js +1 -1
  3. package/dist/builtins/monitor.workflow.d.ts +25 -69
  4. package/dist/builtins/monitor.workflow.js +194 -123
  5. package/dist/builtins/monitor.workflow.js.map +1 -1
  6. package/dist/extension/executor.d.ts +7 -2
  7. package/dist/extension/executor.js +20 -14
  8. package/dist/extension/executor.js.map +1 -1
  9. package/dist/extension/index.js +56 -15
  10. package/dist/extension/index.js.map +1 -1
  11. package/dist/extension/step-message.d.ts +24 -0
  12. package/dist/extension/step-message.js +106 -0
  13. package/dist/extension/step-message.js.map +1 -0
  14. package/dist/extension/widget.d.ts +4 -2
  15. package/dist/extension/widget.js +111 -17
  16. package/dist/extension/widget.js.map +1 -1
  17. package/dist/extension/workflow-tool.d.ts +9 -0
  18. package/dist/extension/workflow-tool.js +10 -0
  19. package/dist/extension/workflow-tool.js.map +1 -1
  20. package/dist/host/rpc-bridge.js +25 -11
  21. package/dist/host/rpc-bridge.js.map +1 -1
  22. package/dist/host/rpc-executor.d.ts +2 -2
  23. package/dist/host/rpc-executor.js +23 -12
  24. package/dist/host/rpc-executor.js.map +1 -1
  25. package/dist/viewer/cli.js +1 -1
  26. package/dist/viewer/cli.js.map +1 -1
  27. package/dist/viewer/render.js +14 -0
  28. package/dist/viewer/render.js.map +1 -1
  29. package/dist/viewer/tui.js +1 -1
  30. package/dist/viewer/tui.js.map +1 -1
  31. package/dist/workflows/engine.d.ts +6 -1
  32. package/dist/workflows/engine.js +88 -6
  33. package/dist/workflows/engine.js.map +1 -1
  34. package/dist/workflows/index.d.ts +4 -2
  35. package/dist/workflows/index.js +2 -0
  36. package/dist/workflows/index.js.map +1 -1
  37. package/dist/workflows/progress.d.ts +34 -0
  38. package/dist/workflows/progress.js +268 -0
  39. package/dist/workflows/progress.js.map +1 -0
  40. package/dist/workflows/schema.js +21 -1
  41. package/dist/workflows/schema.js.map +1 -1
  42. package/dist/workflows/shell.d.ts +2 -2
  43. package/dist/workflows/shell.js +103 -25
  44. package/dist/workflows/shell.js.map +1 -1
  45. package/dist/workflows/store.d.ts +15 -2
  46. package/dist/workflows/store.js +44 -2
  47. package/dist/workflows/store.js.map +1 -1
  48. package/dist/workflows/types.d.ts +52 -1
  49. package/dist/workflows/updates.d.ts +15 -0
  50. package/dist/workflows/updates.js +188 -0
  51. package/dist/workflows/updates.js.map +1 -0
  52. package/docs/DESIGN_PHILOSOPHY.md +51 -0
  53. package/docs/MONITOR.md +282 -0
  54. package/docs/WORKFLOW_STEP_MESSAGES.md +141 -0
  55. package/docs/WORKFLOW_UPDATES.md +416 -0
  56. package/docs/development.md +7 -3
  57. package/docs/plans/2026-08-13-responsive-workflow-widget-plan.md +11 -3
  58. package/docs/plans/2026-08-16-workflow-updates-plan.md +494 -0
  59. package/docs/run-bundles.md +10 -2
  60. package/docs/workflows.md +57 -17
  61. package/package.json +1 -1
  62. package/src/builtins/catalog.ts +1 -1
  63. package/src/builtins/monitor.workflow.ts +217 -148
  64. package/src/extension/executor.ts +36 -14
  65. package/src/extension/index.ts +89 -23
  66. package/src/extension/step-message.ts +145 -0
  67. package/src/extension/widget.ts +158 -14
  68. package/src/extension/workflow-tool.ts +22 -0
  69. package/src/host/rpc-bridge.ts +37 -14
  70. package/src/host/rpc-executor.ts +35 -14
  71. package/src/viewer/cli.ts +1 -1
  72. package/src/viewer/render.ts +27 -0
  73. package/src/viewer/tui.ts +1 -1
  74. package/src/workflows/engine.ts +117 -4
  75. package/src/workflows/index.ts +32 -0
  76. package/src/workflows/progress.ts +326 -0
  77. package/src/workflows/schema.ts +23 -1
  78. package/src/workflows/shell.ts +109 -26
  79. package/src/workflows/store.ts +67 -2
  80. package/src/workflows/types.ts +78 -1
  81. package/src/workflows/updates.ts +208 -0
@@ -0,0 +1,416 @@
1
+ # Workflow updates
2
+
3
+ This specification defines durable, non-terminal updates from running Pi Workflows nodes. An update reports current state without finishing a node or choosing a graph route.
4
+
5
+ ## Minimal examples
6
+
7
+ A function action publishes current state while it runs:
8
+
9
+ ```ts
10
+ const processRows = action({
11
+ run: async (context) => {
12
+ await context.publishUpdate({
13
+ type: "progress",
14
+ key: "worker-a",
15
+ data: {
16
+ schema: "pi-workflows.progress.v1",
17
+ label: "Worker A",
18
+ status: "running",
19
+ phase: "processing",
20
+ completed: 420,
21
+ total: 1_000,
22
+ unit: "rows",
23
+ },
24
+ });
25
+
26
+ return { processed: 420 };
27
+ },
28
+ });
29
+ ```
30
+
31
+ An agent publishes the same update through the existing `workflow` tool:
32
+
33
+ ```json
34
+ {
35
+ "action": "update",
36
+ "step": "process",
37
+ "attempt": "017f5d57-83f1-4d2d-88e6-3dbf878fed17",
38
+ "update": {
39
+ "type": "progress",
40
+ "key": "worker-a",
41
+ "data": {
42
+ "schema": "pi-workflows.progress.v1",
43
+ "label": "Worker A",
44
+ "status": "running",
45
+ "completed": 420,
46
+ "total": 1000,
47
+ "unit": "rows"
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ The `update` tool action does not complete the agent step. The agent still calls `submit` once with the final step output.
54
+
55
+ ## Place in the workflow model
56
+
57
+ Pi Workflows keeps its current node primitives:
58
+
59
+ - `agent` for model judgment and language work
60
+ - `compute` for pure local calculation
61
+ - `action` for external reads and effects
62
+ - `notify` for durable user messages
63
+ - `checkpoint` for human or external input
64
+
65
+ `shell` remains the command form of `action`. Updates are a capability available while an agent or action runs. They are not a node type.
66
+
67
+ Updates do not:
68
+
69
+ - finish a node
70
+ - write to `outputs` or `results`
71
+ - choose an edge
72
+ - change workflow input
73
+ - execute commands
74
+ - notify the user by themselves
75
+ - trigger a model turn
76
+
77
+ A node's final result remains the only value that completes the node and controls routing.
78
+
79
+ ## Public types
80
+
81
+ The workflow layer adds these public types:
82
+
83
+ ```ts
84
+ export type WorkflowUpdateInput = {
85
+ type: string;
86
+ key: string;
87
+ data: Record<string, unknown>;
88
+ };
89
+
90
+ export type WorkflowUpdateRecord = {
91
+ updateId: string;
92
+ seq: number;
93
+ at: string;
94
+ runId: string;
95
+ nodeId: string;
96
+ attemptId: string;
97
+ type: string;
98
+ key: string;
99
+ data: Record<string, unknown>;
100
+ };
101
+
102
+ export type WorkflowUpdateReceipt = Pick<
103
+ WorkflowUpdateRecord,
104
+ "updateId" | "seq" | "at" | "type" | "key"
105
+ >;
106
+
107
+ export type WorkflowActionContext<TInput = unknown> = WorkflowNodeContext<TInput> & {
108
+ publishUpdate(update: WorkflowUpdateInput): Promise<WorkflowUpdateReceipt>;
109
+ };
110
+ ```
111
+
112
+ Function actions receive `WorkflowActionContext`. Compute and notify callbacks retain the read-only `WorkflowNodeContext`. Prompt builders and validators have the same restriction, as do checkpoint callbacks.
113
+
114
+ ## Update envelope
115
+
116
+ ### `type`
117
+
118
+ `type` names the update contract.
119
+
120
+ Rules:
121
+
122
+ - 1 to 64 characters
123
+ - lowercase ASCII letters, numbers, dots, and hyphens
124
+ - starts with a letter
125
+ - matches `[a-z][a-z0-9.-]{0,63}`
126
+
127
+ `progress` is the first package-defined type. Other update types may define their own data contracts.
128
+
129
+ ### `key`
130
+
131
+ `key` identifies one current item within an update type. The pair `(type, key)` is unique within a run.
132
+
133
+ Rules:
134
+
135
+ - 1 to 128 characters
136
+ - ASCII letters, numbers, dots, underscores, colons, slashes, and hyphens
137
+ - starts with a letter or number
138
+ - matches `[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}`
139
+
140
+ Keys are case-sensitive. A publisher must reuse the same key for the same item throughout a run.
141
+
142
+ ### `data`
143
+
144
+ `data` contains the full current state for the key. It is a non-null JSON object. A new update replaces the current projection for the same `(type, key)` pair. It does not merge with the previous object.
145
+
146
+ The generic update layer allows fields it does not understand inside `data`. A package-defined update type may reject unknown fields.
147
+
148
+ The encoded object must not exceed 64 KiB. Large logs and binary data do not belong in updates. Nodes should return large final values through normal outputs, where the run-bundle artifact rules apply.
149
+
150
+ ### Runtime-owned fields
151
+
152
+ The runtime adds `updateId`, `seq`, `at`, `runId`, `nodeId`, and `attemptId`. Publishers cannot set or override them.
153
+
154
+ `seq` is the sequence of the matching trace event. `at` is the runtime receipt time in RFC 3339 UTC form. `updateId` is unique within the run.
155
+
156
+ ## Agent tool action
157
+
158
+ The `workflow` tool adds this input variant:
159
+
160
+ ```ts
161
+ {
162
+ action: "update";
163
+ step: string;
164
+ attempt: string;
165
+ update: WorkflowUpdateInput;
166
+ }
167
+ ```
168
+
169
+ The engine accepts it only when:
170
+
171
+ - an agent step is active
172
+ - `step` matches the active node id
173
+ - `attempt` matches the active attempt id
174
+ - the attempt has not timed out, failed, completed, or been cancelled
175
+ - the caller still owns the run claim
176
+ - the update passes envelope and type-specific validation
177
+
178
+ A successful call returns `WorkflowUpdateReceipt`. It leaves the agent step open. Invalid calls return a tool error and do not write a trace event.
179
+
180
+ The Pi tool call id is the idempotency key for an agent update. Re-delivery of the same tool call returns the first receipt instead of appending a duplicate update.
181
+
182
+ The existing `status` action remains read-only. It reports what the engine knows and includes the current update projection. It does not publish an update or inspect an external target.
183
+
184
+ ## Function actions
185
+
186
+ Function actions call `await context.publishUpdate(update)`. The promise resolves only after the trace event and state projection are durable.
187
+
188
+ An uncaught validation, fencing, storage, or rate error fails the action. An action may catch a rejected publication when losing a display update should not fail its main work. Cancellation and claim loss still apply.
189
+
190
+ Retries run under a new attempt id. Their updates form a new attempt history even when they reuse the same `(type, key)` pair.
191
+
192
+ ## Shell actions
193
+
194
+ Shell actions may define a line parser:
195
+
196
+ ```ts
197
+ shell({
198
+ exec: () => ({ command: "worker", args: ["--progress=ndjson"] }),
199
+ updates: {
200
+ streams: ["stdout"],
201
+ parseLine: ({ stream, text }, context) => {
202
+ const value = JSON.parse(text);
203
+ if (value.kind !== "progress") return undefined;
204
+ return {
205
+ type: "progress",
206
+ key: value.worker,
207
+ data: value.data,
208
+ };
209
+ },
210
+ },
211
+ });
212
+ ```
213
+
214
+ `streams` may contain `stdout`, `stderr`, or both. It defaults to `stdout`. `parseLine` receives complete UTF-8 lines and may return one update, an array of updates, or `undefined`.
215
+
216
+ The runtime keeps normal stdout and stderr capture. It applies backpressure while parsing and publishing updates. A line longer than 64 KiB, invalid UTF-8, a parser error, or a publication error terminates the command and fails the node. A final line without a newline is parsed when the stream closes.
217
+
218
+ The parser is workflow-author code. Update data never selects or changes the command, arguments, working directory, environment, or privileges.
219
+
220
+ ## Controllers and hosts
221
+
222
+ The engine exposes the same publication operation through its runner interface. A controller or standalone host may publish only for a workflow run and attempt whose claim it owns.
223
+
224
+ Controller resource events remain controller events. They become workflow updates only when a controller deliberately publishes them to a linked workflow run.
225
+
226
+ ## Persistence
227
+
228
+ Each accepted update appends one `update_published` event to `trace.ndjson`:
229
+
230
+ ```json
231
+ {
232
+ "seq": 18,
233
+ "at": "2026-08-16T10:15:30.000Z",
234
+ "scope": "node",
235
+ "type": "update_published",
236
+ "runId": "20260816T100000Z-import-2f81a9c3",
237
+ "nodeId": "process",
238
+ "attemptId": "017f5d57-83f1-4d2d-88e6-3dbf878fed17",
239
+ "payload": {
240
+ "updateId": "upd_01K2GZVY3A4D7X8J9M0N",
241
+ "type": "progress",
242
+ "key": "worker-a",
243
+ "data": {
244
+ "schema": "pi-workflows.progress.v1",
245
+ "status": "running",
246
+ "completed": 420,
247
+ "total": 1000,
248
+ "unit": "rows"
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
254
+ `state.json` adds an optional `updates` array containing only the latest record for each `(type, key)` pair. The array is sorted by `seq`. Omission means that the run has never published an update. New runs write an empty array from their first state projection. The trace remains the full history and source of truth.
255
+
256
+ A resumed run keeps its latest projection and appends new update events. A checkpoint continuation starts a new run with an empty update projection. Updates are scoped to one run and are not carried into continuation bundles.
257
+
258
+ The state projection supports at most 1,024 current `(type, key)` pairs. A publication that would exceed the limit is rejected. Trace history remains append-only.
259
+
260
+ The update envelope is an additive part of `pi-workflows.trace-event.v1` and `pi-workflows.run-state.v1`. Existing bundles without `updates` remain valid under the omission rule. The implementation uses one current schema and does not add migration files, fallback formats, or dual writes. New bundles that contain updates are not required to work with older package releases.
261
+
262
+ ## Ordering and limits
263
+
264
+ Updates use the run store's existing serialized write chain and claim fence. Their trace sequence defines their total order.
265
+
266
+ Default safety limits per run are:
267
+
268
+ | Limit | Value |
269
+ | --------------------------- | --------------------: |
270
+ | Encoded `data` size | 64 KiB |
271
+ | Current `(type, key)` pairs | 1,024 |
272
+ | Sustained publication rate | 20 updates per second |
273
+ | Publication burst | 100 updates |
274
+
275
+ The rate limiter uses a token bucket. A rejected update is not queued silently. Agent callers receive a tool error; code publishers receive a rejected promise.
276
+
277
+ The standalone host and Pi extension apply the same limits.
278
+
279
+ ## Progress update type
280
+
281
+ `progress` is the standard optional profile for measurable work. Each key represents one process, worker, phase owner, or explicit overall total.
282
+
283
+ Minimal progress data:
284
+
285
+ ```json
286
+ {
287
+ "schema": "pi-workflows.progress.v1",
288
+ "status": "running"
289
+ }
290
+ ```
291
+
292
+ Measured progress:
293
+
294
+ ```json
295
+ {
296
+ "schema": "pi-workflows.progress.v1",
297
+ "label": "Worker A",
298
+ "status": "running",
299
+ "phase": "processing",
300
+ "completed": 420,
301
+ "total": 1000,
302
+ "unit": "rows",
303
+ "sourceUpdatedAt": "2026-08-16T10:15:27.000Z",
304
+ "sourceEstimatedFinishAt": "2026-08-16T10:34:00.000Z"
305
+ }
306
+ ```
307
+
308
+ Fields:
309
+
310
+ | Field | Required | Type | Meaning |
311
+ | ------------------------- | ----------- | ------ | ------------------------------------------------ |
312
+ | `schema` | Yes | string | Must equal `pi-workflows.progress.v1`. |
313
+ | `status` | Yes | string | Current state of the track. |
314
+ | `label` | No | string | User-facing name, at most 200 characters. |
315
+ | `phase` | No | string | Stable phase name, at most 128 characters. |
316
+ | `completed` | No | number | Work completed in `unit`. |
317
+ | `total` | No | number | Known total work in `unit`. |
318
+ | `unit` | Conditional | string | Required when `completed` or `total` is present. |
319
+ | `sourceUpdatedAt` | No | string | Time when the target produced the facts. |
320
+ | `sourceEstimatedFinishAt` | No | string | Finish estimate supplied by the target. |
321
+
322
+ `status` is one of `pending`, `running`, `waiting`, `blocked`, `completed`, `failed`, `cancelled`, or `unknown`.
323
+
324
+ `completed` must be finite and at least zero. `total` must be finite, greater than zero, and at least `completed`. `unit` is 1 to 32 printable characters after trimming. Time fields use RFC 3339 with an offset.
325
+
326
+ A progress object is a full snapshot for its key. Omitted optional fields clear their previous values. Publishers mark finished tracks with a terminal status instead of deleting them.
327
+
328
+ The reserved key `overall` represents an explicit aggregate supplied by the workflow. Pi Workflows never combines unrelated tracks automatically. Without `overall`, displays list independent tracks.
329
+
330
+ Unknown fields in `pi-workflows.progress.v1` are validation errors.
331
+
332
+ ## Progress estimation
333
+
334
+ The workflows layer exports pure validation and estimation helpers. Reduction and formatting use the same module. The helpers import no Pi, extension, controller, or viewer code.
335
+
336
+ The estimator groups records by run and key. A new estimation epoch starts when:
337
+
338
+ - `phase` changes
339
+ - `unit` changes
340
+ - `total` changes
341
+ - `completed` decreases
342
+ - a terminal track returns to a non-terminal state
343
+
344
+ It derives elapsed time from runtime update timestamps. `sourceUpdatedAt` reports staleness. Trace sequence orders records.
345
+
346
+ A measured ETA requires a known total and at least two usable samples in the current epoch. The estimator calculates consecutive wall-clock rates over the latest eight usable intervals. Zero-progress intervals remain in the window. Intervals with non-positive elapsed time are ignored.
347
+
348
+ The median interval rate is the central estimate. The 25th and 75th percentile rates form the ETA range. The faster rate gives the lower remaining-time bound and the slower rate gives the upper bound. One usable interval has low confidence. With two through four intervals, a ratio of interquartile range to median no greater than 0.5 gives medium confidence; a wider spread gives low confidence. With five or more intervals, a ratio no greater than 0.25 gives high confidence, a ratio through 0.5 gives medium confidence, and a wider spread gives low confidence. A non-positive median makes ETA unavailable. A non-positive lower rate removes the upper time bound, so the formatter shows the central ETA with low confidence instead of a closed range.
349
+
350
+ A fresh `sourceEstimatedFinishAt` takes priority over a measured ETA and is labelled as a source estimate. It is fresh when it comes from the latest track update, is later than the matching `sourceUpdatedAt` or runtime receipt time, and has not passed. When `sourceUpdatedAt` is absent, the runtime receipt time is the source time. A passed source estimate is expired and does not override a measured estimate. Pi Workflows does not ask a model to invent an ETA. When the target supplies no usable estimate and the samples cannot support one, the formatter says `ETA unavailable` and states the reason.
351
+
352
+ For `waiting` or `blocked` tracks, measured ETA is paused and the display reports the current state. A source estimate may still be shown when the target reports one. For terminal tracks, remaining work and ETA are omitted.
353
+
354
+ The estimator never advances `completed` between samples. Live displays may update elapsed time, sample age, the next scheduled check, and the remaining duration to an estimated finish time.
355
+
356
+ ## Presentation
357
+
358
+ The compact Pi widget and `piw` recognize `progress` updates. Other update types remain visible in trace and step inspection without receiving a special renderer.
359
+
360
+ The compact widget:
361
+
362
+ - behaves as it does today when no progress update exists
363
+ - shows the `overall` track first when present
364
+ - prioritizes failed, blocked, and waiting tracks
365
+ - uses remaining lines for active tracks
366
+ - stays within Pi's 10-line widget limit
367
+ - supports the existing manual scroll controls
368
+ - updates clocks from the existing widget ticker without model calls or state writes
369
+
370
+ A typical line is:
371
+
372
+ ```text
373
+ Worker A 420/1,000 rows ETA 18–20m
374
+ ```
375
+
376
+ A footer line may show:
377
+
378
+ ```text
379
+ Last update 7m ago next check 23m
380
+ ```
381
+
382
+ `piw` shows every track, estimate basis, sample count, confidence, update history, and source timestamps.
383
+
384
+ ## Notifications and model context
385
+
386
+ Publishing an update does not notify the user. A workflow uses the existing `notify` node when it wants a durable report.
387
+
388
+ The extension delivers workflow notifications with a custom Pi message and explicit `triggerTurn: false`. The message remains in session history and later model context, but its arrival does not start an assistant response. Workflow reports do not use `sendUserMessage`.
389
+
390
+ Agent-step instructions use a separate message contract because they must start a model turn. See [WORKFLOW_STEP_MESSAGES.md](WORKFLOW_STEP_MESSAGES.md).
391
+
392
+ ## Validation and errors
393
+
394
+ The implementation must test and reject:
395
+
396
+ - invalid type or key names
397
+ - non-object or non-JSON data
398
+ - oversized data
399
+ - too many current keys
400
+ - rate-limit violations
401
+ - updates for inactive or mismatched attempts
402
+ - updates after timeout or cancellation
403
+ - writes without the current claim
404
+ - malformed shell update lines
405
+ - invalid progress fields
406
+ - duplicate agent tool delivery
407
+
408
+ Validation errors must identify the field and rule. They must not include unrelated update data, private run contents, or command environment values.
409
+
410
+ ## Security and boundaries
411
+
412
+ Updates are data. They do not grant permission to execute a command, retry work, change a target, publish an artifact, or increase spending.
413
+
414
+ Run bundles are private and may contain update data from external systems. Existing bundle permissions and export warnings apply.
415
+
416
+ This feature does not add remote transports, a metrics database, global aggregation, automatic polling, or a new Pi core API. Workflow authors remain responsible for the trust and cost of their agent, action, shell, and controller code.
@@ -15,7 +15,9 @@ tui/ Rust piw viewer and live replay server
15
15
  ```
16
16
 
17
17
  The dependency direction is enforced by `slophammer.yml`. `src/workflows`
18
- imports nothing outside itself and never imports Pi. `src/builtins` contains
18
+ imports nothing outside itself and never imports Pi. Durable updates,
19
+ progress validation, estimation, and text formatting stay in this layer so the
20
+ engine, extension, hosts, and viewers share one contract. `src/builtins` contains
19
21
  package-owned definitions and imports only the public workflow engine.
20
22
  `src/controllers` may import the public workflow engine for child-run
21
23
  scheduling. `src/extension` and `src/host` may also import the built-in catalog.
@@ -38,8 +40,10 @@ in `src/viewer`
38
40
  composes the full detail view (header, graph, step timeline, step inspector)
39
41
  and stays pure so tests can assert on rendered lines.
40
42
 
41
- The Rust viewer keeps graph layout and plain rendering in parity with
42
- `src/render`, then applies ratatui-only presentation through semantic canvas
43
+ The TypeScript and Rust viewers both show progress tracks, sample counts,
44
+ confidence, update time, and ETA from bundle data. The Rust viewer also keeps
45
+ graph layout and plain rendering in parity with `src/render`, then applies
46
+ ratatui-only presentation through semantic canvas
43
47
  roles and `tui/src/theme`. Catppuccin is the default. Theme colors must be
44
48
  chosen in the theme layer rather than directly in UI components. Graph node
45
49
  surfaces are intentional styled spaces, so changing the sparse canvas must
@@ -18,6 +18,11 @@ The live workflow widget must remain easy to scan without using most of the conv
18
18
  - Put the status glyph first and a one-column node-type glyph second.
19
19
  - Use terminal-safe type glyphs: `●` agent, `ƒ` compute, `!` notification, `$` shell action, `*` function action, and `◆` checkpoint.
20
20
  - Show concise runtime details when space permits: repeated visits, active elapsed time, latest completed duration, current status detail, and node errors.
21
+ - Use Pi's supplied theme for TUI colors. Keep RPC and other non-TUI widget output plain.
22
+ - Make the active node unmistakable by coloring its full line with the theme accent and making its name bold.
23
+ - Color a held or waiting focus line with the warning color.
24
+ - Color only the status glyph for completed and failed nodes, color failed error text, and dim pending nodes and non-focused type glyphs.
25
+ - Keep status glyphs as the non-color state signal.
21
26
  - Keep the widget within Pi's 10-line budget.
22
27
  - Ensure every line has a visible width less than or equal to the supplied width.
23
28
  - Preserve manual vertical scrolling for long node lists.
@@ -42,14 +47,14 @@ Optional fields are omitted when they do not apply and truncated from the right
42
47
 
43
48
  Node-type glyphs come from one shared formatter used by both the compact widget and graph viewer. The node snapshot already distinguishes shell actions from function actions, so this change needs no workflow schema change. A shell action named `sleep` appears as `$ sleep`; it does not become a new wait-node type.
44
49
 
45
- The extension installs one Pi widget component for each active workflow. Its `render(width)` method reads the latest workflow state and calls `buildWidgetView` with that width. State changes request a normal Pi render by setting the component again. Pi calls the component with the current width after terminal resizes. The standalone viewer remains the place to inspect the full workflow graph.
50
+ The extension installs one Pi widget component for each active workflow. The documented component factory supplies Pi's current `Theme`. Its `render(width)` method reads the latest workflow state and calls `buildWidgetView` with that width and theme. State changes request a normal Pi render by setting the component again. Pi calls the component with the current width after terminal resizes. RPC mode keeps using serializable string arrays and does not receive color codes. The standalone viewer remains the place to inspect the full workflow graph.
46
51
 
47
52
  ## Contract impact
48
53
 
49
54
  - Session state: no change.
50
55
  - Other persistent data: no change.
51
56
  - Pi internals: no change.
52
- - Public API: documented component-form `ctx.ui.setWidget` and `Component.render(width)`.
57
+ - Public API: documented component-form `ctx.ui.setWidget`, its supplied `Theme`, and `Component.render(width)`.
53
58
 
54
59
  ## Acceptance criteria
55
60
 
@@ -59,7 +64,10 @@ The extension installs one Pi widget component for each active workflow. Its `re
59
64
  - Every rendered line satisfies `visibleWidth(line) <= width`.
60
65
  - Each real node and action subtype uses its documented one-column glyph.
61
66
  - A repeated node shows its visit count, and active and completed nodes show useful timing.
62
- - The active node remains visible in a long workflow.
67
+ - The active node remains visible in a long workflow and its full line uses the theme accent with a bold name.
68
+ - Completed, failed, waiting, and pending states use the specified theme roles without relying on color alone.
69
+ - TUI lines remain width-safe after color codes are added.
70
+ - RPC widget lines contain no ANSI color codes.
63
71
  - Existing scrolling and workflow execution behavior remain unchanged.
64
72
 
65
73
  ## Verification