@osolmaz/pi-workflows 0.15.3 → 0.16.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 (207) hide show
  1. package/README.md +24 -8
  2. package/dist/channels/adapter-entry.d.ts +3 -0
  3. package/dist/channels/adapter-entry.js +203 -0
  4. package/dist/channels/adapter-entry.js.map +1 -0
  5. package/dist/channels/config.d.ts +43 -0
  6. package/dist/channels/config.js +234 -0
  7. package/dist/channels/config.js.map +1 -0
  8. package/dist/channels/protocol.d.ts +94 -0
  9. package/dist/channels/protocol.js +219 -0
  10. package/dist/channels/protocol.js.map +1 -0
  11. package/dist/channels/telegram.d.ts +46 -0
  12. package/dist/channels/telegram.js +285 -0
  13. package/dist/channels/telegram.js.map +1 -0
  14. package/dist/client/activity.d.ts +2 -0
  15. package/dist/client/activity.js +6 -0
  16. package/dist/client/activity.js.map +1 -0
  17. package/dist/client/client.d.ts +102 -0
  18. package/dist/client/client.js +737 -0
  19. package/dist/client/client.js.map +1 -0
  20. package/dist/client/index.d.ts +3 -0
  21. package/dist/client/index.js +3 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/materialize.d.ts +7 -0
  24. package/dist/client/materialize.js +177 -0
  25. package/dist/client/materialize.js.map +1 -0
  26. package/dist/client/protocol.d.ts +60 -0
  27. package/dist/client/protocol.js +275 -0
  28. package/dist/client/protocol.js.map +1 -0
  29. package/dist/client/resolver.d.ts +23 -0
  30. package/dist/client/resolver.js +2 -0
  31. package/dist/client/resolver.js.map +1 -0
  32. package/dist/client/view.d.ts +144 -0
  33. package/dist/client/view.js +3 -0
  34. package/dist/client/view.js.map +1 -0
  35. package/dist/controllers/index.d.ts +1 -1
  36. package/dist/controllers/index.js.map +1 -1
  37. package/dist/controllers/sqlite.d.ts +47 -157
  38. package/dist/controllers/sqlite.js +230 -406
  39. package/dist/controllers/sqlite.js.map +1 -1
  40. package/dist/extension/index.d.ts +23 -1
  41. package/dist/extension/index.js +734 -555
  42. package/dist/extension/index.js.map +1 -1
  43. package/dist/extension/recorder.d.ts +95 -0
  44. package/dist/extension/recorder.js +530 -0
  45. package/dist/extension/recorder.js.map +1 -0
  46. package/dist/extension/remote-recorder-store.d.ts +25 -0
  47. package/dist/extension/remote-recorder-store.js +81 -0
  48. package/dist/extension/remote-recorder-store.js.map +1 -0
  49. package/dist/extension/session-events.d.ts +134 -0
  50. package/dist/extension/session-events.js +60 -0
  51. package/dist/extension/session-events.js.map +1 -0
  52. package/dist/extension/session-view.d.ts +9 -2
  53. package/dist/extension/session-view.js +117 -51
  54. package/dist/extension/session-view.js.map +1 -1
  55. package/dist/extension/step-message.d.ts +6 -4
  56. package/dist/extension/step-message.js +12 -6
  57. package/dist/extension/step-message.js.map +1 -1
  58. package/dist/extension/widget.d.ts +3 -2
  59. package/dist/extension/widget.js +41 -24
  60. package/dist/extension/widget.js.map +1 -1
  61. package/dist/extension/workflow-message-coordinator.d.ts +27 -0
  62. package/dist/extension/workflow-message-coordinator.js +294 -0
  63. package/dist/extension/workflow-message-coordinator.js.map +1 -0
  64. package/dist/host/channel-effects.d.ts +72 -0
  65. package/dist/host/channel-effects.js +271 -0
  66. package/dist/host/channel-effects.js.map +1 -0
  67. package/dist/host/channel-supervisor.d.ts +21 -0
  68. package/dist/host/channel-supervisor.js +54 -0
  69. package/dist/host/channel-supervisor.js.map +1 -0
  70. package/dist/host/child-worker-supervisor.js +1 -1
  71. package/dist/host/child-worker-supervisor.js.map +1 -1
  72. package/dist/host/resolver-entry.d.ts +2 -23
  73. package/dist/host/resolver-entry.js +1 -1
  74. package/dist/host/resolver-entry.js.map +1 -1
  75. package/dist/host/runner.d.ts +54 -8
  76. package/dist/host/runner.js +1997 -305
  77. package/dist/host/runner.js.map +1 -1
  78. package/dist/host/state.d.ts +25 -19
  79. package/dist/host/state.js +303 -89
  80. package/dist/host/state.js.map +1 -1
  81. package/dist/host/view.d.ts +83 -0
  82. package/dist/host/view.js +975 -0
  83. package/dist/host/view.js.map +1 -0
  84. package/dist/host/worker-protocol.js +1 -1
  85. package/dist/host/worker-protocol.js.map +1 -1
  86. package/dist/state/database.d.ts +1 -0
  87. package/dist/state/database.js +15 -0
  88. package/dist/state/database.js.map +1 -1
  89. package/dist/state/prune.d.ts +3 -1
  90. package/dist/state/prune.js +9 -33
  91. package/dist/state/prune.js.map +1 -1
  92. package/dist/state/schema.js +88 -98
  93. package/dist/state/schema.js.map +1 -1
  94. package/dist/state/workflow-messages.d.ts +96 -0
  95. package/dist/state/workflow-messages.js +335 -0
  96. package/dist/state/workflow-messages.js.map +1 -0
  97. package/dist/viewer/backup.d.ts +2 -0
  98. package/dist/viewer/backup.js +28 -0
  99. package/dist/viewer/backup.js.map +1 -0
  100. package/dist/viewer/cli.d.ts +4 -0
  101. package/dist/viewer/cli.js +150 -170
  102. package/dist/viewer/cli.js.map +1 -1
  103. package/dist/viewer/render.js +1 -1
  104. package/dist/viewer/render.js.map +1 -1
  105. package/dist/viewer/tui.d.ts +5 -7
  106. package/dist/viewer/tui.js +245 -108
  107. package/dist/viewer/tui.js.map +1 -1
  108. package/dist/workflows/human-decision.d.ts +2 -0
  109. package/dist/workflows/human-decision.js +8 -2
  110. package/dist/workflows/human-decision.js.map +1 -1
  111. package/dist/workflows/index.d.ts +1 -1
  112. package/dist/workflows/index.js.map +1 -1
  113. package/dist/workflows/settings.d.ts +1 -8
  114. package/dist/workflows/settings.js.map +1 -1
  115. package/dist/workflows/store.d.ts +67 -20
  116. package/dist/workflows/store.js +451 -378
  117. package/dist/workflows/store.js.map +1 -1
  118. package/dist/workflows/tool-input.d.ts +21 -0
  119. package/dist/workflows/tool-input.js +23 -1
  120. package/dist/workflows/tool-input.js.map +1 -1
  121. package/dist/workflows/types.d.ts +5 -5
  122. package/dist/workflows/workflow-message-content.d.ts +38 -0
  123. package/dist/workflows/workflow-message-content.js +157 -0
  124. package/dist/workflows/workflow-message-content.js.map +1 -0
  125. package/docs/2026-08-18-herdr-piw-plan.md +2 -1
  126. package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
  127. package/docs/2026-08-25-workflow-follow-ups.md +26 -50
  128. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  129. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
  130. package/docs/2026-09-01-unified-workflow-client-plan.md +385 -0
  131. package/docs/2026-09-02-installed-live-e2e-plan.md +239 -0
  132. package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
  133. package/docs/DEFERRED_TURNS.md +66 -294
  134. package/docs/HUMAN_DECISIONS.md +29 -30
  135. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
  136. package/docs/SQLITE_STATE.md +31 -25
  137. package/docs/WORKFLOW_HOST.md +155 -77
  138. package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
  139. package/docs/development.md +2 -1
  140. package/docs/live-replay-protocol.md +74 -132
  141. package/docs/tui-viewer.md +12 -14
  142. package/docs/workflows.md +78 -28
  143. package/herdr-plugin.toml +1 -1
  144. package/package.json +9 -3
  145. package/plugins/herdr/viewer.mjs +14 -5
  146. package/protocol/client.v1.schema.json +137 -0
  147. package/protocol/fixtures/client-v1.json +23 -0
  148. package/src/channels/adapter-entry.ts +220 -0
  149. package/src/channels/config.ts +296 -0
  150. package/src/channels/protocol.ts +333 -0
  151. package/src/channels/telegram.ts +335 -0
  152. package/src/client/activity.ts +6 -0
  153. package/src/client/client.ts +939 -0
  154. package/src/client/index.ts +25 -0
  155. package/src/client/materialize.ts +228 -0
  156. package/src/client/protocol.ts +333 -0
  157. package/src/client/resolver.ts +26 -0
  158. package/src/client/view.ts +163 -0
  159. package/src/controllers/index.ts +0 -1
  160. package/src/controllers/sqlite.ts +339 -808
  161. package/src/extension/index.ts +853 -665
  162. package/src/extension/recorder.ts +687 -0
  163. package/src/extension/remote-recorder-store.ts +126 -0
  164. package/src/extension/session-events.ts +119 -0
  165. package/src/extension/session-view.ts +157 -51
  166. package/src/extension/step-message.ts +19 -9
  167. package/src/extension/widget.ts +53 -25
  168. package/src/extension/workflow-message-coordinator.ts +334 -0
  169. package/src/host/channel-effects.ts +465 -0
  170. package/src/host/channel-supervisor.ts +73 -0
  171. package/src/host/child-worker-supervisor.ts +1 -1
  172. package/src/host/resolver-entry.ts +11 -26
  173. package/src/host/runner.ts +2639 -450
  174. package/src/host/state.ts +404 -137
  175. package/src/host/view.ts +1212 -0
  176. package/src/host/worker-protocol.ts +1 -1
  177. package/src/state/database.ts +13 -0
  178. package/src/state/prune.ts +14 -42
  179. package/src/state/schema.ts +88 -98
  180. package/src/state/workflow-messages.ts +548 -0
  181. package/src/viewer/backup.ts +29 -0
  182. package/src/viewer/cli.ts +171 -185
  183. package/src/viewer/render.ts +1 -5
  184. package/src/viewer/tui.ts +261 -124
  185. package/src/workflows/human-decision.ts +18 -2
  186. package/src/workflows/index.ts +0 -1
  187. package/src/workflows/settings.ts +1 -20
  188. package/src/workflows/store.ts +650 -511
  189. package/src/workflows/tool-input.ts +36 -1
  190. package/src/workflows/types.ts +5 -5
  191. package/src/workflows/workflow-message-content.ts +197 -0
  192. package/dist/extension/decision-channels.d.ts +0 -134
  193. package/dist/extension/decision-channels.js +0 -1307
  194. package/dist/extension/decision-channels.js.map +0 -1
  195. package/dist/host/client.d.ts +0 -48
  196. package/dist/host/client.js +0 -216
  197. package/dist/host/client.js.map +0 -1
  198. package/dist/host/protocol.d.ts +0 -38
  199. package/dist/host/protocol.js +0 -156
  200. package/dist/host/protocol.js.map +0 -1
  201. package/dist/viewer/watch.d.ts +0 -6
  202. package/dist/viewer/watch.js +0 -46
  203. package/dist/viewer/watch.js.map +0 -1
  204. package/src/extension/decision-channels.ts +0 -1826
  205. package/src/host/client.ts +0 -293
  206. package/src/host/protocol.ts +0 -196
  207. package/src/viewer/watch.ts +0 -51
