@osolmaz/pi-workflows 0.14.0 → 0.15.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.
- package/README.md +78 -110
- package/dist/builtins/autoimplement.workflow.js +3 -1
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/change-verification.workflow.js +7 -2
- package/dist/builtins/change-verification.workflow.js.map +1 -1
- package/dist/builtins/metadata.d.ts +22 -0
- package/dist/builtins/metadata.js +10 -0
- package/dist/builtins/metadata.js.map +1 -0
- package/dist/builtins/monitor.workflow.js +4 -1
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.js +4 -1
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/builtins/workspace-preparation.workflow.js +3 -1
- package/dist/builtins/workspace-preparation.workflow.js.map +1 -1
- package/dist/controllers/index.d.ts +0 -1
- package/dist/controllers/index.js +0 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +51 -0
- package/dist/controllers/sqlite.js +433 -57
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/store.d.ts +1 -0
- package/dist/extension/controller-command.d.ts +22 -0
- package/dist/extension/controller-command.js +32 -0
- package/dist/extension/controller-command.js.map +1 -0
- package/dist/extension/index.d.ts +2 -9
- package/dist/extension/index.js +876 -3447
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/message-card.d.ts +13 -0
- package/dist/extension/message-card.js +46 -0
- package/dist/extension/message-card.js.map +1 -0
- package/dist/extension/step-message.d.ts +5 -6
- package/dist/extension/step-message.js +41 -52
- package/dist/extension/step-message.js.map +1 -1
- package/dist/host/child-worker-supervisor.d.ts +50 -0
- package/dist/host/child-worker-supervisor.js +159 -0
- package/dist/host/child-worker-supervisor.js.map +1 -0
- package/dist/host/client.d.ts +48 -0
- package/dist/host/client.js +216 -0
- package/dist/host/client.js.map +1 -0
- package/dist/host/controller-worker-entry.d.ts +2 -0
- package/dist/host/controller-worker-entry.js +244 -0
- package/dist/host/controller-worker-entry.js.map +1 -0
- package/dist/host/controller-worker-protocol.d.ts +36 -0
- package/dist/host/controller-worker-protocol.js +49 -0
- package/dist/host/controller-worker-protocol.js.map +1 -0
- package/dist/host/controller-worker-supervisor.d.ts +21 -0
- package/dist/host/controller-worker-supervisor.js +54 -0
- package/dist/host/controller-worker-supervisor.js.map +1 -0
- package/dist/host/host-entry.d.ts +2 -0
- package/dist/host/host-entry.js +23 -0
- package/dist/host/host-entry.js.map +1 -0
- package/dist/host/processes.d.ts +17 -12
- package/dist/host/processes.js +154 -50
- package/dist/host/processes.js.map +1 -1
- package/dist/host/protocol.d.ts +38 -0
- package/dist/host/protocol.js +156 -0
- package/dist/host/protocol.js.map +1 -0
- package/dist/host/resolver-entry.d.ts +49 -0
- package/dist/host/resolver-entry.js +149 -0
- package/dist/host/resolver-entry.js.map +1 -0
- package/dist/host/rpc-executor.d.ts +14 -3
- package/dist/host/rpc-executor.js +63 -30
- package/dist/host/rpc-executor.js.map +1 -1
- package/dist/host/runner.d.ts +95 -29
- package/dist/host/runner.js +2311 -348
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +174 -0
- package/dist/host/state.js +689 -0
- package/dist/host/state.js.map +1 -0
- package/dist/host/worker-entry.d.ts +10 -0
- package/dist/host/worker-entry.js +446 -0
- package/dist/host/worker-entry.js.map +1 -0
- package/dist/host/worker-protocol.d.ts +31 -0
- package/dist/host/worker-protocol.js +122 -0
- package/dist/host/worker-protocol.js.map +1 -0
- package/dist/host/worker-store.d.ts +80 -0
- package/dist/host/worker-store.js +91 -0
- package/dist/host/worker-store.js.map +1 -0
- package/dist/host/worker-supervisor.d.ts +22 -0
- package/dist/host/worker-supervisor.js +54 -0
- package/dist/host/worker-supervisor.js.map +1 -0
- package/dist/render/canvas.d.ts +2 -1
- package/dist/render/canvas.js +40 -15
- package/dist/render/canvas.js.map +1 -1
- package/dist/render/graph-render.d.ts +7 -3
- package/dist/render/graph-render.js +125 -74
- package/dist/render/graph-render.js.map +1 -1
- package/dist/state/database.js +3 -1
- package/dist/state/database.js.map +1 -1
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +1 -0
- package/dist/state/index.js.map +1 -1
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +171 -3
- package/dist/state/schema.js.map +1 -1
- package/dist/state/viewer.d.ts +46 -0
- package/dist/state/viewer.js +249 -0
- package/dist/state/viewer.js.map +1 -0
- package/dist/viewer/cli.d.ts +1 -1
- package/dist/viewer/cli.js +47 -18
- package/dist/viewer/cli.js.map +1 -1
- package/dist/workflows/composition.js +25 -2
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +3 -1
- package/dist/workflows/definition.js +25 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +1 -0
- package/dist/workflows/engine.js +157 -42
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +3 -1
- package/dist/workflows/errors.js +4 -7
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +3 -0
- package/dist/workflows/human-decision.js +31 -0
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +17 -6
- package/dist/workflows/schema.js.map +1 -1
- package/dist/{viewer → workflows}/session-reducer.d.ts +3 -1
- package/dist/{viewer → workflows}/session-reducer.js +4 -0
- package/dist/workflows/session-reducer.js.map +1 -0
- package/dist/workflows/store.d.ts +87 -1
- package/dist/workflows/store.js +892 -93
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +0 -26
- package/dist/workflows/tool-input.js +2 -50
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +36 -4
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +353 -0
- package/docs/CONTROLLERS.md +11 -11
- package/docs/DEFERRED_TURNS.md +23 -2
- package/docs/SQLITE_STATE.md +41 -31
- package/docs/WORKFLOW_HOST.md +452 -0
- package/docs/development.md +46 -30
- package/docs/live-replay-protocol.md +129 -100
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +42 -0
- package/docs/plans/2026-08-28-piw-incremental-viewer-plan.md +293 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +11 -1
- package/docs/plans/tui-viewer-implementation-plan.md +5 -0
- package/docs/tui-viewer.md +18 -3
- package/docs/workflows.md +162 -210
- package/examples/workflows/command-batch.workflow.ts +2 -0
- package/examples/workflows/shell.workflow.ts +2 -1
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/src/builtins/autoimplement.workflow.ts +3 -0
- package/src/builtins/change-verification.workflow.ts +7 -2
- package/src/builtins/metadata.ts +9 -0
- package/src/builtins/monitor.workflow.ts +4 -0
- package/src/builtins/sanity-check.workflow.ts +4 -0
- package/src/builtins/workspace-preparation.workflow.ts +3 -1
- package/src/controllers/index.ts +0 -5
- package/src/controllers/sqlite.ts +709 -76
- package/src/controllers/store.ts +1 -0
- package/src/extension/controller-command.ts +45 -0
- package/src/extension/index.ts +944 -4163
- package/src/extension/message-card.ts +61 -0
- package/src/extension/step-message.ts +58 -63
- package/src/host/child-worker-supervisor.ts +183 -0
- package/src/host/client.ts +293 -0
- package/src/host/controller-worker-entry.ts +311 -0
- package/src/host/controller-worker-protocol.ts +104 -0
- package/src/host/controller-worker-supervisor.ts +79 -0
- package/src/host/host-entry.ts +23 -0
- package/src/host/processes.ts +171 -54
- package/src/host/protocol.ts +196 -0
- package/src/host/resolver-entry.ts +241 -0
- package/src/host/rpc-executor.ts +76 -34
- package/src/host/runner.ts +2813 -422
- package/src/host/state.ts +1160 -0
- package/src/host/worker-entry.ts +533 -0
- package/src/host/worker-protocol.ts +165 -0
- package/src/host/worker-store.ts +229 -0
- package/src/host/worker-supervisor.ts +74 -0
- package/src/render/canvas.ts +44 -10
- package/src/render/graph-render.ts +145 -90
- package/src/state/database.ts +2 -1
- package/src/state/index.ts +14 -0
- package/src/state/schema.ts +171 -3
- package/src/state/viewer.ts +356 -0
- package/src/viewer/cli.ts +49 -17
- package/src/workflows/composition.ts +36 -2
- package/src/workflows/definition.ts +32 -0
- package/src/workflows/engine.ts +157 -54
- package/src/workflows/errors.ts +11 -2
- package/src/workflows/human-decision.ts +49 -0
- package/src/workflows/index.ts +2 -0
- package/src/workflows/schema.ts +19 -6
- package/src/{viewer → workflows}/session-reducer.ts +11 -1
- package/src/workflows/store.ts +1316 -108
- package/src/workflows/tool-input.ts +3 -69
- package/src/workflows/types.ts +32 -4
- package/dist/controllers/workflow-engine-scheduler.d.ts +0 -29
- package/dist/controllers/workflow-engine-scheduler.js +0 -175
- package/dist/controllers/workflow-engine-scheduler.js.map +0 -1
- package/dist/extension/controller-host.d.ts +0 -48
- package/dist/extension/controller-host.js +0 -110
- package/dist/extension/controller-host.js.map +0 -1
- package/dist/extension/deferred-turn-coordinator.d.ts +0 -32
- package/dist/extension/deferred-turn-coordinator.js +0 -143
- package/dist/extension/deferred-turn-coordinator.js.map +0 -1
- package/dist/extension/deferred-turn.d.ts +0 -44
- package/dist/extension/deferred-turn.js +0 -110
- package/dist/extension/deferred-turn.js.map +0 -1
- package/dist/extension/executor.d.ts +0 -86
- package/dist/extension/executor.js +0 -311
- package/dist/extension/executor.js.map +0 -1
- package/dist/extension/follow-up-coordinator.d.ts +0 -27
- package/dist/extension/follow-up-coordinator.js +0 -131
- package/dist/extension/follow-up-coordinator.js.map +0 -1
- package/dist/extension/recorder.d.ts +0 -84
- package/dist/extension/recorder.js +0 -528
- package/dist/extension/recorder.js.map +0 -1
- package/dist/extension/restart-policy.d.ts +0 -38
- package/dist/extension/restart-policy.js +0 -116
- package/dist/extension/restart-policy.js.map +0 -1
- package/dist/extension/session-events.d.ts +0 -133
- package/dist/extension/session-events.js +0 -61
- package/dist/extension/session-events.js.map +0 -1
- package/dist/extension/terminal-decision.d.ts +0 -51
- package/dist/extension/terminal-decision.js +0 -110
- package/dist/extension/terminal-decision.js.map +0 -1
- package/dist/viewer/session-reducer.js.map +0 -1
- package/src/controllers/workflow-engine-scheduler.ts +0 -246
- package/src/extension/controller-host.ts +0 -167
- package/src/extension/deferred-turn-coordinator.ts +0 -171
- package/src/extension/deferred-turn.ts +0 -166
- package/src/extension/executor.ts +0 -411
- package/src/extension/follow-up-coordinator.ts +0 -151
- package/src/extension/recorder.ts +0 -655
- package/src/extension/restart-policy.ts +0 -163
- package/src/extension/session-events.ts +0 -121
- package/src/extension/terminal-decision.ts +0 -172
|
@@ -1,146 +1,175 @@
|
|
|
1
1
|
# Live replay protocol
|
|
2
2
|
|
|
3
|
-
The Rust viewer (`tui/`) can
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
The
|
|
10
|
-
[SQLITE_STATE.md](SQLITE_STATE.md)) and never writes them. The protocol has no
|
|
11
|
-
authentication, so the server only accepts loopback bind addresses and refuses
|
|
12
|
-
to start on anything else; workflow state contains private data, and remote viewing
|
|
13
|
-
goes through an SSH tunnel. Handshakes that
|
|
14
|
-
carry an `Origin` header are rejected: browsers always send one, and a web
|
|
15
|
-
page must not be able to read workflow state by opening a WebSocket to localhost.
|
|
16
|
-
|
|
17
|
-
## Transport and framing
|
|
18
|
-
|
|
19
|
-
A single WebSocket endpoint (`/ws`). Every message is one JSON object with a
|
|
20
|
-
`type` field. Unknown message types and unknown fields must be ignored by both
|
|
21
|
-
sides. The server sends `hello` on connect; a client that does not recognize
|
|
22
|
-
the protocol id must disconnect.
|
|
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`.
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Framing
|
|
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.
|
|
23
10
|
|
|
24
11
|
```json
|
|
25
12
|
{ "type": "hello", "protocol": "pi-workflows.replay.v1" }
|
|
26
13
|
```
|
|
27
14
|
|
|
28
|
-
##
|
|
15
|
+
## Bounded run view
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
run:
|
|
17
|
+
A snapshot contains one bounded run view:
|
|
32
18
|
|
|
33
19
|
```json
|
|
34
20
|
{
|
|
21
|
+
"presentationRevision": 42,
|
|
22
|
+
"graphRevision": 17,
|
|
35
23
|
"manifest": { … },
|
|
36
24
|
"workflow": { … },
|
|
37
|
-
"
|
|
38
|
-
|
|
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
|
+
},
|
|
39
44
|
"session": {
|
|
45
|
+
"presentationRevision": 42,
|
|
40
46
|
"binding": { … },
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
},
|
|
45
59
|
"capture": { … }
|
|
46
60
|
},
|
|
61
|
+
"settingsScopes": [ … ],
|
|
62
|
+
"followUpQueue": { … },
|
|
47
63
|
"live": true,
|
|
48
64
|
"possiblyInterrupted": false
|
|
49
65
|
}
|
|
50
66
|
```
|
|
51
67
|
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
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.
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
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
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
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.
|
|
98
|
+
|
|
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.
|
|
100
|
+
|
|
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.
|
|
102
|
+
|
|
103
|
+
The database retains 256 presentation revisions per run. The server does not replay an unbounded patch backlog.
|
|
61
104
|
|
|
62
|
-
|
|
63
|
-
scrubbing is a pure client-side operation; rewinding never requires the
|
|
64
|
-
server. Clients order session events by `seq` and use `at` only for playback
|
|
65
|
-
timing.
|
|
105
|
+
## Pages
|
|
66
106
|
|
|
67
|
-
|
|
107
|
+
A client asks for a page with `fetch_page`:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"type": "fetch_page",
|
|
112
|
+
"runId": "run-1",
|
|
113
|
+
"kind": "session_events",
|
|
114
|
+
"cursor": 20000
|
|
115
|
+
}
|
|
116
|
+
```
|
|
68
117
|
|
|
69
|
-
|
|
70
|
-
subscribes to a run, the server sends one `run_snapshot`, then a stream of
|
|
71
|
-
`run_patch` messages:
|
|
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`:
|
|
72
119
|
|
|
73
120
|
```json
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
|
79
131
|
```
|
|
80
132
|
|
|
81
|
-
|
|
82
|
-
must resubscribe and take a fresh snapshot.
|
|
83
|
-
- `patch` is JSON Patch (RFC 6902) plus one extension op: `append`, whose
|
|
84
|
-
`value` is an array of items appended to the array at `path`. Semantically
|
|
85
|
-
`append` equals a sequence of `add` ops at `/-`; it exists so that the
|
|
86
|
-
common case (trace and session growth) stays compact and readable.
|
|
87
|
-
- Session growth uses `append` at `/session/entries` and `/session/events`.
|
|
88
|
-
Capture changes use `replace` at `/session/capture`. Changes to the derived
|
|
89
|
-
tail diagnostics use `replace` at `/session/eventsMalformed` and
|
|
90
|
-
`/session/eventsTornTail`.
|
|
91
|
-
- The server waits 50 ms after a filesystem notification before refreshing,
|
|
92
|
-
so one token burst normally becomes one revision. Batch boundaries never
|
|
93
|
-
merge or alter event records.
|
|
94
|
-
- Patches are computed against the previous view revision; applying them in
|
|
95
|
-
order reproduces the server's view exactly.
|
|
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.
|
|
96
134
|
|
|
97
135
|
## Messages
|
|
98
136
|
|
|
99
137
|
Client to server:
|
|
100
138
|
|
|
101
|
-
|
|
|
102
|
-
| ---------------- |
|
|
103
|
-
| `watch_runs` |
|
|
104
|
-
| `watch_run` | `runId`
|
|
105
|
-
| `unwatch_run` | `runId`
|
|
106
|
-
| `
|
|
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`. |
|
|
107
146
|
|
|
108
147
|
Server to client:
|
|
109
148
|
|
|
110
|
-
|
|
|
111
|
-
| -------------- |
|
|
112
|
-
| `hello` | `protocol`
|
|
113
|
-
| `runs` | `runs`
|
|
114
|
-
| `run_snapshot` | `runId`, `revision`, `view`
|
|
115
|
-
| `run_patch` | `runId`, `revision`, `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
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. |
|
|
118
158
|
|
|
119
|
-
|
|
120
|
-
an `error` because there is no artifact directory.
|
|
159
|
+
The run list contains `presentationRevision`, `manifest`, `live`, and `possiblyInterrupted`. It contains no payload bodies.
|
|
121
160
|
|
|
122
|
-
|
|
123
|
-
`possiblyInterrupted`:
|
|
161
|
+
## Several clients
|
|
124
162
|
|
|
125
|
-
|
|
126
|
-
{ "type": "runs", "runs": [ { "manifest": { … }, "live": true, "possiblyInterrupted": false } ] }
|
|
127
|
-
```
|
|
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.
|
|
128
164
|
|
|
129
|
-
|
|
130
|
-
run views use patches.
|
|
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.
|
|
131
166
|
|
|
132
167
|
## Reconnection
|
|
133
168
|
|
|
134
|
-
The
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
bounded backoff, sends `watch_runs` after the next valid `hello`, and restores
|
|
138
|
-
the current `watch_run`. A reconnect receives a fresh snapshot before later
|
|
139
|
-
patches.
|
|
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.
|
|
170
|
+
|
|
171
|
+
## SQLite consistency
|
|
140
172
|
|
|
141
|
-
|
|
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.
|
|
142
174
|
|
|
143
|
-
The
|
|
144
|
-
reads a committed run projection, immutable events, session rows, and blob
|
|
145
|
-
values. A transaction is either fully visible or not visible, so a client never
|
|
146
|
-
observes half of a state transition.
|
|
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.
|
|
@@ -281,6 +281,45 @@ Document:
|
|
|
281
281
|
|
|
282
282
|
No workflow definition needs an opt-in or terminal restart step.
|
|
283
283
|
|
|
284
|
+
### 9. Render deferred terminal turns as compact cards
|
|
285
|
+
|
|
286
|
+
**Where**
|
|
287
|
+
|
|
288
|
+
- `src/extension/deferred-turn.ts`
|
|
289
|
+
- `src/extension/index.ts`
|
|
290
|
+
- Deferred-turn renderer unit tests
|
|
291
|
+
- Real-Pi end-to-end tests
|
|
292
|
+
|
|
293
|
+
**Change**
|
|
294
|
+
|
|
295
|
+
Register a custom TUI message renderer for the existing `pi-workflows-deferred-turn` message type through Pi's documented `pi.registerMessageRenderer()` API.
|
|
296
|
+
|
|
297
|
+
Keep the complete existing message content unchanged. The model and session history must still receive the terminal facts, exact input, bounded result, restart history, and instructions. Do not replace that content with a summary, split it into another entry, or hide the deferred fallback with `display: false`.
|
|
298
|
+
|
|
299
|
+
Add small, bounded presentation fields to the existing message details. The fields cover the workflow name, terminal state or cause, run identity, terminal reason kind, and restart count and limit when available. The renderer must read those fields directly and must not parse the model prompt.
|
|
300
|
+
|
|
301
|
+
The collapsed card must show a concise workflow summary. It must not show the terminal facts JSON, exact input, result, fingerprint, or full model instructions. The expanded card must show the complete existing content through Pi's standard `expanded` state, consistent with agent-step message cards.
|
|
302
|
+
|
|
303
|
+
Use standard Pi TUI components and theme colors. Sanitize all workflow-derived display text. Missing or malformed details must produce a safe generic card instead of an exception.
|
|
304
|
+
|
|
305
|
+
Keep the behavior workflow-agnostic. Ordinary deferred fallbacks, terminal decisions, and restored messages use the same renderer. Presentation messages that already use `display: false` stay unchanged. Headless and RPC behavior stays unchanged. Rendering must not create a duplicate session entry or model turn.
|
|
306
|
+
|
|
307
|
+
This change uses only `pi.sendMessage()` and `pi.registerMessageRenderer()`. It adds no Pi core or private API use, database table, migration, store, service, controller, daemon, or external resource.
|
|
308
|
+
|
|
309
|
+
**Verification**
|
|
310
|
+
|
|
311
|
+
Focused tests must prove that:
|
|
312
|
+
|
|
313
|
+
- collapsed output shows bounded workflow, state or cause, run, and restart fields
|
|
314
|
+
- collapsed output omits the full prompt, terminal JSON, input, result, and fingerprint
|
|
315
|
+
- expanded output contains the complete model-facing content
|
|
316
|
+
- completed, failed, timed-out, cancelled, launch-failure, and maxSteps states render correctly
|
|
317
|
+
- long or terminal-unsafe fields are safe
|
|
318
|
+
- missing and malformed details do not throw
|
|
319
|
+
- the renderer registers once for `pi-workflows-deferred-turn`
|
|
320
|
+
- restored messages render without creating another entry or turn
|
|
321
|
+
- real Pi still gives the provider the complete prompt while the session record keeps the custom message type and renderer details
|
|
322
|
+
|
|
284
323
|
## Contract changes
|
|
285
324
|
|
|
286
325
|
- The workflow tool gains `restart`.
|
|
@@ -293,6 +332,9 @@ No workflow definition needs an opt-in or terminal restart step.
|
|
|
293
332
|
- Restart lineage uses existing run launch data.
|
|
294
333
|
- No new store, service, controller, or Pi API is added.
|
|
295
334
|
- No original-message provenance contract is added.
|
|
335
|
+
- The existing deferred-turn message details gain small, bounded presentation fields.
|
|
336
|
+
- The existing deferred-turn message content remains complete and unchanged for the model and session history.
|
|
337
|
+
- Interactive Pi renders deferred turns through the public message-renderer API. Headless and RPC delivery do not change.
|
|
296
338
|
|
|
297
339
|
## Test plan
|
|
298
340
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Build the incremental and virtualized piw viewer
|
|
3
|
+
author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
|
|
4
|
+
date: 2026-08-28
|
|
5
|
+
status: implemented
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Incremental and virtualized piw viewer plan
|
|
9
|
+
|
|
10
|
+
## Goal
|
|
11
|
+
|
|
12
|
+
Make `piw` stay fast as workflow history grows.
|
|
13
|
+
|
|
14
|
+
Pi Workflows will expose one canonical viewer projection for local and remote clients. Every viewer-visible change will advance one run presentation revision and produce a bounded, resumable JSON Patch delta directly from the committed change. Viewers will load small run-list rows, the graph state they need, and bounded pages around the current replay position.
|
|
15
|
+
|
|
16
|
+
The graph will use bounded adaptive cards. Each card will use space based on its own content. One long name or large switch will not enlarge unrelated cards.
|
|
17
|
+
|
|
18
|
+
This plan replaces the tactical indexed-worker design. That design would stop loading every run, but it would still poll and rebuild the complete selected run. The selected design removes that remaining scaling limit.
|
|
19
|
+
|
|
20
|
+
## Current problems
|
|
21
|
+
|
|
22
|
+
The current local viewer checks SQLite every 300 ms. Its run-list path loads every run in full, and its refresh path then loads every run again. It also reloads terminal runs. The UI performs this work on the same path that handles input and drawing.
|
|
23
|
+
|
|
24
|
+
A measured database contained 44 runs, 17,812 workflow events, 9,268 session entries, and 48,634 session events. The database was 97 MB. An eight-second all-runs check reached 370 MB peak RSS and used 1.29 CPU seconds. A single-run check reached 37 MB and used 0.04 CPU seconds.
|
|
25
|
+
|
|
26
|
+
Boxed graph mode also calculates one width and height for the whole graph. A 58-character hierarchical name and a 14-way switch made every card about 62 columns wide and 21 rows high. Most cards contained large empty areas.
|
|
27
|
+
|
|
28
|
+
## Boundaries
|
|
29
|
+
|
|
30
|
+
This design keeps SQLite as the canonical durable store. It keeps `piw` as a standalone Rust TUI and keeps the TypeScript graph renderer.
|
|
31
|
+
|
|
32
|
+
The implementation must not change workflow execution, saved Pi session content, full workflow history, theme behavior, keyboard or mouse meaning, replay meaning, or Pi extension boundaries.
|
|
33
|
+
|
|
34
|
+
The WebSocket server remains loopback-only. Remote use continues through an SSH tunnel. The design adds no external service, sidecar, cache database, or telemetry system.
|
|
35
|
+
|
|
36
|
+
The existing JSON Patch operations and `append` extension remain the change format. The implementation changes how patches are created and what they target. It must not rebuild two complete run views to find their differences.
|
|
37
|
+
|
|
38
|
+
Pi Workflows is in alpha. If the implementation needs a persisted contract change, it will replace the current `v1` contract in place. It will not add a migration shim, compatibility reader, dual read, dual write, feature flag, or `v2` contract. An incompatible database will fail with a clear reset instruction and remain untouched.
|
|
39
|
+
|
|
40
|
+
The repository source now implements this design. Release work, installation, and adoption of a new `piw` binary remain separate.
|
|
41
|
+
|
|
42
|
+
## Canonical viewer projection
|
|
43
|
+
|
|
44
|
+
One projection will serve local and remote viewers. It will have the following read surfaces:
|
|
45
|
+
|
|
46
|
+
- lightweight rows for the run list;
|
|
47
|
+
- graph state and replay metadata for one run;
|
|
48
|
+
- bounded timeline pages;
|
|
49
|
+
- bounded conversation pages;
|
|
50
|
+
- bounded inspector pages; and
|
|
51
|
+
- ordered JSON Patch deltas after a known run presentation revision.
|
|
52
|
+
|
|
53
|
+
Run-list rows contain only the fields needed by the browser, such as identity, title, status, timestamps, and interruption state. Building the list must not read run outputs, trace payloads, session entry bodies, or session event bodies.
|
|
54
|
+
|
|
55
|
+
The local viewer and `piw serve` must use the same projection rules. They may use different transports, but they must agree on revisions, cursors, pages, gaps, and failure recovery.
|
|
56
|
+
|
|
57
|
+
## Presentation revision
|
|
58
|
+
|
|
59
|
+
Each run has one authoritative presentation revision. The revision covers every fact that can change the viewer:
|
|
60
|
+
|
|
61
|
+
- run status and current position;
|
|
62
|
+
- node attempts and completed steps;
|
|
63
|
+
- workflow trace records;
|
|
64
|
+
- session segments, entries, and temporal events;
|
|
65
|
+
- live settings;
|
|
66
|
+
- follow-up queues and items;
|
|
67
|
+
- human decisions;
|
|
68
|
+
- leases and interruption state; and
|
|
69
|
+
- any other field exposed by the canonical viewer projection.
|
|
70
|
+
|
|
71
|
+
A transaction that changes one of these facts also advances the presentation revision. It creates the corresponding JSON Patch operations from the records changed by that transaction. The database commit makes the domain change, revision, and patch visible together. Patch creation must not read the previous complete run view or compare two complete run documents. A viewer must never observe a revision that does not describe the committed state.
|
|
72
|
+
|
|
73
|
+
The implementation must audit every durable writer that can change viewer output. A missing writer is a correctness bug because it can leave a viewer stale.
|
|
74
|
+
|
|
75
|
+
## JSON Patch deltas and cursors
|
|
76
|
+
|
|
77
|
+
A delta is an ordered list of the existing JSON Patch operations after one presentation revision. The current `add`, `replace`, `remove`, and `append` operations remain available. A client identifies the run and last revision it has applied.
|
|
78
|
+
|
|
79
|
+
Each patch targets one bounded projection document or one bounded page. It does not target one JSON document containing the complete run history. The envelope identifies the target document, run revision, and page revision or cursor needed to apply it safely.
|
|
80
|
+
|
|
81
|
+
The active timeline or conversation page can use `append` as new records arrive. An unloaded or evicted page is not rebuilt only to apply a patch. Its revision changes, and the client fetches the current page if it needs that range. Stable IDs and page boundaries prevent paths from depending on array positions outside the loaded page.
|
|
82
|
+
|
|
83
|
+
The server returns the next available patches when the cursor is valid. If the cursor is missing, invalid, or older than the retained patch window, the server returns a fresh bounded snapshot and the pages needed for the current view.
|
|
84
|
+
|
|
85
|
+
Applying patches in order must produce the same logical view as reading a fresh projection at the final revision. Duplicate patches must be harmless. A client rejects an out-of-order revision, malformed page, invalid path, or mismatched run ID without changing its current good view.
|
|
86
|
+
|
|
87
|
+
The canonical database retains bounded patch records or enough authoritative revisioned records to return the same patches without reconstructing full history. The implementation must prove bounded resume behavior and must not depend on an unbounded in-memory backlog.
|
|
88
|
+
|
|
89
|
+
## Paging and replay
|
|
90
|
+
|
|
91
|
+
Timeline, conversation, and inspector data use bounded pages. A page identifies its run, presentation revision, content range, and neighboring cursors.
|
|
92
|
+
|
|
93
|
+
The viewer loads the page that contains the current step, event, or time. It may prefetch a small number of neighboring pages. It evicts pages outside a documented memory window.
|
|
94
|
+
|
|
95
|
+
A jump to another time loads a page around that point. It does not load the complete timeline or conversation. The viewer may show a short loading state while the page arrives. The amount of data loaded for one jump stays bounded.
|
|
96
|
+
|
|
97
|
+
Replay order and timing keep their current meaning. The temporal session event sequence remains authoritative. Paging changes how the viewer obtains records, not how it interprets them.
|
|
98
|
+
|
|
99
|
+
Search and count displays must state whether they cover the complete run or only loaded pages. A complete search must use a bounded database query or a paged server operation. It must not silently search only the cache.
|
|
100
|
+
|
|
101
|
+
## Multiple clients
|
|
102
|
+
|
|
103
|
+
`piw serve` keeps one shared projection and cache for each watched run.
|
|
104
|
+
|
|
105
|
+
The first watcher starts that run's projection. Later watchers reuse it. Each client keeps its own revision cursor, selected replay position, and loaded pages. A database change is projected once and fanned out to every client that watches the run.
|
|
106
|
+
|
|
107
|
+
The server tracks a watcher count for each run. An unwatch request or client disconnect reduces the count. When the count reaches zero, the server releases the run projection and its pages. Different watched runs load independently. Unwatched runs do not load.
|
|
108
|
+
|
|
109
|
+
A slow client does not block projection work or delivery to other clients. If it falls behind the retained delta window, it receives a fresh bounded snapshot and the pages for its current position. The server does not keep an unbounded queue for that client.
|
|
110
|
+
|
|
111
|
+
Duplicate clients watching one run must not duplicate database reconstruction, graph layout, or delta generation.
|
|
112
|
+
|
|
113
|
+
## Client state and failures
|
|
114
|
+
|
|
115
|
+
The TUI applies database and network results away from the input and drawing path. Input, mouse handling, and frame drawing must not wait for a complete run read, page query, or graph layout.
|
|
116
|
+
|
|
117
|
+
The run list stays usable when one run fails to load. An initial failure shows a sanitized error for that run. A refresh failure keeps the last good view and marks it stale.
|
|
118
|
+
|
|
119
|
+
A revision gap causes a fresh bounded snapshot. A malformed page, invalid cursor, or wrong revision is rejected. The viewer keeps its last good state and requests recovery. Reconnect keeps the current stale label until a valid snapshot or delta stream resumes.
|
|
120
|
+
|
|
121
|
+
Shutdown cancels pending reads and joins local worker tasks. A disconnected client releases server watcher counts and page references.
|
|
122
|
+
|
|
123
|
+
## Semantic graph scene
|
|
124
|
+
|
|
125
|
+
Rust and TypeScript will consume one language-neutral semantic scene contract. The contract contains logical nodes and edges, display text, status, branch meaning, card bounds, edge ports, and stable scene identities.
|
|
126
|
+
|
|
127
|
+
A card calculates its width and height from its own content and then applies documented minimum and maximum bounds. Long text uses one deterministic overflow rule. The local node name remains visible. Full names, details, and branch cases remain available in the inspector.
|
|
128
|
+
|
|
129
|
+
Branch meaning stays on edges and in the selected-node inspector. A switch with many cases can summarize its card content, but it must keep every logical branch and full case label in the scene.
|
|
130
|
+
|
|
131
|
+
One node's label, detail, or branch count cannot change the size of another node. The current graph-wide maximum is removed.
|
|
132
|
+
|
|
133
|
+
Rust and TypeScript use the same size limits, text-width rules, branch order, overflow markers, ports, and fixture output. Unicode width behavior must be pinned by conformance fixtures.
|
|
134
|
+
|
|
135
|
+
## Retained layout and virtualization
|
|
136
|
+
|
|
137
|
+
The viewer keeps a retained logical graph scene between updates. A change updates the affected node or edge and recomputes only the ranks and routes that depend on it.
|
|
138
|
+
|
|
139
|
+
The renderer materializes viewport-near cards and edges. Off-screen graph items remain in the logical scene, so keyboard navigation, centering, search, and replay can still reach them.
|
|
140
|
+
|
|
141
|
+
Hit testing uses exact visible bounds. Edge routing uses exact node ports. Panning, following, clipping, and mouse selection use the same scene geometry.
|
|
142
|
+
|
|
143
|
+
The implementation must define the viewport margin and scene eviction rules. These bounds must be large enough to avoid visible popping during normal movement and small enough to keep memory independent of total graph size.
|
|
144
|
+
|
|
145
|
+
## Performance contract
|
|
146
|
+
|
|
147
|
+
The implementation must meet these structural rules:
|
|
148
|
+
|
|
149
|
+
- idle work does not grow with stored trace or session payload;
|
|
150
|
+
- an unchanged run causes no payload read;
|
|
151
|
+
- building the run list reads no payload bodies;
|
|
152
|
+
- client memory is bounded by run-list rows, watched-run metadata, configured page windows, and viewport-near scene data;
|
|
153
|
+
- two clients watching one run share projection and layout work;
|
|
154
|
+
- a replay jump loads a bounded page; and
|
|
155
|
+
- input handling never waits for full-history reconstruction; and
|
|
156
|
+
- patch creation never reads or compares complete old and new run views.
|
|
157
|
+
|
|
158
|
+
Benchmarks must report raw query counts, rows and bytes read, delta sizes, page loads, layout work, input-stall latency, CPU time, and peak RSS. They must use generated fixtures with increasing run count, trace size, session size, graph size, and client count.
|
|
159
|
+
|
|
160
|
+
Timing measurements must use repeated runs and report the median, range, and a high percentile. Query, load, and memory bounds are deterministic gates. Timing is supporting evidence and must include uncertainty.
|
|
161
|
+
|
|
162
|
+
The measured comparison points are 370 MB peak RSS for the all-runs view and cards of about 62 columns by 21 rows for the large Autoimplement graph. The new design must show that memory and card size no longer grow from unrelated history or unrelated nodes.
|
|
163
|
+
|
|
164
|
+
## Implementation sequence
|
|
165
|
+
|
|
166
|
+
### State revision
|
|
167
|
+
|
|
168
|
+
Update the durable state writer so every viewer-visible transaction advances the run presentation revision and creates its bounded JSON Patch operations directly. Add the authoritative revision and required bounded patch records to the current SQLite schema in place.
|
|
169
|
+
|
|
170
|
+
Audit run transitions, attempts, steps, trace, session capture, settings, follow-ups, decisions, leases, and interruption changes. Add one mutation test for each writer.
|
|
171
|
+
|
|
172
|
+
Exit when a fresh projection and directly created JSON Patch operations agree at every revision without a full-view diff.
|
|
173
|
+
|
|
174
|
+
### Projection reads
|
|
175
|
+
|
|
176
|
+
Add canonical read operations for run-list rows, graph state, replay metadata, timeline pages, conversation pages, inspector pages, bounded snapshots, and JSON Patch deltas.
|
|
177
|
+
|
|
178
|
+
Keep payload bodies out of run-list queries. Make page queries use indexed range bounds. Add query-plan tests and row-read counters.
|
|
179
|
+
|
|
180
|
+
Exit when increasing payload size does not change idle or run-list work.
|
|
181
|
+
|
|
182
|
+
### Local viewer
|
|
183
|
+
|
|
184
|
+
Replace full-run polling with revision checks, page requests, and bounded caches. Apply reads outside the input path. Add loading, stale, error, and recovery states.
|
|
185
|
+
|
|
186
|
+
Keep replay semantics, themes, input, and inspection behavior. Make arbitrary time jumps request the containing page.
|
|
187
|
+
|
|
188
|
+
Exit when a delayed database read does not delay synthetic keyboard or mouse input.
|
|
189
|
+
|
|
190
|
+
### Replay protocol
|
|
191
|
+
|
|
192
|
+
Keep the current JSON Patch protocol and `append` operation. Change the alpha protocol in place so each patch identifies its bounded projection document or page and carries the required presentation revision, page cursor, and recovery data. Keep the current loopback and browser-origin restrictions.
|
|
193
|
+
|
|
194
|
+
Update the server to share one directly created patch stream per watched run. Update the client to keep independent cursors and page caches. Remove complete run-view transfer, full-view diffing, and unbounded patch history.
|
|
195
|
+
|
|
196
|
+
Exit when local and connected viewers produce the same logical view at each revision.
|
|
197
|
+
|
|
198
|
+
### Graph scene
|
|
199
|
+
|
|
200
|
+
Define the semantic scene and bounded adaptive card rules. Port the same contract to Rust and TypeScript. Replace graph-wide card sizing with node-owned bounds.
|
|
201
|
+
|
|
202
|
+
Add retained rank and route updates, viewport materialization, exact hit testing, and full inspector detail. Regenerate shared fixtures.
|
|
203
|
+
|
|
204
|
+
Exit when long names and large switches do not resize unrelated cards and large graphs keep bounded materialized geometry.
|
|
205
|
+
|
|
206
|
+
### Multi-client recovery
|
|
207
|
+
|
|
208
|
+
Add shared watched-run caches, watcher counts, independent client cursors, slow-client recovery, disconnect cleanup, and bounded delivery queues.
|
|
209
|
+
|
|
210
|
+
Exit when many clients can watch one run without duplicate reconstruction and one stalled client cannot delay the others.
|
|
211
|
+
|
|
212
|
+
### Benchmarks and checks
|
|
213
|
+
|
|
214
|
+
Add generated scale fixtures for small and large histories, graphs, replay jumps, and client counts. Record raw structural and timing results. Compare them with the current measurements.
|
|
215
|
+
|
|
216
|
+
Run the full Rust, TypeScript, parity, protocol, end-to-end, and repository checks.
|
|
217
|
+
|
|
218
|
+
## Tests
|
|
219
|
+
|
|
220
|
+
Test presentation revision changes for every viewer-visible writer and remains unchanged for unrelated writes.
|
|
221
|
+
|
|
222
|
+
Test bounded snapshots plus ordered JSON Patch operations against a fresh projection at every revision. Prove that patch creation does not read a complete old or new run view. Cover `add`, `replace`, `remove`, `append`, duplicate, missing, stale, malformed, invalid-path, wrong-page, and wrong-run cases.
|
|
223
|
+
|
|
224
|
+
Test first-page, next-page, previous-page, arbitrary-time, end-of-run, active-tail, and evicted-page behavior for timeline, conversation, and inspector data.
|
|
225
|
+
|
|
226
|
+
Test run-list queries with large payload bodies and prove that no payload rows or blobs are read.
|
|
227
|
+
|
|
228
|
+
Test local and remote parity for current, replayed, reconnecting, stale, and recovered views.
|
|
229
|
+
|
|
230
|
+
Test several clients watching the same run, different runs, unwatching, disconnecting, falling behind, and reconnecting. Prove that one run is projected once and released after its last watcher leaves.
|
|
231
|
+
|
|
232
|
+
Test long labels, long local names, Unicode, large fan-out, loops, back edges, mixed card sizes, clipping, panning, following, centering, keyboard navigation, and mouse hit testing.
|
|
233
|
+
|
|
234
|
+
Test viewport materialization and retained layout with graphs much larger than the terminal. Prove that logical navigation still reaches off-screen nodes.
|
|
235
|
+
|
|
236
|
+
Test failure handling for database read errors, protocol gaps, invalid cursors, malformed pages, interrupted streams, and shutdown during a read.
|
|
237
|
+
|
|
238
|
+
Test performance with growing history and client counts. Assert deterministic query, load, page, cache, and materialized-node bounds. Report timing and memory separately.
|
|
239
|
+
|
|
240
|
+
## Verification
|
|
241
|
+
|
|
242
|
+
Run:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
cargo fmt --check --manifest-path tui/Cargo.toml
|
|
246
|
+
cargo clippy --manifest-path tui/Cargo.toml --all-targets -- -D warnings
|
|
247
|
+
cargo test --manifest-path tui/Cargo.toml
|
|
248
|
+
npm run fixtures
|
|
249
|
+
npm run check
|
|
250
|
+
npm run test:e2e
|
|
251
|
+
npx slophammer-ts@latest dry .
|
|
252
|
+
npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
|
|
253
|
+
npx -y @simpledoc/simpledoc check
|
|
254
|
+
git diff --check
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Use a source-built `piw` in a PTY to verify run selection, replay jumps, box and line modes, panning, mouse selection, reconnect, several clients, and clean exit. Use only generated state for destructive or failure tests.
|
|
258
|
+
|
|
259
|
+
A read-only check against a real growing database may report aggregate counts, timings, query counts, and memory. It must not print workflow titles, prompts, outputs, session text, actor identifiers, channel references, or credentials. It must not write the database.
|
|
260
|
+
|
|
261
|
+
## Implementation record
|
|
262
|
+
|
|
263
|
+
The current alpha schema now stores `viewer_runs` and `viewer_deltas`. A run starts at presentation revision 1. Each viewer-visible write advances that revision in the same SQLite transaction and stores ordered target patches. The store retains 256 revisions. An older or invalid cursor requires a bounded snapshot.
|
|
264
|
+
|
|
265
|
+
Timeline, step, session-entry, session-event, settings, follow-up, and current-update pages contain at most 256 rows. Session rows have run-wide sequence numbers and indexed `(run_id, run_seq)` reads. Step pages keep full detail for the active replay window and return compact graph state for that exact cursor. A separate graph projection keeps only the latest attempt per node and the distinct taken transitions up to the replay cursor. Run-list reads use metadata and do not open payload blobs.
|
|
266
|
+
|
|
267
|
+
The local viewer checks `PRAGMA data_version`. An unchanged check does not scan the run list or load payloads. Local page reads use one overwrite-only worker request, so a newer selection replaces pending work. The source keeps the last good view after a read failure and marks it stale. Direct tail patches update a loaded page without another run read.
|
|
268
|
+
|
|
269
|
+
`piw serve` keeps one bounded projection and one retained graph scene for each watched run. Watchers share that state. Each client keeps its own revision and page cursors. Page requests do not change another client's window. A client that misses retained deltas receives a bounded snapshot and the pages it requests.
|
|
270
|
+
|
|
271
|
+
Boxed cards use a content width from 20 through 28 cells, which gives an outer width from 24 through 32 cells. A card has a 7-row core and at most 3 branch rows. A larger switch shows the first two branch names and a `+N branches` row. Each rank uses its own tallest card. Exact node bounds control ports, centering, and mouse hits. The viewer retains the graph layout between state changes and materializes only the visible rows and columns for Ratatui. Rust checks every TypeScript fixture byte for byte, including long labels, large fan-out, and Unicode labels.
|
|
272
|
+
|
|
273
|
+
The generated scale fixture is 98,242,560 bytes and contains 44 runs, 17,820 run events, 9,284 session entries, and 48,620 session events. Five release-mode runs of 1,000 idle checks each loaded one selected window with 723 payload rows. They performed one run-index read, no later payload loads, and used 9,388 KiB peak RSS, or 9.61 MB. Total time was 1,291 through 2,331 microseconds per 1,000 checks. Median checks took 1 through 2 microseconds, p99 checks took 1 through 2 microseconds, and the maximum was 9 through 27 microseconds. Compared with the measured 370 MB baseline, peak memory fell by about 360.39 MB, or 97.4%. This is much larger than the registered 50% memory reduction gate. A separate run of 1,000,000 unchanged checks used 0.63 CPU seconds in user code and 0.65 CPU seconds in system calls. Its p99 check took 2 microseconds. The deterministic structural gate also passed: idle checks performed zero payload reads after the first bounded selected window.
|
|
274
|
+
|
|
275
|
+
## Rollout
|
|
276
|
+
|
|
277
|
+
The implementation is one hard replacement. State writers, projection readers, local viewer, replay server, replay client, and protocol must agree before release.
|
|
278
|
+
|
|
279
|
+
If the new presentation revision or delta records make the existing alpha database incompatible, startup must stop with the standard clear reset instruction. It must not modify or delete the old database.
|
|
280
|
+
|
|
281
|
+
Release publication and installation of the matching npm and crates.io packages are separate work. Existing installed viewers keep their current behavior until they are replaced.
|
|
282
|
+
|
|
283
|
+
## Outside dependencies
|
|
284
|
+
|
|
285
|
+
SQLite remains responsible for committed read consistency. Ratatui and Crossterm remain responsible for terminal drawing and input. Terminal emulators and Unicode cell-width behavior remain outside repository control. Tests and bounds account for them without assuming they will change.
|
|
286
|
+
|
|
287
|
+
A matching release and installed `piw` binary are required before users receive the fix.
|
|
288
|
+
|
|
289
|
+
## Completion criteria
|
|
290
|
+
|
|
291
|
+
The work is complete when one canonical revisioned projection serves local and remote clients, committed changes create bounded JSON Patch operations without full-view diffing, bounded pages support arbitrary replay positions, shared subscriptions handle several clients without duplicate reconstruction, graph work stays near the viewport, and unrelated history or nodes no longer determine memory, input latency, or card size.
|
|
292
|
+
|
|
293
|
+
All failure, parity, performance, privacy, hard-replacement, and repository checks must pass before release.
|