@osolmaz/pi-workflows 0.15.2 → 0.16.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 (124) hide show
  1. package/README.md +6 -6
  2. package/dist/client/activity.d.ts +2 -0
  3. package/dist/client/activity.js +6 -0
  4. package/dist/client/activity.js.map +1 -0
  5. package/dist/client/client.d.ts +101 -0
  6. package/dist/client/client.js +733 -0
  7. package/dist/client/client.js.map +1 -0
  8. package/dist/client/index.d.ts +3 -0
  9. package/dist/client/index.js +3 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/materialize.d.ts +7 -0
  12. package/dist/client/materialize.js +177 -0
  13. package/dist/client/materialize.js.map +1 -0
  14. package/dist/client/protocol.d.ts +60 -0
  15. package/dist/client/protocol.js +269 -0
  16. package/dist/client/protocol.js.map +1 -0
  17. package/dist/client/resolver.d.ts +23 -0
  18. package/dist/client/resolver.js +2 -0
  19. package/dist/client/resolver.js.map +1 -0
  20. package/dist/client/view.d.ts +118 -0
  21. package/dist/client/view.js +3 -0
  22. package/dist/client/view.js.map +1 -0
  23. package/dist/controllers/sqlite.d.ts +64 -0
  24. package/dist/controllers/sqlite.js +219 -3
  25. package/dist/controllers/sqlite.js.map +1 -1
  26. package/dist/extension/index.d.ts +1 -0
  27. package/dist/extension/index.js +384 -156
  28. package/dist/extension/index.js.map +1 -1
  29. package/dist/extension/session-delivery.d.ts +6 -0
  30. package/dist/extension/session-delivery.js +80 -25
  31. package/dist/extension/session-delivery.js.map +1 -1
  32. package/dist/extension/session-view.d.ts +21 -0
  33. package/dist/extension/session-view.js +127 -0
  34. package/dist/extension/session-view.js.map +1 -0
  35. package/dist/extension/widget.d.ts +2 -1
  36. package/dist/extension/widget.js +15 -7
  37. package/dist/extension/widget.js.map +1 -1
  38. package/dist/host/child-worker-supervisor.js +1 -1
  39. package/dist/host/child-worker-supervisor.js.map +1 -1
  40. package/dist/host/resolver-entry.d.ts +2 -23
  41. package/dist/host/resolver-entry.js +1 -1
  42. package/dist/host/resolver-entry.js.map +1 -1
  43. package/dist/host/runner.d.ts +12 -0
  44. package/dist/host/runner.js +565 -43
  45. package/dist/host/runner.js.map +1 -1
  46. package/dist/host/state.d.ts +8 -3
  47. package/dist/host/state.js +59 -27
  48. package/dist/host/state.js.map +1 -1
  49. package/dist/host/view.d.ts +73 -0
  50. package/dist/host/view.js +871 -0
  51. package/dist/host/view.js.map +1 -0
  52. package/dist/host/worker-protocol.js +1 -1
  53. package/dist/host/worker-protocol.js.map +1 -1
  54. package/dist/state/database.d.ts +1 -0
  55. package/dist/state/database.js +15 -0
  56. package/dist/state/database.js.map +1 -1
  57. package/dist/state/prune.d.ts +3 -1
  58. package/dist/state/prune.js +6 -8
  59. package/dist/state/prune.js.map +1 -1
  60. package/dist/state/schema.js +12 -1
  61. package/dist/state/schema.js.map +1 -1
  62. package/dist/viewer/backup.d.ts +2 -0
  63. package/dist/viewer/backup.js +28 -0
  64. package/dist/viewer/backup.js.map +1 -0
  65. package/dist/viewer/cli.d.ts +4 -0
  66. package/dist/viewer/cli.js +150 -170
  67. package/dist/viewer/cli.js.map +1 -1
  68. package/dist/viewer/tui.d.ts +5 -7
  69. package/dist/viewer/tui.js +188 -108
  70. package/dist/viewer/tui.js.map +1 -1
  71. package/dist/workflows/store.d.ts +62 -1
  72. package/dist/workflows/store.js +350 -44
  73. package/dist/workflows/store.js.map +1 -1
  74. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +139 -0
  75. package/docs/2026-09-01-unified-workflow-client-plan.md +381 -0
  76. package/docs/2026-09-02-installed-live-e2e-plan.md +225 -0
  77. package/docs/SQLITE_STATE.md +13 -11
  78. package/docs/WORKFLOW_HOST.md +81 -64
  79. package/docs/WORKFLOW_STEP_MESSAGES.md +4 -4
  80. package/docs/development.md +2 -1
  81. package/docs/live-replay-protocol.md +70 -132
  82. package/docs/tui-viewer.md +10 -14
  83. package/docs/workflows.md +56 -3
  84. package/herdr-plugin.toml +1 -1
  85. package/package.json +9 -3
  86. package/protocol/client.v1.schema.json +137 -0
  87. package/protocol/fixtures/client-v1.json +23 -0
  88. package/src/client/activity.ts +6 -0
  89. package/src/client/client.ts +935 -0
  90. package/src/client/index.ts +24 -0
  91. package/src/client/materialize.ts +228 -0
  92. package/src/client/protocol.ts +327 -0
  93. package/src/client/resolver.ts +26 -0
  94. package/src/client/view.ts +138 -0
  95. package/src/controllers/sqlite.ts +342 -3
  96. package/src/extension/index.ts +482 -171
  97. package/src/extension/session-delivery.ts +88 -25
  98. package/src/extension/session-view.ts +154 -0
  99. package/src/extension/widget.ts +18 -9
  100. package/src/host/child-worker-supervisor.ts +1 -1
  101. package/src/host/resolver-entry.ts +11 -26
  102. package/src/host/runner.ts +749 -75
  103. package/src/host/state.ts +82 -44
  104. package/src/host/view.ts +1084 -0
  105. package/src/host/worker-protocol.ts +1 -1
  106. package/src/state/database.ts +13 -0
  107. package/src/state/prune.ts +11 -11
  108. package/src/state/schema.ts +12 -1
  109. package/src/viewer/backup.ts +29 -0
  110. package/src/viewer/cli.ts +171 -185
  111. package/src/viewer/tui.ts +196 -124
  112. package/src/workflows/store.ts +500 -45
  113. package/dist/host/client.d.ts +0 -48
  114. package/dist/host/client.js +0 -216
  115. package/dist/host/client.js.map +0 -1
  116. package/dist/host/protocol.d.ts +0 -38
  117. package/dist/host/protocol.js +0 -156
  118. package/dist/host/protocol.js.map +0 -1
  119. package/dist/viewer/watch.d.ts +0 -6
  120. package/dist/viewer/watch.js +0 -46
  121. package/dist/viewer/watch.js.map +0 -1
  122. package/src/host/client.ts +0 -293
  123. package/src/host/protocol.ts +0 -196
  124. package/src/viewer/watch.ts +0 -51
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: Restore workflow session delivery and controls
3
+ author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
4
+ date: 2026-09-01
5
+ ---
6
+
7
+ # Restore workflow session delivery and controls
8
+
9
+ The out-of-process workflow host removed the Pi workflow widget and Escape-to-pause behavior. A later delivery safety fix also caused normal polling to report `Interactive request presentation claim conflict`. This plan restores those features and fixes delivery without bringing back the embedded workflow runtime.
10
+
11
+ [Workflow host](WORKFLOW_HOST.md) remains the process and state specification. [Workflow step messages](WORKFLOW_STEP_MESSAGES.md) remains the session message specification. This plan records the cause, scope, implementation order, and acceptance checks for the repair.
12
+
13
+ ## Observed problems
14
+
15
+ - The active workflow widget no longer appears in Pi.
16
+ - Escape aborts the current model turn but leaves its workflow unpaused.
17
+ - Polling can report a presentation claim conflict before the workflow step appears.
18
+ - A visible message can become eligible for another send if saving its durable receipt fails.
19
+
20
+ The affected live run remained durably parked at its interactive request. No workflow state or Pi session message was lost.
21
+
22
+ ## Root cause
23
+
24
+ The out-of-process redesign removed the embedded extension executor. The old widget and `agent_end` pause handler were coupled to that executor, so both were removed with it. The redesign did not add host-backed replacements.
25
+
26
+ The delivery coordinator checks Pi again after an asynchronous host claim. If Pi becomes busy during that claim, the coordinator currently drops the claim. The next poll tries to claim the same interaction before its ten-second presentation lease expires. The host correctly rejects that second claim. The extension incorrectly exposes the expected rejection as a workflow tool failure.
27
+
28
+ The coordinator also removes its local queued guard before durable settlement finishes. A settlement error can therefore make later polling treat a message that is already visible in Pi as sendable work.
29
+
30
+ ## Requirements
31
+
32
+ - Keep one global host as the normal workflow state writer.
33
+ - Keep workflow and controller code in supervised child processes.
34
+ - Use documented Pi extension APIs only.
35
+ - Preserve one ordered session delivery path for steps, decisions, notifications, and final results.
36
+ - Never send through an expired claim.
37
+ - Never resend a message that is visible in Pi only because its durable receipt failed.
38
+ - Pause a presented workflow interaction when its Pi model turn ends with stop reason `aborted`.
39
+ - Reject workflow updates and submissions while that interaction is paused.
40
+ - Restore the widget as a read-only view of durable host state.
41
+ - Keep schema identifiers at version 1 and add no compatibility path.
42
+
43
+ ## Delivery coordinator
44
+
45
+ The coordinator will have three in-memory states for one delivery:
46
+
47
+ 1. `claimed`: the host granted a lease, but Pi became busy before send;
48
+ 2. `queued`: `pi.sendMessage()` was called and the matching Pi entry is not yet durably settled;
49
+ 3. settled or ambiguous: the host accepted the public Pi entry ID, or settlement could not be proved.
50
+
51
+ The coordinator records `claimExpiresAt` with every claim. A later poll can use the same claim while it is live. Immediately before send, the extension revalidates that exact claim and durable resource through the host, then checks Pi and the lease again. It discards cancelled, paused, replaced, or expired work. It does not request another claim while a live claim is remembered.
52
+
53
+ The extension reads the presentation claim owner and expiry from the existing version-1 interaction row. A live claim held by any extension is normal unavailable work. It is not a tool error. Notification and terminal-turn claim receipts also include their exact expiry.
54
+
55
+ The coordinator keeps a queued delivery until durable settlement succeeds. If the Pi entry is visible and settlement fails, the coordinator reports an ambiguous receipt and keeps the send blocked. Recovery may acquire a fresh claim and adopt the existing Pi entry, but it cannot send that delivery again.
56
+
57
+ ## Widget
58
+
59
+ The extension will read the active origin-session run and render the existing workflow widget from its durable run state and definition snapshot. This path is read-only. It does not load workflow source, execute workflow code, or write workflow state.
60
+
61
+ The widget will:
62
+
63
+ - show running, waiting, and paused state;
64
+ - use the existing bounded ten-line renderer;
65
+ - support `Shift+Up` and `Shift+Down` scrolling;
66
+ - use serializable lines outside TUI mode;
67
+ - clear when the session has no active run or shuts down.
68
+
69
+ ## Escape and pause
70
+
71
+ The extension will use Pi's documented `agent_end` event and public extension context abort signal. It will act only when:
72
+
73
+ - the active context signal is aborted, or an assistant message has stop reason `aborted`;
74
+ - the origin session has a pending agent or assistant interaction;
75
+ - the same `agent_end` event contains that interaction's workflow prompt; and
76
+ - the run is not already paused.
77
+
78
+ The extension will send `run.pause` to the host. A live worker uses the existing exact-claim pause transaction. A waiting interaction has no worker and no live run claim, so the host will atomically set `paused = 1` on the parked run. The host will reject updates, submissions, and decision answers while paused.
79
+
80
+ Resume will clear the pause on the same pending interaction without creating a worker or a second prompt. Other paused work will keep the existing behavior: take a new claim generation and resume in a supervised child.
81
+
82
+ ## Scope
83
+
84
+ The change may update:
85
+
86
+ - the extension delivery coordinator and host client integration;
87
+ - the read-only workflow widget projection;
88
+ - host pause and resume handling for parked interactions;
89
+ - existing version-1 interaction response fields;
90
+ - focused unit, integration, and live Pi tests;
91
+ - the workflow host and authoring documentation.
92
+
93
+ ## Non-goals
94
+
95
+ - Do not change Pi core, private Pi APIs, or Pi session schemas.
96
+ - Do not restore the embedded workflow executor.
97
+ - Do not add a second production runtime, database, service, feature flag, migration, or compatibility reader.
98
+ - Do not claim exactly-once execution for an external effect that cannot prove it.
99
+ - Do not release until the repair passes a live test in a new Pi session.
100
+
101
+ ## Implementation order
102
+
103
+ 1. Keep a live claim in the shared delivery coordinator and add claim expiry to all delivery receipts.
104
+ 2. Treat an existing live presentation claim as unavailable work.
105
+ 3. Keep visible messages blocked until durable settlement succeeds or recovery adopts them.
106
+ 4. Add atomic pause and resume operations for a parked interaction.
107
+ 5. Reject updates and submissions while the run is paused.
108
+ 6. Add the public `agent_end` Escape handler.
109
+ 7. Restore the widget as a read-only durable-state view.
110
+ 8. Add regression tests and update the canonical documentation.
111
+ 9. Run repository checks, Pi Reviewer, a new-session live test, and CI before release.
112
+
113
+ ## Acceptance criteria
114
+
115
+ - Pi can remain busy longer than both the poll interval and presentation lease without a duplicate message or claim error.
116
+ - Every delivery ID creates at most one Pi session message and one model turn.
117
+ - A visible message with a failed receipt remains blocked from resend.
118
+ - A competing live presentation claim does not appear as a workflow tool failure.
119
+ - The widget appears for an active origin-session run and shows paused state after Escape.
120
+ - Escape pauses the matching waiting run through the host.
121
+ - A paused interaction rejects `update` and `submit`.
122
+ - Resume keeps the same request and allows submission without another prompt.
123
+ - Non-aborted turns and unrelated sessions do not pause the workflow.
124
+ - The extension and host execute no workflow or controller code in their own event loops.
125
+
126
+ ## Verification
127
+
128
+ Run:
129
+
130
+ ```bash
131
+ npm run check
132
+ npm run test:e2e
133
+ git diff --check
134
+ npx slophammer-ts@latest dry .
135
+ npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
136
+ npx -y @simpledoc/simpledoc check
137
+ ```
138
+
139
+ Then run Pi Reviewer against `main` with a ten-minute tool timeout until no P0 or P1 finding remains. Test the built package in a new Pi session and a new Herdr tab. The live test must show the widget, start one workflow step, pause it with Escape, resume it, and complete it without a duplicate prompt or claim error.
@@ -0,0 +1,381 @@
1
+ ---
2
+ title: Unify live workflow clients
3
+ author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
4
+ date: 2026-09-01
5
+ status: implemented
6
+ ---
7
+
8
+ # Unify live workflow clients
9
+
10
+ ## Goal
11
+
12
+ Give every live Pi Workflows interface one source of truth.
13
+
14
+ One package-owned host will own live SQLite state and produce one versioned run view. The Pi widget, status line, workflow controls, Herdr actions, CLI, and Rust `piw` viewer will use the same client protocol and the same run view. No production client will read or interpret live SQLite state.
15
+
16
+ This is one client design, not three separate repairs:
17
+
18
+ ```text
19
+ one host + one WorkflowClient v1 protocol + one run view + several renderers
20
+ ```
21
+
22
+ The change is an alpha hard cut. It replaces the split live client paths and removes the old paths in the same change.
23
+
24
+ ## User-visible failures
25
+
26
+ Three failures exposed the split design.
27
+
28
+ - The Pi workflow widget showed a pause icon and `[waiting]` while the origin Pi session was actively processing the workflow turn.
29
+ - The widget no longer showed the Herdr viewer action. `Ctrl+Shift+R` and `/piw` were not wired after the out-of-process cut.
30
+ - A matching installed `piw` rejected a live database that the TypeScript host accepted because Rust contained an older copied application version and DDL digest.
31
+
32
+ These are not independent display defects. The extension, Herdr integration, and Rust viewer use separate live-state paths and separate status logic.
33
+
34
+ ## Root cause
35
+
36
+ `WorkflowHostClient` is the authority for commands, but it is not the authority for live views.
37
+
38
+ The Pi extension opens SQLite twice during one refresh. It first finds the origin session reservation and then loads the run from a second database snapshot. It maps durable workflow state directly to a widget label. The host cannot add exact origin-session model activity to that result.
39
+
40
+ The Herdr adapter still exists, but the extension no longer connects it to the active workflow view or registers its shortcut and command.
41
+
42
+ The Rust viewer opens the live database directly. It therefore copies TypeScript-owned SQLite identity facts and can drift from the package that writes the database. Its separate replay protocol is also another live interface.
43
+
44
+ Polling frequency cannot fix these ownership errors. A longer lease or another SQLite compatibility check would preserve the split design.
45
+
46
+ ## Public Pi API feasibility
47
+
48
+ The documented Pi extension API provides the required boundary:
49
+
50
+ - session identity and lifecycle events;
51
+ - exact custom-message delivery through `pi.sendMessage()`;
52
+ - agent start, end, and settled events;
53
+ - command and shortcut registration;
54
+ - widget and status updates;
55
+ - the existing workflow tool and extension execution context.
56
+
57
+ The extension can associate its own delivered workflow message with the matching origin-session model turn. It can report that activity to the host without changing Pi core, reading private state, or changing Pi session files. No required public capability is missing.
58
+
59
+ ## Boundaries
60
+
61
+ ### In scope
62
+
63
+ - Replace the current host request, direct SQLite view, and replay client paths with one versioned live client protocol.
64
+ - Make the host the only production process that opens the active SQLite database.
65
+ - Add one host-produced live run view with one effective display status and allowed controls.
66
+ - Add exact, ephemeral origin-session workflow-turn activity reports.
67
+ - Restore the widget, status line, `/piw`, `Ctrl+Shift+R`, and the Herdr placement chooser from the same run view.
68
+ - Make local and remote `piw` use the same host protocol and projection.
69
+ - Move active database status, verification, backup, and prune commands through the host.
70
+ - Remove duplicated SQLite identity constants and all selectable direct live-state readers.
71
+ - Add unit, integration, Rust, real Pi, and Herdr tests.
72
+ - Update documentation to match the implementation.
73
+
74
+ ### Out of scope
75
+
76
+ - Pi core, private Pi APIs, Pi session schemas, or Pi session file edits.
77
+ - Another repository or an external service.
78
+ - A second database, cache database, state bridge, or operating-system service.
79
+ - A second production workflow runtime.
80
+ - A compatibility reader, protocol fallback, schema `v2`, migration, dual read, dual write, alias, or feature flag.
81
+ - A claim of exactly-once behavior for an external effect that cannot prove its result.
82
+ - Release or package publication as part of this documentation change.
83
+
84
+ ## Selected design
85
+
86
+ ### One live client protocol
87
+
88
+ Replace `pi-workflows.host-request.v1`, `pi-workflows.host-response.v1`, and `pi-workflows.replay.v1` with one logical protocol named `pi-workflows.client.v1`.
89
+
90
+ The protocol has one envelope with four message types:
91
+
92
+ - `hello` identifies the protocol and package version;
93
+ - `request` asks for a command, view, page, subscription, activity change, or maintenance action;
94
+ - `response` settles one request;
95
+ - `event` carries a revisioned list, snapshot, patch, page, origin-session delivery change, or availability change.
96
+
97
+ An `interaction.submit` request stays open while the supervised child validates the submitted value. Its one response settles only after the durable submission becomes `accepted`, `adopted`, or `rejected`. If the connection fails, repeating the same request ID and payload waits for and returns the same durable outcome. A local tool abort stops waiting without cancelling the host command. A later retry uses a new transport request ID with the same durable idempotency and submission IDs, so a late response cannot settle the retry. Clients do not poll SQLite for validation results.
98
+
99
+ Use newline-delimited canonical JSON on the user-only local socket. Use one canonical JSON object per message on the loopback WebSocket transport. Both transports carry the same fields and semantics. TypeScript and Rust use the same ECMAScript number formatting and UTF-16 object-key order. Both parsers reject unknown envelope fields and non-canonical framing. A validation failure closes only the offending connection.
100
+
101
+ Create one neutral wire schema at `protocol/client.v1.schema.json`. TypeScript and Rust parsers must validate against that contract and the same accepted and rejected fixture corpus. Neither language owns a private variant.
102
+
103
+ The private worker and source-resolver channels stay separate because they are internal supervision boundaries. They are not selectable user clients and do not expose live views.
104
+
105
+ ### One WorkflowClient abstraction
106
+
107
+ Move the public client boundary to `src/client/`.
108
+
109
+ `WorkflowClient` owns:
110
+
111
+ - on-demand host startup and connection without a durable status probe;
112
+ - explicit status requests when a caller asks for host status;
113
+ - verified chunk reads and content-reference hydration;
114
+ - one persistent connection per client process;
115
+ - request IDs, idempotency keys, and command receipts;
116
+ - desired run-list, origin-session, and run subscriptions;
117
+ - reconnect and revision resume;
118
+ - bounded snapshot recovery when a cursor is stale or a client falls behind;
119
+ - repeatable page navigation, including returning to a previously viewed cursor;
120
+ - protocol and package-version rejection;
121
+ - sanitized unavailable and not-found errors, including closed-socket backpressure waits.
122
+
123
+ The Pi extension and TypeScript CLI use this implementation. Rust implements the same wire contract in `tui/src/client.rs` and proves parity with the shared fixtures. Rust does not import TypeScript and does not know the SQLite schema digest.
124
+
125
+ ### One live run view
126
+
127
+ The host produces `pi-workflows.run-view.v1` from one consistent host-side read. It includes:
128
+
129
+ - the current bounded workflow, graph, attempt, trace, and session projection;
130
+ - presentation revision and page cursors;
131
+ - live and interruption facts;
132
+ - a `display` object with effective status, activity kind, allowed controls, and either the complete inline reason or a small notice plus a digest-bound `reasonContent` reference.
133
+
134
+ The run-list row contains the same `display` object. The origin-session response contains the complete active run view or no active run plus an ordered byte-bounded window of pending delivery records and their complete count. It does not retain an older terminal run after the active session reservation ends. Those records include the request, delivery, contract, revision, presentation entry, and claim facts that the shared delivery coordinator needs for steps, decisions, notifications, and terminal turns. The host returns this session response from one consistent read. A client does not resolve a reservation, load a run, or inspect delivery tables in separate reads.
135
+
136
+ Large history remains available through byte-bounded pages. The host counts histories and reads only the selected SQLite ranges. A page has an item limit and an encoded byte budget. It echoes the requested cursor and run-view revision, and a client rejects a response that no longer matches its request or current snapshot. Large workflow topology has bounded node, edge, graph-step, and transition projections plus durable references to the complete original definition and complete graph history. TypeScript clients assemble every run-history page for one revision and hydrate the complete definition. Values that do not fit inline use digest-bound opaque content references, and `view.content` returns the complete value in verified chunks before a TypeScript non-interactive viewer emits the complete run or the extension updates its widget. TypeScript and Rust load the complete graph steps and transitions through the same verified content interface. Rust also requests and verifies the complete referenced workflow definition, decodes only the complete value, and then builds the graph layout. Session-event pages include the replay checkpoint immediately before their first event. A large checkpoint uses the same content protocol. TypeScript hydrates it with the run view, and Rust requests and resolves it before replay. The complete logical result remains available. The client protocol does not add an arbitrary user-visible truncation.
137
+
138
+ The run list is also byte-bounded and revision-bound. It contains only lightweight status and source facts. TypeScript and Rust clients assemble every page for one revision before replacing the visible complete list. An unchanged subscription performs a lightweight revision check and reuses its prior result.
139
+
140
+ The origin-session response also contains read-only notification and turn availability. The shared delivery coordinator issues a claim only when the matching fact is true. A claimed terminal turn loads its exact run by ID instead of using the latest run in the session. Its idle poll does not write an empty claim or host-status command.
141
+
142
+ ### One status reducer
143
+
144
+ Only the host computes the effective display status.
145
+
146
+ The closed `display.status` set is `queued`, `running`, `waiting`, `paused`, `completed`, `failed`, `timed_out`, `cancelled`, and `ambiguous`.
147
+
148
+ Use this precedence:
149
+
150
+ 1. A durable ambiguous external effect that requires explicit review is `ambiguous`. An effect that is still applying under a live worker is not ambiguous.
151
+ 2. Another durable terminal result keeps its `completed`, `failed`, `timed_out`, or `cancelled` label.
152
+ 3. A durable pause is `paused`.
153
+ 4. A live supervised worker or an exact active origin-session workflow turn is `running`.
154
+ 5. A pending interaction, decision, or presentation with no exact active turn is `waiting`.
155
+ 6. Parked resumable work with no pending interaction is `queued`.
156
+ 7. Admitted work that has not started is `queued`.
157
+
158
+ Transport failure is the client condition `unavailable`. It is not a `display.status` value and must not appear as `waiting` or `paused`.
159
+
160
+ A parked queue is not enough to display `paused`. Only the durable pause flag permits that label and icon. A renderer does not inspect workflow rows to override the host result.
161
+
162
+ ### Exact origin-session activity
163
+
164
+ The Pi extension already owns the delivery map that links a workflow delivery to the visible custom message. Extend that coordinator to report activity for only that exact delivery.
165
+
166
+ A report contains:
167
+
168
+ - origin session ID;
169
+ - run ID;
170
+ - request ID;
171
+ - delivery ID;
172
+ - client connection ID;
173
+ - increasing activity sequence;
174
+ - state `started`, `refresh`, or `settled`.
175
+
176
+ The host accepts activity only when the durable pending interactive request and its recorded delivery or presentation entry match the session, run, request, and delivery. Presentation-claim settlement does not end the request, so valid activity can continue while the model turn runs. Repeated reports are idempotent. A stale sequence, replaced delivery, or wrong session is rejected.
177
+
178
+ The host keeps activity in memory with a short renewable lease tied to the client connection. The first report on each connection is `started`; only later reports on that same connection are refreshes. One constants module under `src/client/` owns both the refresh period and lease duration. The refresh period must be shorter than the lease duration, and the lease duration must bound how long a dead client can leave a false `running` display. The host clears activity on the matching settled event, connection loss, or lease expiry. A missing report falls back to durable `waiting`. It never creates a false `paused` or `running` state.
179
+
180
+ Activity is display evidence only. The host requires the deterministic `interaction:<request-id>` delivery ID and keys one overlay by connection and request. A caller-supplied alternate label cannot create a second overlay. Activity cannot renew a run claim, settle an interaction, change a workflow state, or authorize a control command. Durable workflow correctness does not depend on it.
181
+
182
+ ### One renderer input
183
+
184
+ The Pi extension subscribes to the active run view for its origin session. It gives that same immutable view to:
185
+
186
+ - the widget renderer;
187
+ - the status-line renderer;
188
+ - the Escape-to-pause matcher;
189
+ - the `/piw` command;
190
+ - the `Ctrl+Shift+R` shortcut;
191
+ - the Herdr placement and focus adapter.
192
+
193
+ The Herdr adapter owns only Herdr capability checks, pane placement, labels, focus, and cleanup. It receives the exact run ID and workflow name from the run view. It does not query workflow state.
194
+
195
+ The widget shows the Herdr hint only when the documented Herdr capability check succeeds. The command remains available as a fallback and reports a bounded reason when Herdr or `piw` is unavailable.
196
+
197
+ ### One live `piw` source
198
+
199
+ Local `piw` connects to the package-owned host, subscribes to run views, and fetches pages through `pi-workflows.client.v1`. When the socket is absent, Rust may start the host only by executing the installed `pi-workflows host start` command. Rust does not reimplement host launch, locking, epochs, or readiness. If that command is unavailable or fails, `piw` stops with the direct install or startup instruction.
200
+
201
+ `piw serve` stops reading SQLite. It becomes a loopback-only WebSocket relay for the same logical protocol. Remote clients continue to use an SSH tunnel. The relay opens one host socket connection for each WebSocket connection and couples their lifecycles one to one. It carries frames only. It does not multiplex clients, translate run state, keep another projection, or retain activity after either side closes.
202
+
203
+ Remove the selectable local SQLite source and the copied TypeScript application version and DDL digest from Rust. The Rust viewer has no SQLite mode, including for backups. If the host and viewer protocol do not match, `piw` stops with one direct package-version instruction. It must not suggest deleting live state and must not fall back to SQLite.
204
+
205
+ Only `pi-workflows state verify` may open an operator-selected inactive backup in query-only mode. That TypeScript maintenance path cannot select the active state path and is not part of the Rust viewer.
206
+
207
+ ## Contract changes
208
+
209
+ This is an alpha hard replacement.
210
+
211
+ - Keep persisted `pi-workflows-state` at version 1.
212
+ - Do not change the SQLite schema unless implementation proves that the host view needs a durable field. The activity overlay needs no durable field.
213
+ - Replace the current live wire contracts with `pi-workflows.client.v1` in place.
214
+ - Keep the run-view schema at version 1 and change its fields in place.
215
+ - Extend the version-1 `host_commands` operation set in place so backup and prune receipts are durable.
216
+ - Exclude transport request IDs from durable request fingerprints. Keep every other command field in the durable identity.
217
+ - Remove direct live SQLite access from the Pi extension, TypeScript viewer paths, replay server, Rust viewer, and active-state maintenance CLI.
218
+ - Remove the old replay protocol and all production fallback selection.
219
+ - Keep old incompatible state untouched and use the standard backup-and-reset instruction only when the SQLite digest itself is incompatible.
220
+ - A client protocol mismatch must ask for matching packages. It must not misreport a valid database as incompatible.
221
+
222
+ No migration, compatibility reader, dual protocol, bridge period, or feature flag is permitted.
223
+
224
+ ## Implementation plan
225
+
226
+ ### 1. Define the neutral client contract
227
+
228
+ **Location:** `protocol/client.v1.schema.json`, shared protocol fixtures, `src/client/protocol.ts`, and `tui/src/protocol.rs`.
229
+
230
+ **Change:** Define the one envelope, operation names, outcomes, subscriptions, run-view snapshots, patches, pages, activity reports, maintenance requests, safe errors, and protocol handshake. Keep foreground host-start retry timers referenced until success or timeout; unreference only background reconnect timers. End a backpressured write wait on connection close, socket error, or request cancellation. Replace the host and replay schema identifiers. Add one accepted and rejected fixture corpus used by TypeScript and Rust.
231
+
232
+ **Verification:** TypeScript and Rust accept every valid fixture, reject every invalid fixture, and serialize the same canonical messages. No old live protocol identifier remains in production code.
233
+
234
+ ### 2. Make the host the only live database reader
235
+
236
+ **Location:** `src/host/`, `src/state/`, controller stores, and state maintenance commands.
237
+
238
+ **Change:** Add host handlers for atomic origin-session view lookup, run-list and run-view snapshots, revision subscriptions, bounded pages, and active database maintenance. Keep projection reads and writes in the host. A view read must resolve the session reservation, run, durable display facts, and presentation revision from one consistent read boundary. Persist generated large view values in the run-scoped content table under their digest and media type before advertising their references. Do not reuse general state blobs or allow another run to read the content. Store the complete original workflow definition, not its escaped projection. Externalize large replay checkpoints and make both clients resolve them. Route state-changing Pi extension commands through the durable retry path. Give the CLI one stable client identity and one fresh key for each backup or applied prune invocation. Reuse that key only for the invocation's automatic reconnect retry, with a new request ID. Keep an in-flight maintenance operation alive after disconnect, store its accepted or rejected command receipt before response, wait for it during host shutdown, and adopt an exact retry. Wait for socket drain before sending another snapshot, and remove every subscription kind explicitly when its client unsubscribes.
239
+
240
+ **Verification:** Concurrent run changes cannot produce a view for the wrong session or combine two revisions. A source-level dependency test fails if production extension, CLI, relay, or Rust code opens the active database path. It permits SQLite only in the named TypeScript module that verifies an explicit inactive backup.
241
+
242
+ ### 3. Add the host status reducer and activity overlay
243
+
244
+ **Location:** a host-owned view module under `src/host/` or `src/viewer/`, with no Pi import.
245
+
246
+ **Change:** Build the `display` object from durable facts and the validated activity overlay. Add connection-scoped activity leases and monotonic sequences. Apply the documented status precedence and allowed-control rules in one function.
247
+
248
+ **Verification:** Table tests cover every durable state, worker state, pending request, parked resumable run, pause, terminal result, ambiguous effect, activity start, activity expiry, disconnect, and stale sequence. `paused` appears only with the durable pause fact. Activity changes no durable row.
249
+
250
+ ### 4. Replace the Pi extension's SQLite view
251
+
252
+ **Location:** `src/extension/session-view.ts`, `src/extension/index.ts`, and the delivery coordinator.
253
+
254
+ **Change:** Remove all extension imports and construction of `HostStateStore`, `SqliteControllerStore`, `WorkflowRunStore`, and the active state path. Replace `waitForInteractionSubmission`, `pendingInteractionForSession`, `pendingDecision`, `interactionPresentationClaimIsLive`, `hasClaimableNotification`, `hasClaimableTurn`, `terminalRunState`, `sessionRun`, and the widget's two-read refresh with `WorkflowClient` requests or the one origin-session subscription. The `interaction.submit` response supplies the final validation outcome. Report exact delivery activity from documented Pi events and the coordinator's delivery map. Render only the host `display` object. Keep Escape pause tied to the exact active workflow delivery.
255
+
256
+ **Verification:** Hold Pi busy longer than both the poll interval and activity lease. The widget stays `running` while refreshed exact activity is live, falls back to `waiting` after activity ends without submission, and shows `paused` only after the host accepts pause. The controlled run produces one visible delivery and one model turn without claiming a universal exactly-once guarantee.
257
+
258
+ ### 5. Restore Herdr actions from the same view
259
+
260
+ **Location:** `src/extension/index.ts`, `src/extension/herdr-viewer.ts`, widget rendering, and extension tests.
261
+
262
+ **Change:** Register `/piw` and `Ctrl+Shift+R` through documented Pi APIs. Pass the current run target from the subscribed view to the existing Herdr adapter. Restore the conditional widget hint, placement chooser, exact-run pane labels, reuse, focus, and cleanup.
263
+
264
+ **Verification:** Outside Herdr, normal Pi behavior is unchanged. Inside a disposable Herdr session, the widget shows the hint, all supported placements open the exact run, repeated open focuses the existing pane, and failure leaves no empty tab or workspace.
265
+
266
+ ### 6. Replace Rust live SQLite access
267
+
268
+ **Location:** `tui/src/client.rs`, `tui/src/source.rs`, `tui/src/source_loader.rs`, `tui/src/server.rs`, `tui/src/state/reader.rs`, and `tui/src/main.rs`.
269
+
270
+ **Change:** Make the protocol client the only Rust source. Remove the default active database path, all Rust `ProjectionReader` and backup-reader paths, copied application version, copied DDL digest, and direct-reader command selection. Start the host only through the installed TypeScript CLI. Make `piw serve` map each loopback WebSocket connection to one host socket connection and relay the same client protocol. Use the Unix socket on Unix and the package-derived named pipe on Windows. Drop old run pages, content requests, and artifacts when selection moves to another run.
271
+
272
+ **Verification:** A database created by the TypeScript package is visible in local and remote `piw` through the host. A protocol mismatch gives a package-version error. A valid live database never produces a Rust DDL mismatch. No `piw` live mode opens `state.sqlite`.
273
+
274
+ ### 7. Remove split paths and update documentation
275
+
276
+ **Location:** old host/replay protocol code, obsolete viewer readers, `docs/WORKFLOW_HOST.md`, `docs/SQLITE_STATE.md`, `docs/live-replay-protocol.md`, README usage, and package contents.
277
+
278
+ **Change:** Delete the superseded request/response and replay contracts, direct live readers, duplicated status reducers, and fallback flags. Update all user commands and architecture diagrams to show the one client stack. Keep inactive backup verification explicitly separate.
279
+
280
+ **Verification:** Search and dependency checks find no selectable direct live-state reader, old protocol identifier, copied DDL digest, or second status reducer. Package dry runs contain the client schema and both clients.
281
+
282
+ ## Failure handling
283
+
284
+ - If the host cannot start or connect, clients show `unavailable` with one bounded next action. They do not display a cached run as current.
285
+ - If a client disconnects, the host expires its activity and subscriptions. Reconnection requests a patch from the last accepted revision or receives a bounded snapshot.
286
+ - If a subscriber falls behind retained revisions, only that subscriber receives a new bounded snapshot.
287
+ - If an activity report is stale or mismatched, the host rejects it and keeps the durable view.
288
+ - If Pi returns from message delivery but the visible result cannot be proved, the delivery remains ambiguous and blocked. The display protocol does not retry it.
289
+ - If a control command has an uncertain transport result, the client repeats the same request ID and payload and adopts the stored receipt.
290
+ - If a client protocol version is wrong, the client asks for matching package versions. It does not ask the user to reset SQLite.
291
+ - If the live SQLite schema is truly incompatible, the host fails before mutation with the standard backup-and-reset instruction and leaves all files untouched.
292
+
293
+ ## Tests
294
+
295
+ ### Contract and unit tests
296
+
297
+ - Shared TypeScript and Rust wire fixtures, including ECMAScript number formatting and UTF-16 key ordering.
298
+ - Envelope size, framing, unknown field, malformed message, and safe-error tests.
299
+ - Status precedence and allowed-control table tests.
300
+ - Activity validation against the recorded deterministic delivery ID and entry, duplicate alternate-ID rejection, idempotency, sequence, first report after reconnect, refresh-before-expiry, disconnect, and bounded expiry tests.
301
+ - Foreground cold-start timer reference tests and backpressured-write close and cancellation tests.
302
+ - Rust complete-definition request, digest verification, decode, and graph-layout tests.
303
+ - Atomic origin-session view tests, including bounded pending delivery records and removal of a terminal run when its reservation ends.
304
+ - Large workflow-topology frame tests with exact complete durable definition recovery, including user data that looks like an artifact sentinel.
305
+ - More-than-256-node graph-history tests that recover all graph steps and transitions through verified content in TypeScript and Rust.
306
+ - Large replay-checkpoint frame tests with TypeScript hydration and Rust artifact resolution.
307
+ - Durable backup and applied-prune retry tests with a new transport request ID and one stored receipt, plus separate-invocation key tests that prevent stale receipt reuse.
308
+ - One-shot and Rust explicit-watch missing-run tests that show a not-found error instead of rendering `null` or loading forever.
309
+ - Full TypeScript run-page assembly, exact-attempt step-trace selection, widget history, and content-hydration tests that bind bytes to the advertised digest, plus Rust page and content retrieval tests.
310
+ - Extension mutation tests that prove the durable client path is used for starts, updates, and submissions.
311
+ - Slow subscriber and response backpressure, close-before-drain cleanup, explicit unsubscribe, stale revision, reconnect, and bounded page tests.
312
+ - Durable generated-content recovery after memory-cache eviction, same-byte media collision tests, run-scoped read tests, and oversized run-list diagnostic references.
313
+ - Run-list and full-view failure tests that preserve the complete stored diagnostic instead of the machine error code.
314
+ - Idempotent interaction retry with a different attempted submission ID.
315
+ - Rust run-switch cleanup, explicit single-run selection, and cross-platform local transport compilation.
316
+ - Herdr capability, command, shortcut, placement, reuse, and cleanup tests.
317
+
318
+ ### Integration tests
319
+
320
+ - Start one host and connect extension, CLI, and Rust clients to the same run.
321
+ - Prove that all clients receive the same revision, display status, and allowed controls.
322
+ - Create live state with TypeScript and view it through Rust without SQLite identity constants.
323
+ - Pause, resume, cancel, and submit through the one protocol and adopt repeated request receipts.
324
+ - Restart the host and recover subscriptions without a second database reader.
325
+ - Run the loopback WebSocket relay and compare its messages with local socket messages.
326
+ - Verify that active-state maintenance commands go through the host.
327
+ - Verify that only the TypeScript inactive-backup verifier can open SQLite outside the host and that it cannot select the active path.
328
+
329
+ ### Live tests
330
+
331
+ Use a new Pi session and a disposable Herdr tab.
332
+
333
+ 1. Start an interactive test workflow.
334
+ 2. Keep the origin model turn active longer than the polling and activity lease intervals.
335
+ 3. Confirm that the widget and status line show `running`, not `paused` or stale `waiting`.
336
+ 4. Confirm that `Ctrl+Shift+R` and `/piw` open or focus the exact run.
337
+ 5. Pause with Escape, confirm a durable `paused` view, resume, and complete.
338
+ 6. Open the same run with local `piw` and the loopback relay.
339
+ 7. Confirm one delivered workflow message and one model turn in this controlled run.
340
+ 8. Restart the Pi session, reconnect, and confirm the correct run and controls.
341
+
342
+ ## Required checks
343
+
344
+ Run:
345
+
346
+ ```bash
347
+ npm run check
348
+ npm run test:e2e
349
+ git diff --check
350
+ npx slophammer-ts@latest dry .
351
+ npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
352
+ npx -y @simpledoc/simpledoc check
353
+ npm pack --dry-run
354
+ cargo test --manifest-path tui/Cargo.toml
355
+ cargo clippy --manifest-path tui/Cargo.toml --all-targets --all-features -- -D warnings
356
+ cargo fmt --manifest-path tui/Cargo.toml --check
357
+ ```
358
+
359
+ Run Pi Reviewer against `main` with a ten-minute tool timeout until no P0 or P1 finding remains. Check pull-request comments and CI before merge.
360
+
361
+ ## Rollout
362
+
363
+ Implement this as one release-sized hard cut. Do not publish the TypeScript package or Rust crate separately. Do not install one without the matching other package.
364
+
365
+ Before release, test the packed package in a new Pi session and a disposable Herdr tab. Confirm that the package starts its host on demand and does not install an operating-system service.
366
+
367
+ This documentation task does not publish a release.
368
+
369
+ ## Completion criteria
370
+
371
+ The work is complete when:
372
+
373
+ - the host is the only production process that opens active SQLite state;
374
+ - all live clients use `pi-workflows.client.v1`;
375
+ - the host produces one canonical run view and one display status;
376
+ - the Pi widget, status line, controls, Herdr actions, CLI, and `piw` agree on the same revision;
377
+ - exact origin-session activity changes display only and cannot change authority;
378
+ - `paused` can come only from durable pause;
379
+ - local and remote `piw` work without copied SQLite identity facts;
380
+ - no direct live SQLite client, old replay protocol, fallback, or second status reducer remains;
381
+ - all unit, integration, live, repository, reviewer, and CI checks pass.