@@ -1,175 +1,117 @@
1
- # Live replay protocol
1
+ # Live client protocol
2
2
 
3
- The Rust viewer (`tui/`) can read SQLite directly or connect to `piw serve`. Both modes use the same bounded viewer projection. The protocol ID is `pi-workflows.replay.v1`.
3
+ Pi Workflows uses one live client protocol for the Pi extension, the TypeScript CLI, local `piw`, and remote `piw` through the loopback relay.
4
4
 
5
- The server reads SQLite and never writes it. It accepts loopback addresses only. Remote use goes through an SSH tunnel. The server rejects WebSocket handshakes with an `Origin` header so a web page cannot read workflow state from localhost.
5
+ The protocol ID is `pi-workflows.client.v1`. Its schema is [`protocol/client.v1.schema.json`](../protocol/client.v1.schema.json). TypeScript and Rust use the same valid and invalid fixture corpus.
6
6
 
7
- ## Framing
7
+ The host is the only process that reads or writes the active SQLite database. A client protocol or package-version mismatch does not mean that SQLite state is incompatible. The client stops and asks for matching `pi-workflows` and `piw` packages.
8
8
 
9
- The endpoint is `/ws`. Each message is one JSON object with a `type` field. Unknown message types and fields are ignored. The server sends `hello` first. A client disconnects when it does not support the protocol ID.
9
+ ## Transports
10
10
 
