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