11
- ```json
12
- { "type": "hello", "protocol": "pi-workflows.replay.v1" }
11
+ The local transport is a user-only Unix socket on Unix systems:
12
+
13
+ ```text
14
+ ~/.pi/agent/workflows/host/host.sock
13
15
  ```
14
16
 
15
- ## Bounded run view
17
+ On Windows, the same client uses the package-derived `\\.\pipe\pi-workflows-<state-directory-hash>` named pipe. Local `piw`, `piw serve`, and the TypeScript client derive the same endpoint from the workflow state directory. A foreground TypeScript cold start keeps its retry wait referenced until the detached host becomes ready or the start deadline expires. Only background reconnect timers are unreferenced.
18
+
19
+ Each message is one canonical JSON object followed by a newline. TypeScript and Rust use the same ECMAScript number formatting and UTF-16 object-key order, including for arbitrary workflow JSON. A message can be at most 1 MiB. Unknown envelope fields, non-canonical JSON, and invalid framing close only the offending connection. If socket backpressure delays a client write, connection close, socket error, or request cancellation ends the wait instead of leaving the request pending.
20
+
21
+ `piw serve` provides the remote transport at `/ws`. It binds to loopback only. Each WebSocket connection has one matching host-socket connection, and their lifecycles are coupled. The relay forwards one canonical JSON object per text frame. It does not read SQLite, translate views, multiplex clients, or retain state.
22
+
23
+ Remote clients use an SSH tunnel to reach the loopback relay.
24
+
25
+ ## Envelope
16
26
 
17
- A snapshot contains one bounded run view:
27
+ The protocol has four message types:
28
+
29
+ - `hello` identifies the protocol connection and package version.
30
+ - `request` carries one operation, request ID, client ID, idempotency key, optional run ID and revision, and payload.
31
+ - `response` settles one request with an outcome, optional revision, receipt, or safe error.
32
+ - `event` carries a revisioned run list, run snapshot, run patch, run page, session snapshot, or unavailable condition.
33
+
34
+ The host sends `hello` first:
18
35
 
19
36
  ```json
20
37
  {
21
- "presentationRevision": 42,
22
- "graphRevision": 17,
23
- "manifest": { … },
24
- "workflow": { … },
25
- "graphScene": {
26
- "ranks": [ … ],
27
- "edges": [ … ],
28
- "segments": [ … ],
29
- "rankOfNode": { … }
30
- },
31
- "graphSteps": [ … ],
32
- "takenTransitions": [ "prepare->run" ],
33
- "stepStart": 768,
34
- "stepTotal": 1000,
35
- "state": {
36
- "steps": [ … ]
37
- },
38
- "tracePage": {
39
- "presentationRevision": 42,
40
- "start": 768,
41
- "total": 1000,
42
- "items": [ … ]
43
- },
44
- "session": {
45
- "presentationRevision": 42,
46
- "binding": { … },
47
- "entryPage": {
48
- "presentationRevision": 42,
49
- "start": 768,
50
- "total": 1000,
51
- "items": [ … ]
52
- },
53
- "eventPage": {
54
- "presentationRevision": 42,
55
- "start": 768,
56
- "total": 1000,
57
- "items": [ … ]
58
- },
59
- "capture": { … }
60
- },
61
- "settingsScopes": [ … ],
62
- "followUpQueue": { … },
63
- "live": true,
64
- "possiblyInterrupted": false
38
+ "connectionId": "connection-1",
39
+ "packageVersion": "0.15.3",
40
+ "schema": "pi-workflows.client.v1",
41
+ "type": "hello"
65
42
  }
66
43
  ```
67
44
 
68
- Each step, trace, session-entry, and session-event page contains at most 256 rows. `graphSteps` contains at most one latest attempt per node at the replay cursor. `takenTransitions` contains distinct transitions up to that cursor. `graphScene` is the retained language-neutral rank and route plan shared by Rust and TypeScript.
69
-
70
- A snapshot does not contain complete trace or session history. A replay jump fetches the page that contains the requested zero-based cursor.
71
-
72
- ## Revisions and target patches
73
-
74
- Each viewer-visible SQLite transaction advances the run presentation revision and commits ordered target patches with the same transaction. The server reads those patches. It does not build complete old and new run views to compare them.
45
+ A request uses a stable request ID and idempotency key:
75
46
 
76
47
  ```json
77
48
  {
78
- "type": "run_patch",
79
- "runId": "run-1",
80
- "revision": 43,
81
- "targets": [
82
- {
83
- "targetType": "conversation",
84
- "targetKey": "entries:tail",
85
- "patch": [
86
- { "op": "replace", "path": "/presentationRevision", "value": 43 },
87
- { "op": "remove", "path": "/items/0" },
88
- { "op": "append", "path": "/items", "value": [ { "seq": 1001, … } ] },
89
- { "op": "replace", "path": "/start", "value": 745 },
90
- { "op": "replace", "path": "/total", "value": 1001 }
91
- ]
92
- }
93
- ]
49
+ "clientId": "client-1",
50
+ "idempotencyKey": "status-1",
51
+ "operation": "host.status",
52
+ "payload": {},
53
+ "requestId": "request-1",
54
+ "schema": "pi-workflows.client.v1",
55
+ "type": "request"
94
56
  }
95
57
  ```
96
58
 
97
- The patch set supports `add`, `replace`, `remove`, and `append`. `append` adds each value to the target array in order. Sliding tail pages keep 256 rows by removing old leading rows when necessary. Session-event pages stay aligned to 256-event checkpoint boundaries. They append inside one block and request the next page when a write crosses a boundary.
59
+ The closed outcomes are `accepted`, `adopted`, `rejected`, `conflict`, `notFound`, `claimLost`, and `unavailable`.
98
60
 
99
- A patch targets one bounded document or page. A client applies a tail patch only when it holds that tail page. Older loaded pages stay valid because committed history is immutable. A target that needs a fresh bounded projection causes a snapshot. This still avoids complete-run reads and complete-run JSON comparison.
61
+ ## Run and session views
100
62
 
101
- Revisions must arrive in order. Duplicate state is harmless because a client ignores an old revision. A wrong run, malformed patch, missing path, stale page, future revision, or gap cannot replace the last good view. A gap or a cursor older than retained patches causes a bounded snapshot.
63
+ The host produces `pi-workflows.run-view.v1` from one consistent database read. The view contains the bounded workflow state, graph, trace, session projection, page cursors, presentation revision, and one `display` object. A terminal display includes the stored failure reason, not only its machine error code. A reason above the shared 16 KiB inline-content threshold uses a small `reason` notice and a digest-bound `reasonContent` reference. This keeps the run list below the 1 MiB frame limit and keeps the complete diagnostic available.
102
64
 
103
- The database retains 256 presentation revisions per run. The server does not replay an unbounded patch backlog.
65
+ The closed display statuses are:
104
66
 
105
- ## Pages
67
+ - `queued`
68
+ - `running`
69
+ - `waiting`
70
+ - `paused`
71
+ - `completed`
72
+ - `failed`
73
+ - `timed_out`
74
+ - `cancelled`
75
+ - `ambiguous`
106
76
 
107
- A client asks for a page with `fetch_page`:
77
+ Only the host computes this status. A parked queue is not a pause. `paused` requires the durable pause flag. An exact live worker or origin-session turn is `running`. An effect being applied by that live worker is still `running`; only a durable `ambiguous` effect that needs operator action is `ambiguous`. `unavailable` is a client connection condition, not a run status.
108
78
 
109
- ```json
110
- {
111
- "type": "fetch_page",
112
- "runId": "run-1",
113
- "kind": "session_events",
114
- "cursor": 20000
115
- }
116
- ```
79
+ The run list uses the same display object. The host sends it as revision-bound `pi-workflows.run-list-page.v1` pages. Each page reads only lightweight run status and source facts. It does not load input, launch options, steps, trace, or session history. TypeScript and Rust clients collect all pages for one revision before they replace the visible list. If the revision changes, they discard the partial list and start from the next subscription event.
117
80
 
118
- `kind` is one of `steps`, `trace`, `trace_at_step`, `session_entries`, `session_events`, `settings`, `follow_ups`, or `updates`. `trace_at_step` uses a step index as its cursor and returns the trace page around that step's timestamp. The server answers with `run_page`:
81
+ An origin-session subscription returns `pi-workflows.session-view.v1`. It contains the active run view first. When no run is active, it keeps the most recent terminal run while its terminal workflow message is pending or its first model turn is open, and then for 60 seconds after that turn ends. The same response contains an ordered byte-bounded window of all nonterminal workflow messages and open sent messages needed for recovery, their complete count, and the next eligible pending message only for the active coordinator epoch.
119
82
 
120
- ```json
121
- {
122
- "type": "run_page",
123
- "runId": "run-1",
124
- "revision": 43,
125
- "kind": "session_events",
126
- "cursor": 20000,
127
- "start": 19872,
128
- "total": 48620,
129
- "items": [ … ]
130
- }
131
- ```
83
+ The Pi extension assembles the complete run revision and workflow-message content before it updates the widget or coordinator. After every host connection, it reports the active branch before it sends a workflow message or reports a model turn. The report can name only message IDs from the complete session-view window. The extension always uses the exact run ID from that view. It does not select the latest run separately. An idle session does not create empty commands.
84
+
85
+ A snapshot page contains at most 256 items and also has a byte budget. `view.page` returns another byte-bounded window that contains the requested cursor. Page responses use `pi-workflows.run-page.v1` and echo the requested cursor and run-view revision. A client applies a page only when both still match its current request and snapshot. A step-centered trace request selects the exact stored attempt first and uses its node only when that attempt has no trace event. Large workflow topology has bounded node, edge, graph-step, and transition projections plus durable content references for the complete original definition and complete graph history. TypeScript clients assemble every run-history page for that revision and hydrate the complete definition and all referenced content before they emit a complete non-interactive view or update the Pi widget. TypeScript and Rust use the same verified content loader for complete graph steps and transitions. Rust also requests, verifies, and decodes the complete referenced definition before it builds the graph layout. A session-event page also carries the replay checkpoint for the exact sequence before its first item, so reducing the page does not lose earlier active messages or tool calls. The checkpoint can itself be a durable content reference. TypeScript hydrates it with the run view, and Rust requests and resolves it before replay.
86
+
87
+ The host counts histories first and reads only the selected SQLite ranges. An unchanged subscription uses a lightweight revision check and reuses its prior view. It does not rebuild complete histories every 250 milliseconds.
132
88
 
133
- Page reads use bounded ranges. The response echoes the requested `cursor` and carries the presentation revision read in the same SQLite snapshot as its rows. A client ignores an older response when a newer cursor is pending. A step page also returns `graphCursor`, `graphSteps`, and `takenTransitions` for that exact replay point, even when the selected step was already in the prior page. A historical session-event page can return `replayCheckpoint`. The checkpoint contains only active message and tool state at the page boundary. The writer stores it at each 256-event boundary, so a page jump reads one checkpoint blob instead of predecessor event rows. It lets the client continue the temporal reducer without loading predecessor event pages. The client also requests the related entry page. Settings, follow-up, and current-update pages keep the Info inspector complete without loading every record. A page request does not change the shared watched-run projection or another client's cursor.
89
+ Large prompt, output, event, settings, follow-up, and update values use a content reference instead of making a protocol frame exceed 1 MiB. `view.content` returns the referenced UTF-8 content in verified chunks. The reference includes its media type, byte count, SHA-256 digest, and an opaque marker for host-created references. The host saves generated view content directly under its exact run ID, content digest, and media type before it advertises the reference. It does not share media metadata with general state blobs. A request for another run or media representation is unavailable. Memory-cache eviction therefore cannot make an advertised aggregate unavailable. Run pruning removes the durable content. Clients reassemble all chunks and verify the bytes against both the content response and the advertised reference before display. Opaque content is restored as user data without interpreting nested objects as host references. Other cursors and content references keep the complete logical history and result available.
134
90
 
135
- ## Messages
91
+ ## Subscriptions and reconnection
136
92
 
137
- Client to server:
93
+ A client keeps one persistent connection and records its desired run-list, run, and origin-session subscriptions. A request to watch a run that does not exist returns `notFound` and does not install a subscription. TypeScript and Rust clients show that response instead of waiting for a snapshot. Explicit `piw <runId>` mode keeps that run selected even when the run list contains a newer run. After reconnection, the client sends accepted subscriptions again with its run revision. The host sends a bounded snapshot when the client needs one. The protocol also supports retained revision patches. Unsubscribing sends the subscription ID to the host for every subscription kind, so no unused snapshot work remains on a live connection.
138
94
 
139
- | Type | Fields | Meaning |
140
- | ---------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
141
- | `watch_runs` | none | Subscribe to run-list rows. |
142
- | `watch_run` | `runId`, optional `revision`, `stepCursor`, `traceCursor`, `sessionEntryCursor`, `sessionEventCursor` | Subscribe or resume one run. |
143
- | `unwatch_run` | `runId` | End one run subscription. |
144
- | `fetch_page` | `runId`, `kind`, `cursor` | Read one bounded page. |
145
- | `fetch_artifact` | `runId`, `path` | Unsupported for SQLite state; returns `error`. |
95
+ A slow or disconnected client cannot stop the host, another client, claim renewal, or workflow execution. The host waits for socket drain before it publishes another snapshot to that connection. Polling coalesces while the connection is blocked, so the socket buffer cannot grow by one snapshot on every poll. When a connection closes, the host removes its subscriptions and active coordinator epoch. It does not infer that an open model turn ended.
146
96
 
147
- Server to client:
97
+ ## Origin-session activity
148
98
 
149
- | Type | Fields | Meaning |
150
- | -------------- | ------------------------------------------------------ | ---------------------------------------- |
151
- | `hello` | `protocol` | Identify the protocol. |
152
- | `runs` | `runs` | Send all lightweight run-list rows. |
153
- | `run_snapshot` | `runId`, `revision`, `view` | Send one bounded run view. |
154
- | `run_patch` | `runId`, `revision`, `targets` | Apply direct bounded target patches. |
155
- | `run_page` | `runId`, `revision`, `kind`, `start`, `total`, `items` | Return one bounded page. |
156
- | `artifact` | `runId`, `path`, `content` | Reserved and not sent by SQLite servers. |
157
- | `error` | `message`, optional `runId` | Report a sanitized request failure. |
99
+ The Pi extension uses `workflowTurn.report` to record `started` and `ended` for the exact workflow message, workflow turn, run, and origin session. There is no refresh, activity lease, heartbeat, or sequence counter. A started turn keeps the host-produced display status `running` until the matching end is accepted. A stale end cannot clear a newer turn.
158
100
 
159
- The run list contains `presentationRevision`, `manifest`, `live`, and `possiblyInterrupted`. It contains no payload bodies.
101
+ The host accepts a start only for an open sent workflow message in that origin session. A step message is open while its interaction remains pending and its run is not paused. A terminal or follow-up message is open until its first turn ends. Decisions and notifications never open model turns. Turn binding does not inspect branch membership because documented `agent_start` has no message payload; branch reporting owns entry adoption and branch-specific re-presentation.
160
102
 
161
- ## Several clients
103
+ If the workflow message or session view is still loading, the extension buffers the matching start and end and reports them in order after the message is confirmed `sent`. Activity changes display only. It does not grant workflow authority, renew a run claim, or settle an interaction. Host startup does not close an open Pi turn. Only a later idle-session active-branch report can close a proved-lost turn.
162
104
 
163
- The server keeps one projection and graph scene for each watched run. The first watcher loads it. Later watchers reuse it. The last unwatch or disconnect releases it. Different watched runs load independently.
105
+ ## Commands and uncertain results
164
106
 
165
- Each client keeps its own revision and page cursors. Network sends happen outside the shared state lock. A slow client cannot stop another client. If a broadcast receiver falls behind, that client receives a bounded snapshot.
107
+ Durable commands use stable idempotency keys. The Pi extension routes each state-changing command through the durable client request path. If the connection closes after the host commits but before the response arrives, the client reconnects with a new request ID and adopts the stored result. A retry with the same durable identity and payload adopts the stored receipt. Reusing that identity with another payload is a conflict. The request ID identifies one transport attempt and is not part of the durable request fingerprint. A retry after a local abort uses a new request ID and keeps the durable idempotency and submission IDs, so a late response from the aborted attempt cannot settle the retry.
166
108
 
167
- ## Reconnection
109
+ An `interaction.submit` response stays open while the supervised workflow child validates the value. The response settles only after the durable result is accepted, adopted, or rejected. A tool abort stops waiting immediately but does not undo an accepted host command. A later retry adopts the durable outcome. The host waits on the stored submission ID returned by adoption, not a different ID from the retry attempt. Clients do not poll SQLite.
168
110
 
169
- The client keeps the run list and selected run as desired state. After a disconnect, it keeps cached content visible with a stale or reconnecting label. It retries with bounded backoff, sends `watch_runs` after the next valid `hello`, and resumes `watch_run` from its revision and page cursors. A retained cursor receives patches. A stale cursor receives a bounded snapshot and requested pages.
111
+ The protocol does not claim exactly-once behavior for an external system that cannot prove it. An uncertain non-idempotent effect becomes ambiguous and requires explicit recovery.
170
112
 
171
- ## SQLite consistency
113
+ ## Maintenance
172
114
 
173
- The server uses a query-only SQLite connection. A writer commits the domain change, presentation revision, and patch records atomically. A reader sees all of that transaction or none of it.
115
+ Active `state.status`, `state.verify`, `state.backup`, and `state.prune` requests run in the host against its existing database connection. The CLI uses one stable client identity and creates a fresh idempotency key for each backup or applied prune invocation. If that invocation loses its connection, the client reconnects once with a new request ID and the same invocation key, so the host adopts the exact in-flight or stored result. A later user invocation gets a new key and does not reuse a stale success or rejection. The host keeps an in-flight maintenance command alive after a client disconnect, stores its accepted or rejected receipt before it responds, and adopts the exact retry instead of running the operation again. Host shutdown waits for in-flight maintenance receipts. `state.status` returns the database file size and safe counts for resources, runs, controllers, decisions, settings scopes, pending interactions, pending follow-ups, active leases, and unsettled effects.
174
116
 
175
- The refresh timer first checks `PRAGMA data_version`. An unchanged value causes no run-index query and no payload read. A changed value refreshes lightweight rows and only the watched projections whose presentation revisions changed.
117
+ Only `pi-workflows state verify <inactive-backup>` opens SQLite outside the host. It uses a query-only TypeScript connection and rejects the active database, including another path to the same file.
@@ -9,11 +9,13 @@ a recorded Pi conversation, themes, and remote viewing.
9
9
 
10
10
  The viewer uses the [incremental and virtualized viewer design](plans/2026-08-28-piw-incremental-viewer-plan.md).
11
11
 
12
- The run browser reads small metadata rows. It does not load trace, step, session, settings, or follow-up payloads. The local viewer checks SQLite `data_version` on its timer. When the value is unchanged, the timer does not scan runs or read payloads.
12
+ The run browser subscribes to small host-owned metadata views. It does not load trace, step, session, settings, or follow-up payloads. The host publishes a new bounded view only when its content changes.
13
13
 
14
- The selected run contains bounded pages. Step, trace, session-entry, and session-event pages contain at most 256 rows. Replay can jump to any position. The viewer loads the page that contains that position and keeps only the current windows. A compact graph projection keeps the latest attempt for each node and the taken transitions up to the replay point.
14
+ Each live view keeps the host's `display` value separate from the durable workflow `state`. The run browser, current-run status, timeline, and latest graph use `display` directly. During an origin-session model turn, the latest graph presents the durable `waitingOn` node as running. Replay continues to use durable state and recorded history. `piw` does not calculate another live status.
15
15
 
16
- Local page reads run outside input and drawing through one overwrite-only request slot. A newer selection replaces pending work. A failed first read leaves the run browser usable. A failed refresh keeps the last good view and marks it stale.
16
+ The selected run contains bounded pages. Step, trace, session-entry, session-event, settings, follow-up, and update pages have both a row limit and a byte budget. Replay can jump to any position. The viewer loads the page that contains that position and keeps only the current windows. A session-event page includes the replay checkpoint immediately before its first event. A compact graph projection keeps the latest attempt for each node and the taken transitions up to the replay point.
17
+
18
+ Large values use host content references. `piw` fetches workflow definitions, graph history, and complete host display reasons before it publishes the related live view. It fetches other large details when the user opens them. It verifies the byte count and SHA-256 digest before it shows the complete text or JSON value. Page and content requests run outside input and drawing through the shared client protocol. A newer page selection replaces the previous request, including when the user returns to an earlier page. A failed first read leaves the run browser usable. A failed refresh keeps the last good view and marks it stale.
17
19
 
18
20
  ## Install
19
21
 
@@ -26,17 +28,15 @@ cargo install pi-workflows
26
28
 
27
29
  ## Modes
28
30
 
29
- - `piw` browses `~/.pi/agent/workflows/state.sqlite` through a read-only SQLite connection.
30
- - `piw <runId>` opens one run from that database.
31
- - `piw serve [--bind 127.0.0.1:9377]` exposes database-backed run views over the
32
- [live replay protocol](live-replay-protocol.md). Only
33
- loopback addresses are accepted; use an SSH tunnel for remote viewing.
31
+ - `piw` connects to the local package-owned workflow host. If the socket is absent, it runs the installed `pi-workflows host start` command.
32
+ - `piw <runId>` opens one host-owned run view.
33
+ - `piw <runId> --once` waits for that run, renders one complete 120 × 40 plain-text frame, and exits. It returns a nonzero status for host, protocol, missing-run, or snapshot-timeout failures.
34
+ - `piw serve [--bind 127.0.0.1:9377]` relays each WebSocket connection to one host socket over the [live client protocol](live-replay-protocol.md). Only loopback addresses are accepted; use an SSH tunnel for remote viewing.
34
35
  - `piw --connect ws://…` reads from another `piw serve` process.
35
36
  - `piw --theme <name>` selects a theme for this invocation.
36
37
  - `piw --list-themes` prints the built-in theme names.
37
38
 
38
- Direct database mode and connected mode use the same semantic run view.
39
- The protocol is the network form of that view.
39
+ Local and remote modes use the same semantic run view and the same protocol. `piw` has no SQLite mode or database schema copy.
40
40
 
41
41
  ## Herdr
42
42
 
@@ -211,11 +211,9 @@ listing and selected-run subscription after the server returns. Cached content
211
211
  stays visible but is labeled reconnecting or disconnected, never current.
212
212
  Revision gaps force a bounded snapshot.
213
213
 
214
- `piw serve` keeps one loaded projection and one graph scene for each watched run. The first watcher loads it. Later watchers reuse it. The last unwatch or disconnect releases it. Clients keep separate revision and page cursors, so one client's replay jump does not move another client. A lagged client gets a bounded snapshot instead of an unbounded patch backlog.
214
+ `piw serve` is a frame relay. It keeps no run projection or client activity after either side closes. Clients keep separate revision and page cursors, so one client's replay jump does not move another client. A lagged client gets a bounded snapshot instead of an unbounded patch backlog.
215
215
 
216
- Expanded prompt and output fields come from content-addressed SQLite blobs.
217
- The local reader uses query-only mode. Remote snapshots carry the same bounded
218
- semantic view and do not expose a filesystem path.
216
+ The host resolves expanded prompt and output fields from durable content. Local and remote snapshots carry the same bounded semantic view and do not expose a database path.
219
217
 
220
218
  ## Interaction
221
219
 
package/docs/workflows.md CHANGED
@@ -126,6 +126,8 @@ pi-workflows host run
126
126
  `host run` stays attached. The other commands start, inspect, or stop the
127
127
  on-demand process. No command installs an operating-system service.
128
128
 
129
+ The extension, CLI, and `piw` use the same version-1 client protocol over a Unix socket or Windows named pipe. The host sends byte-bounded run-list and run-view pages. Clients collect a complete run list for one revision and reject stale run pages whose cursor or revision no longer matches. The host reads only the selected history ranges and reuses an unchanged subscribed view after a lightweight revision check. It waits for a slow socket to drain and removes every subscription when its client unsubscribes. Large values stay available through verified content chunks. Host-generated aggregate values are saved and linked to the run before the host advertises them.
130
+
129
131
  A worker verifies the root and all mounted source identities before it loads
130
132
  workflow modules. It then checks the resolved mounted-source map and executes
131
133
  from committed state through a host-backed store. A source mismatch parks the
@@ -145,9 +147,11 @@ provisional. A new supervised worker loads the workflow and runs its `validate`
145
147
  function before the host accepts the submission. A validation error leaves the
146
148
  same request pending and returns the error to the model. Closing Pi leaves that
147
149
  request pending; reopening the same session adopts the existing session entry
148
- or presents it once. Notifications use the durable session outbox. A root
149
- `presentationPrompt` creates a durable terminal turn only after completion is
150
- committed. A controller child without an origin session can use a supervised
150
+ or presents it once. Step prompts, protected decisions, notifications, terminal
151
+ results, and follow-ups use the host-owned `workflow_messages` table and one
152
+ extension sender. Initial, reminder, and resumed prompts are the same step-message
153
+ kind with different display reasons. A terminal workflow message becomes eligible only after
154
+ the terminal outcome is committed. A controller child without an origin session can use a supervised
151
155
  headless `pi --mode rpc` child for structured agent steps.
152
156
 
153
157
  Pause stops the worker and parks at the last durable boundary. Resume takes a
@@ -193,17 +197,24 @@ For submitted output, the engine appends the existing workflow-tool contract.
193
197
  The host checks the durable transport identifiers, stores a `validating`
194
198
  submission, and starts a supervised worker. In that worker, the output passes
195
199
  through tolerant JSON normalization and then `validate`. The tool reports
196
- success only after this check accepts the output. Rejected submissions return
200
+ success only after this check accepts the output. If the tool turn is aborted,
201
+ the client stops waiting but does not cancel the durable host command. A retry
202
+ uses a new transport request ID with the same durable submission identity and
203
+ adopts the stored result. Rejected submissions return
197
204
  the validation error and can retry in the same step. If the model settles
198
- without submitting, the durable request stays pending until it receives valid
199
- output, times out, or is cancelled. For assistant-message output, the engine appends a normal-response contract,
205
+ without submitting, the host increments the request's unproductive-turn counter
206
+ and can create at most two step messages with reason `reminder`. The next
207
+ unproductive turn fails the step. The timeout remains active during each
208
+ reported model turn, and cancellation remains active throughout. For assistant-message output, the engine appends a normal-response contract,
200
209
  waits for `agent_settled`, rejects empty, failed, aborted, or tool-only results,
201
210
  and never suppresses the visible text. Timeout and cancellation abort either
202
211
  form's active Pi turn.
203
212
 
204
213
  `timeoutMs` can be a finite positive number, `null`, or a function of the normal
205
214
  node context that returns either value. Omit it to use the 15-minute engine
206
- default. Set it to `null` to disable only the wall-clock deadline; cancellation,
215
+ default. The limit counts active node execution. For an origin-session agent
216
+ node, it counts reported model-turn time from an active connected Pi session.
217
+ It excludes message delivery, waiting, paused time, disconnects, and host downtime. Set it to `null` to disable only this deadline; cancellation,
207
218
  parking, claim loss, shutdown, and the node's abort signal still work. A timeout
208
219
  function can use prepared outputs to select a policy for this run. It has 30
209
220
  seconds to return. Computed timeout functions are runtime code, so definition
@@ -368,7 +379,7 @@ humanDecision({
368
379
  });
369
380
  ```
370
381
 
371
- The waiting run stores a versioned request and asks every channel configured for the logical audience. The structured `subject` remains machine data. Channels receive only the normalized `presentation`, title, choices, input prompts, and any deadline policy. The first valid verified human answer wins. When `onTimeout` is present and no human answer wins before the saved deadline, the host takes a control claim on the waiting parent and atomically applies the validated response with `timeout` provenance, closes the interaction, and reserves the continuation. This policy can continue without a configured channel. A continuation preserves the original workflow input and exposes the resolved response as the checkpoint output. `humanDecisionEdge()` provides exhaustive routing for the choices. Existing `body` requests remain a legacy compatibility form and use deterministic readable formatting.
382
+ The waiting run stores a versioned request and asks every channel configured for the logical audience. The structured `subject` remains machine data. Channels receive only the normalized `presentation`, title, choices, input prompts, and any deadline policy. The first valid verified human answer wins. When `onTimeout` is present and no human answer wins before the saved deadline, the host takes a control claim on the waiting parent and atomically applies the validated response with `timeout` provenance, closes the interaction, and reserves the continuation. This policy can continue without a configured channel. A continuation preserves the original workflow input and exposes the resolved response as the checkpoint output. `humanDecisionEdge()` provides exhaustive routing for the choices. The removed `body` request form is invalid under the alpha hard cut.
372
383
 
373
384
  The model-facing workflow tool cannot answer a protected human decision. The origin Pi session displays the request without starting a model turn. A person uses `/workflow answer` to send the answer through the host-owned path. Ordinary checkpoints can also use the model-facing `answer` action.
374
385
 
@@ -475,18 +486,19 @@ The model sees one `workflow` tool. Its `action` field supports:
475
486
  A direct user request to continue or resume the active workflow maps to
476
487
  `resume` immediately. The model does not call `status` instead of `resume` or
477
488
  use it as a prerequisite. An already active run adopts the resume request. A
478
- paused interaction remains the same durable request and resumes without a
479
- worker. Other paused or parked work gets a new claim generation and worker.
489
+ pending interaction on a paused run remains the same durable request and resumes
490
+ without a worker. Other paused or parked work gets a new claim generation and worker.
480
491
  With no resumable run, the host rejects the request.
481
492
 
482
493
  The origin Pi session shows its active run in the workflow widget. `Shift+Up`
483
- and `Shift+Down` scroll it. If Escape aborts the model turn started by a
484
- presented workflow interaction, the extension detects the public context abort
485
- signal and pauses that run through the host. It also accepts Pi's public
486
- `aborted` stop reason. The matching `agent_end` event must contain the workflow
487
- prompt, so an unrelated interrupted turn cannot pause old pending work. The
488
- paused run does not accept updates, submissions, or decision answers until
489
- `resume`.
494
+ and `Shift+Down` scroll it. A sent step message is open only while its
495
+ interaction is pending and its run is not paused. Because public `agent_start`
496
+ has no message payload, any model turn that starts in that state is workflow
497
+ work. If Escape ends that turn with Pi's public `aborted` stop reason, one
498
+ turn-end report atomically sets the run pause and cancels the interaction's
499
+ pending step messages. A turn that starts while the run is paused does not bind
500
+ to the workflow. The paused run does not accept updates, submissions, or
501
+ decision answers until `resume`.
490
502
 
491
503
  `status` reports the durable queue projection. A host command succeeds only
492
504
  after its transaction commits. The protocol stores request fingerprints and
@@ -679,18 +691,16 @@ is pending, the step id is wrong, the attempt id belongs to an earlier attempt
679
691
  of the same node (loops revisit node ids, so each attempt gets a fresh id), or
680
692
  `validate` throws.
681
693
  Acceptance resolves the step and the engine advances. In an interactive Pi
682
- session, each agent prompt arrives as a `pi-workflows-agent-step` custom message
694
+ session, each agent prompt arrives as a `pi-workflows-step` custom message
683
695
  with `triggerTurn: true`. The model receives the complete prompt, while the
684
696
  conversation shows a compact workflow and node card. Expanding tool output with
685
- Ctrl+O shows the exact contract and full prompt. Reminders and resumed prompts
686
- use the same card and keep the active attempt id.
697
+ Ctrl+O shows the exact contract and full prompt. Step messages with reason `reminder` or `resumed` use the same card and keep the active attempt ID.
687
698
 
688
699
  Headless RPC execution receives the same complete prompt without TUI metadata.
689
- Workflow notifications use a separate message type with `triggerTurn: false`,
690
- so a notification does not start an assistant response. Deferred successor turns
691
- use an internal turn-intent contract instead of the notification outbox. See
692
- [WORKFLOW_STEP_MESSAGES.md](WORKFLOW_STEP_MESSAGES.md) for the step-message contract
693
- and [Deferred workflow turns](DEFERRED_TURNS.md) for the successor-turn contract.
700
+ Workflow notifications use a custom message with `triggerTurn: false`, so a
701
+ notification does not start an assistant response. Step prompts, decisions, notifications, terminal results, and follow-ups use the same saved workflow-message contract and extension coordinator. Initial, reminder, and resumed prompts use the same step kind. See
702
+ [Workflow messages in Pi](WORKFLOW_STEP_MESSAGES.md) and the approved
703
+ [workflow-message restoration plan](2026-09-02-unify-workflow-messages-plan.md).
694
704
 
695
705
  ## Visible responses
696
706
 
@@ -707,7 +717,7 @@ returns its stored receipt.
707
717
 
708
718
  A headless controller child cannot produce a visible assistant message without
709
719
  an approved origin-session binding. Use structured agent output for detached
710
- work. Terminal run state does not create an extra model turn.
720
+ work. A final continuation-chain outcome creates its own terminal workflow message through the shared coordinator only after the outcome is durable.
711
721
 
712
722
  ## Runtime behavior
713
723
 
@@ -719,8 +729,10 @@ possible. Defaults worth knowing:
719
729
  number or context callback. A timed-out node has outcome `timed_out` and can
720
730
  be routed with `$result.outcome`. A timed-out agent node also aborts its Pi
721
731
  turn, and late output for that attempt is rejected. Interactive runs save the
722
- resolved wall-clock deadline before they park. The host enforces that deadline
723
- while Pi is closed and after host restart.
732
+ resolved deadline before they park. The host advances it only during a
733
+ reported model turn from an active connected origin session. Message delivery,
734
+ waiting, pauses, disconnects, and host downtime do not consume the limit. This
735
+ active-time budget survives host restart.
724
736
  - `maxSteps` (workflow-level, default 100) bounds loops built from cycles in
725
737
  the graph.
726
738
  - `/workflow pause` atomically parks the run with `paused: true`, stores the
@@ -795,3 +807,41 @@ const engine = new WorkflowEngine({
795
807
  });
796
808
  const { state } = await engine.run(workflow, { task: "..." });
797
809
  ```
810
+
811
+ ## Test the installed package
812
+
813
+ The installed-package end-to-end test packs this repository and installs the
814
+ archive with production dependencies in a temporary consumer project. It then
815
+ starts the repository-pinned base Pi with only that Pi Workflows installation.
816
+ The test checks command isolation, host startup, widget and status changes,
817
+ pause, resume, completion, and `piw <runId> --once` output.
818
+
819
+ Run the deterministic phase without a model call:
820
+
821
+ ```bash
822
+ npm run test:e2e:live -- --runtime-only
823
+ ```
824
+
825
+ A real-model phase is manual. Supply the exact Pi provider and model as separate
826
+ values. The runner does not select a default or accept model fallback:
827
+
828
+ ```bash
829
+ npm run test:e2e:live -- \
830
+ --provider openai \
831
+ --model gpt-5.6-luna
832
+ ```
833
+
834
+ For subscription authentication, use a dedicated Pi profile that has no other
835
+ extensions or resources:
836
+
837
+ ```bash
838
+ npm run test:e2e:live -- \
839
+ --profile ~/.config/pi-workflows-e2e/openai-codex \
840
+ --provider openai-codex \
841
+ --model gpt-5.6-luna
842
+ ```
843
+
844
+ The profile and normal provider environment remain operator-owned. The runner
845
+ does not read, copy, print, or save credentials. It uses one guarded temporary
846
+ root and removes that root after success or failure. Use `--keep` only when you
847
+ need the isolated files for diagnosis.
package/herdr-plugin.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  id = "osolmaz.pi-workflows"
2
2
  name = "pi-workflows"
3
- version = "0.15.3"
3
+ version = "0.16.1"
4
4
  min_herdr_version = "0.7.0"
5
5
  description = "Open the active pi-workflows run in piw from a managed Herdr pane."
6
6
  platforms = ["linux", "macos"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osolmaz/pi-workflows",
3
- "version": "0.15.3",
3
+ "version": "0.16.1",
4
4
  "description": "Workflow and controller runtime with a live terminal viewer for the pi coding agent",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -24,6 +24,7 @@
24
24
  "examples",
25
25
  "docs",
26
26
  "schemas",
27
+ "protocol",
27
28
  "plugins/herdr",
28
29
  "herdr-plugin.toml",
29
30
  "README.md",
@@ -39,6 +40,10 @@
39
40
  "types": "./dist/extension/index.d.ts",
40
41
  "default": "./dist/extension/index.js"
41
42
  },
43
+ "./client": {
44
+ "types": "./dist/client/index.d.ts",
45
+ "default": "./dist/client/index.js"
46
+ },
42
47
  "./controllers": {
43
48
  "types": "./dist/controllers/index.d.ts",
44
49
  "default": "./dist/controllers/index.js"
@@ -62,11 +67,13 @@
62
67
  "test": "vitest run",
63
68
  "test:coverage": "vitest run --coverage",
64
69
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
70
+ "test:e2e:live": "node scripts/live-e2e.mjs",
65
71
  "typecheck": "tsc -p tsconfig.json --noEmit"
66
72
  },
67
73
  "dependencies": {
68
74
  "better-sqlite3": "^13.0.2",
69
- "jiti": "^2.7.0"
75
+ "jiti": "^2.7.0",
76
+ "tsx": "^4.23.13"
70
77
  },
71
78
  "devDependencies": {
72
79
  "@earendil-works/pi-ai": "0.84.2",
@@ -77,7 +84,6 @@
77
84
  "@vitest/coverage-istanbul": "^4.1.10",
78
85
  "oxfmt": "^0.59.0",
79
86
  "oxlint": "^1.74.0",
80
- "tsx": "^4.23.1",
81
87
  "typebox": "^1.3.6",
82
88
  "typescript": "^7.0.2",
83
89
  "vitest": "^4.1.10"
@@ -12,12 +12,14 @@ if (!/^[A-Za-z0-9]+:p[A-Za-z0-9]+$/u.test(paneId)) {
12
12
  fail("HERDR_PANE_ID is missing or invalid.");
13
13
  }
14
14
 
15
- const herdr = process.env.HERDR_BIN_PATH ?? "herdr";
15
+ const configuredHerdr = process.env.HERDR_BIN_PATH ?? "herdr";
16
16
  const label = `piw · ${runId}`;
17
- const labeled = spawnSync(herdr, ["pane", "rename", paneId, label], {
18
- encoding: "utf8",
19
- stdio: ["ignore", "ignore", "pipe"],
20
- });
17
+ let herdr = configuredHerdr;
18
+ let labeled = renamePane(herdr, paneId, label);
19
+ if (labeled.error?.code === "ENOENT" && herdr !== "herdr") {
20
+ herdr = "herdr";
21
+ labeled = renamePane(herdr, paneId, label);
22
+ }
21
23
  if (labeled.error) fail(`Could not label the Herdr viewer pane: ${labeled.error.message}`);
22
24
  if (labeled.status !== 0) {
23
25
  fail(`Could not label the Herdr viewer pane: ${bounded(labeled.stderr) || "unknown error"}`);
@@ -32,6 +34,13 @@ viewer.on("exit", (code, signal) => {
32
34
  process.exitCode = signal ? 1 : (code ?? 1);
33
35
  });
34
36
 
37
+ function renamePane(command, targetPaneId, targetLabel) {
38
+ return spawnSync(command, ["pane", "rename", targetPaneId, targetLabel], {
39
+ encoding: "utf8",
40
+ stdio: ["ignore", "ignore", "pipe"],
41
+ });
42
+ }
43
+
35
44
  function fail(message) {
36
45
  process.stderr.write(`${message}\n`);
37
46
  process.exit(1);