@possumtech/rummy 2.1.0 → 2.2.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/.env.example +40 -15
- package/.xai.key +1 -0
- package/PLUGINS.md +169 -53
- package/README.md +38 -32
- package/SPEC.md +366 -179
- package/bin/digest.js +1097 -0
- package/biome/no-fallbacks.grit +2 -2
- package/gemini.key +1 -0
- package/lang/en.json +10 -1
- package/migrations/001_initial_schema.sql +9 -2
- package/package.json +19 -8
- package/service.js +1 -0
- package/src/agent/AgentLoop.js +76 -26
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/Entries.js +238 -60
- package/src/agent/ProjectAgent.js +44 -0
- package/src/agent/TurnExecutor.js +99 -30
- package/src/agent/XmlParser.js +206 -111
- package/src/agent/errors.js +35 -0
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +3 -42
- package/src/agent/materializeContext.js +30 -1
- package/src/agent/runs.sql +8 -18
- package/src/agent/tokens.js +0 -1
- package/src/agent/turns.sql +1 -0
- package/src/hooks/Hooks.js +26 -0
- package/src/hooks/RummyContext.js +12 -1
- package/src/lib/hedberg/README.md +60 -0
- package/src/lib/hedberg/hedberg.js +60 -0
- package/src/lib/hedberg/marker.js +158 -0
- package/src/{plugins → lib}/hedberg/matcher.js +1 -2
- package/src/llm/LlmProvider.js +41 -3
- package/src/llm/openaiStream.js +17 -0
- package/src/plugins/ask_user/ask_user.js +12 -2
- package/src/plugins/ask_user/ask_userDoc.md +1 -5
- package/src/plugins/budget/README.md +29 -24
- package/src/plugins/budget/budget.js +166 -110
- package/src/plugins/cli/README.md +3 -4
- package/src/plugins/cli/cli.js +31 -5
- package/src/plugins/cloudflare/cloudflare.js +136 -0
- package/src/plugins/cp/cp.js +41 -4
- package/src/plugins/cp/cpDoc.md +5 -6
- package/src/plugins/engine/engine.sql +1 -1
- package/src/plugins/env/README.md +5 -4
- package/src/plugins/env/env.js +7 -4
- package/src/plugins/env/envDoc.md +7 -8
- package/src/plugins/error/error.js +56 -15
- package/src/plugins/file/README.md +12 -3
- package/src/plugins/file/file.js +2 -2
- package/src/plugins/get/get.js +59 -36
- package/src/plugins/get/getDoc.md +10 -34
- package/src/plugins/google/google.js +115 -0
- package/src/plugins/hedberg/hedberg.js +13 -56
- package/src/plugins/helpers.js +66 -12
- package/src/plugins/index.js +1 -2
- package/src/plugins/instructions/README.md +44 -47
- package/src/plugins/instructions/instructions-system.md +44 -0
- package/src/plugins/instructions/instructions-user.md +53 -0
- package/src/plugins/instructions/instructions.js +58 -189
- package/src/plugins/known/README.md +6 -7
- package/src/plugins/known/known.js +24 -30
- package/src/plugins/log/log.js +41 -32
- package/src/plugins/mv/mv.js +40 -1
- package/src/plugins/mv/mvDoc.md +1 -8
- package/src/plugins/ollama/ollama.js +4 -3
- package/src/plugins/openai/openai.js +4 -3
- package/src/plugins/openrouter/openrouter.js +14 -4
- package/src/plugins/persona/README.md +11 -13
- package/src/plugins/persona/default.md +29 -0
- package/src/plugins/persona/persona.js +10 -66
- package/src/plugins/policy/policy.js +23 -22
- package/src/plugins/prompt/README.md +37 -27
- package/src/plugins/prompt/prompt.js +13 -19
- package/src/plugins/rm/rm.js +18 -0
- package/src/plugins/rm/rmDoc.md +5 -6
- package/src/plugins/rpc/rpc.js +3 -3
- package/src/plugins/set/set.js +205 -323
- package/src/plugins/set/setDoc.md +47 -17
- package/src/plugins/sh/README.md +6 -5
- package/src/plugins/sh/sh.js +8 -5
- package/src/plugins/sh/shDoc.md +7 -8
- package/src/plugins/skill/README.md +37 -14
- package/src/plugins/skill/skill.js +200 -101
- package/src/plugins/skill/skillDoc.js +3 -0
- package/src/plugins/skill/skillDoc.md +9 -0
- package/src/plugins/stream/README.md +7 -6
- package/src/plugins/stream/finalize.js +100 -0
- package/src/plugins/stream/stream.js +13 -45
- package/src/plugins/telemetry/telemetry.js +27 -4
- package/src/plugins/think/think.js +2 -3
- package/src/plugins/think/thinkDoc.md +2 -4
- package/src/plugins/unknown/README.md +1 -1
- package/src/plugins/unknown/unknown.js +17 -19
- package/src/plugins/update/update.js +4 -51
- package/src/plugins/update/updateDoc.md +21 -6
- package/src/plugins/xai/xai.js +68 -102
- package/src/plugins/yolo/yolo.js +102 -75
- package/src/sql/functions/hedmatch.js +1 -1
- package/src/sql/functions/hedreplace.js +1 -1
- package/src/sql/functions/hedsearch.js +1 -1
- package/src/sql/functions/slugify.js +16 -2
- package/BENCH_ENVIRONMENT.md +0 -230
- package/CLIENT_INTERFACE.md +0 -396
- package/last_run.txt +0 -5617
- package/scriptify/ask_run.js +0 -77
- package/scriptify/cache_probe.js +0 -66
- package/scriptify/cache_probe_grok.js +0 -74
- package/src/agent/budget.js +0 -33
- package/src/agent/config.js +0 -38
- package/src/plugins/hedberg/README.md +0 -71
- package/src/plugins/hedberg/docs.md +0 -0
- package/src/plugins/hedberg/edits.js +0 -55
- package/src/plugins/hedberg/normalize.js +0 -17
- package/src/plugins/hedberg/sed.js +0 -49
- package/src/plugins/instructions/instructions.md +0 -34
- package/src/plugins/instructions/instructions_104.md +0 -8
- package/src/plugins/instructions/instructions_105.md +0 -39
- package/src/plugins/instructions/instructions_106.md +0 -22
- package/src/plugins/instructions/instructions_107.md +0 -17
- package/src/plugins/instructions/instructions_108.md +0 -0
- package/src/plugins/known/knownDoc.js +0 -3
- package/src/plugins/known/knownDoc.md +0 -8
- package/src/plugins/unknown/unknownDoc.js +0 -3
- package/src/plugins/unknown/unknownDoc.md +0 -11
- package/turns/cli_1777462658211/turn_001.txt +0 -772
- package/turns/cli_1777462658211/turn_002.txt +0 -606
- package/turns/cli_1777462658211/turn_003.txt +0 -667
- package/turns/cli_1777462658211/turn_004.txt +0 -297
- package/turns/cli_1777462658211/turn_005.txt +0 -301
- package/turns/cli_1777462658211/turn_006.txt +0 -262
- package/turns/cli_1777465095132/turn_001.txt +0 -715
- package/turns/cli_1777465095132/turn_002.txt +0 -236
- package/turns/cli_1777465095132/turn_003.txt +0 -287
- package/turns/cli_1777465095132/turn_004.txt +0 -694
- package/turns/cli_1777465095132/turn_005.txt +0 -422
- package/turns/cli_1777465095132/turn_006.txt +0 -365
- package/turns/cli_1777465095132/turn_007.txt +0 -885
- package/turns/cli_1777465095132/turn_008.txt +0 -1277
- package/turns/cli_1777465095132/turn_009.txt +0 -736
- /package/src/{plugins → lib}/hedberg/patterns.js +0 -0
package/CLIENT_INTERFACE.md
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
# CLIENT_INTERFACE
|
|
2
|
-
|
|
3
|
-
Wire-protocol contract for any client that drives a rummy server (nvim,
|
|
4
|
-
CLI, tbench harness, future GUIs). Pulse + query model: the server
|
|
5
|
-
emits a content-free `run/changed` notification when entries land;
|
|
6
|
-
clients reconcile against the entry store on demand.
|
|
7
|
-
|
|
8
|
-
The entry store is the only source of truth for run progress. The
|
|
9
|
-
server tells the client *that* something changed — never *what*.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## TL;DR
|
|
14
|
-
|
|
15
|
-
1. Connect a JSON-RPC websocket; `rummy/hello` to register the project.
|
|
16
|
-
2. `set run://...` (or omit alias) to start a run; you receive `{ alias }`.
|
|
17
|
-
3. Subscribe to **`run/changed`** pulses (notification from server).
|
|
18
|
-
4. On each pulse, call **`getEntries(run, { since, pattern })`** to fetch
|
|
19
|
-
what's new. The reply is a flat list of insertion-ordered entries.
|
|
20
|
-
Pass `withBody: true` if you want the body inline (otherwise omit and
|
|
21
|
-
pull bodies via `getRun` or per-row).
|
|
22
|
-
5. Track the highest `id` you've seen per run; pass it as `since` next pulse.
|
|
23
|
-
6. Resolve any `state: "proposed"` entries by writing back via `set`
|
|
24
|
-
with `state: "resolved" | "cancelled" | "failed"`.
|
|
25
|
-
7. Drive UI from the entry stream + `runs.status`; the run is complete
|
|
26
|
-
when its row reaches a terminal status (200/204/413/422/499/500).
|
|
27
|
-
|
|
28
|
-
No typed payloads. No "render this widget" hints. The store is the
|
|
29
|
-
narrative; the client decides what to show.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## 1. Connection & handshake
|
|
34
|
-
|
|
35
|
-
WebSocket JSON-RPC 2.0. Default port `3044`. Send:
|
|
36
|
-
|
|
37
|
-
```json
|
|
38
|
-
{ "jsonrpc": "2.0", "method": "rummy/hello", "params": {
|
|
39
|
-
"name": "my-client", "projectRoot": "/abs/path",
|
|
40
|
-
"clientVersion": "2.0.0"
|
|
41
|
-
}, "id": 1 }
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Reply: `{ rummyVersion, projectId, projectRoot }`. The server enforces
|
|
45
|
-
**MAJOR-version match** between client and server protocol versions and
|
|
46
|
-
rejects on mismatch.
|
|
47
|
-
|
|
48
|
-
After `rummy/hello`, every subsequent RPC carries the project context
|
|
49
|
-
implicitly — the server knows which project this socket belongs to.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## 2. Starting a run
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{ "method": "set", "params": {
|
|
57
|
-
"path": "run://",
|
|
58
|
-
"body": "Write a brief OC_RIVERS.md ...",
|
|
59
|
-
"attributes": { "model": "fast", "mode": "act", "yolo": false }
|
|
60
|
-
} }
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
The server returns `{ alias }` immediately and kicks off the run async.
|
|
64
|
-
`mode` is `"ask"` or `"act"`. `yolo: true` opts out of client proposal
|
|
65
|
-
resolution (server auto-accepts everything and materializes file edits
|
|
66
|
-
to disk).
|
|
67
|
-
|
|
68
|
-
Aliases are formatted `<modelAlias>_<unixMs>` (e.g.
|
|
69
|
-
`gfast_1777422716094`). The format is **not a stable public contract**
|
|
70
|
-
— treat the alias as an opaque string and don't parse it. To recover
|
|
71
|
-
the model, read `runs.model` via `getRun`.
|
|
72
|
-
|
|
73
|
-
To **cancel**:
|
|
74
|
-
```json
|
|
75
|
-
{ "method": "set", "params": {
|
|
76
|
-
"path": "run://gfast_1777422716094", "state": "cancelled"
|
|
77
|
-
} }
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
To **inject a continuation prompt** into an existing run, write to its
|
|
81
|
-
`run://` path with a `body` and `attributes.mode`. To **fork** a run,
|
|
82
|
-
include `attributes.fork: true`.
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## 3. The `run/changed` pulse
|
|
87
|
-
|
|
88
|
-
The server emits this notification any time an entry write occurs in
|
|
89
|
-
the project. Payload is intentionally minimal:
|
|
90
|
-
|
|
91
|
-
```json
|
|
92
|
-
{ "method": "run/changed", "params": {
|
|
93
|
-
"run": "gfast_1777422716094",
|
|
94
|
-
"runId": 42,
|
|
95
|
-
"path": "log://turn_3/set/notes.md",
|
|
96
|
-
"changeType": "insert"
|
|
97
|
-
} }
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
The pulse is **content-free** — it does not carry the entry body, the
|
|
101
|
-
run status, telemetry, or render hints. It is only a hint that the
|
|
102
|
-
store has moved. Treat it as a debounce signal.
|
|
103
|
-
|
|
104
|
-
**Identifiers.** Both `run` (string alias, what you pass to other
|
|
105
|
-
RPCs) and `runId` (integer, the SQLite primary key) are included.
|
|
106
|
-
Clients should key UI state by `run`. `runId` is informational and may
|
|
107
|
-
be useful when multi-tenancy / cross-project bookkeeping requires a
|
|
108
|
-
globally unique key, but you never pass it back to the server.
|
|
109
|
-
|
|
110
|
-
**Delivery.** Pulses are best-effort and may be coalesced server-side
|
|
111
|
-
during burst writes. Use `since` (§4) for catch-up — a missed pulse is
|
|
112
|
-
recovered on the next reliable pulse by `getEntries(run, { since })`
|
|
113
|
-
returning every entry that landed in the gap. Do not assume one pulse
|
|
114
|
-
per write.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## 4. Reconciling via `getEntries`
|
|
119
|
-
|
|
120
|
-
After receiving (or coalescing) one or more pulses for a given run,
|
|
121
|
-
query for the diff:
|
|
122
|
-
|
|
123
|
-
```json
|
|
124
|
-
{ "method": "getEntries", "params": {
|
|
125
|
-
"run": "gfast_1777422716094",
|
|
126
|
-
"pattern": "**",
|
|
127
|
-
"since": 1234,
|
|
128
|
-
"limit": 200,
|
|
129
|
-
"withBody": false
|
|
130
|
-
} }
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
**Parameters:**
|
|
134
|
-
|
|
135
|
-
- **`run`** — alias from `rummy/hello`-then-`set run://`.
|
|
136
|
-
- **`pattern`** — glob over entry path. Default `"*"`. Use `"**"` to
|
|
137
|
-
mirror everything, `"log://**"` for the audit trail, etc.
|
|
138
|
-
- **`since`** — the highest `id` you've already processed for this run
|
|
139
|
-
(or `0` / omit on first call). Server returns only entries with
|
|
140
|
-
`id > since`, ordered by `id` ASC (insertion order).
|
|
141
|
-
- **`limit`** — cap result count; chunk catch-up by re-querying with
|
|
142
|
-
the new high-water mark.
|
|
143
|
-
- **`scheme`**, **`state`**, **`visibility`** — exact-match filters.
|
|
144
|
-
- **`bodyFilter`** — substring/glob match against entry body content;
|
|
145
|
-
filters which **rows** are returned by their body. *Not* a body-
|
|
146
|
-
inclusion knob — for that, see `withBody`.
|
|
147
|
-
- **`withBody`** — when `true`, each returned row carries `body`
|
|
148
|
-
inline. Default `false` to keep pulse-reconcile traffic lean.
|
|
149
|
-
|
|
150
|
-
**Returned row shape:**
|
|
151
|
-
|
|
152
|
-
| Field | Type | Notes |
|
|
153
|
-
|--------------|--------------------------------|-------------------------------------------------|
|
|
154
|
-
| `id` | integer | Monotonic insertion id; the `since` cursor key. |
|
|
155
|
-
| `path` | string | URI-encoded; e.g. `log://turn_1/update/done`. |
|
|
156
|
-
| `scheme` | string \| null | URI scheme of `path`, or `null` for bare files.|
|
|
157
|
-
| `state` | string | `proposed` / `resolved` / `cancelled` / `failed` / `streaming`. |
|
|
158
|
-
| `outcome` | string \| null | Free-form outcome label (e.g. `not_found`). |
|
|
159
|
-
| `visibility` | string | `visible` / `summarized` / `archived`. |
|
|
160
|
-
| `turn` | integer | Turn this entry was written in. |
|
|
161
|
-
| `tokens` | integer | `countTokens(body)` — included even when body isn't. |
|
|
162
|
-
| `attributes` | object | Always parsed JSON object (never a string). |
|
|
163
|
-
| `body` | string (only with `withBody`) | Full entry body. Omitted by default. |
|
|
164
|
-
|
|
165
|
-
**Important:** when `since` is set, results are ordered by `id` ASC
|
|
166
|
-
(insertion order — what catch-up streams want). When `since` is
|
|
167
|
-
omitted, results are ordered by `path` ASC (browse mode — what
|
|
168
|
-
inventory walks want). Pick the mode that matches your use case.
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## 5. Resolving proposals
|
|
173
|
-
|
|
174
|
-
Some entries land in `state: "proposed"` — the run is parked waiting
|
|
175
|
-
for the client to decide. Examples: file edits (`log://turn_N/set/...`),
|
|
176
|
-
shell commands (`log://turn_N/sh/...`), `ask_user` prompts.
|
|
177
|
-
|
|
178
|
-
To accept, reject, or fail a proposal, write back through `set`:
|
|
179
|
-
|
|
180
|
-
```json
|
|
181
|
-
{ "method": "set", "params": {
|
|
182
|
-
"run": "gfast_1777422716094",
|
|
183
|
-
"path": "log://turn_3/set/notes.md",
|
|
184
|
-
"state": "resolved",
|
|
185
|
-
"body": ""
|
|
186
|
-
} }
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
| Resolution | `state` | Meaning |
|
|
190
|
-
|------------|--------------|--------------------------------------|
|
|
191
|
-
| accept | `resolved` | Apply the proposal; server materializes side effects |
|
|
192
|
-
| reject | `cancelled` | Drop the proposal; the run continues |
|
|
193
|
-
| error | `failed` | The proposal couldn't be applied; the run aborts |
|
|
194
|
-
|
|
195
|
-
For `ask_user` proposals, put the user's answer in `body`.
|
|
196
|
-
|
|
197
|
-
The server's response carries `{ status }` reflecting the run's
|
|
198
|
-
**current** status (102 mid-run, terminal at completion). Do **not**
|
|
199
|
-
treat `status >= 200` from a resolve response as terminal — the run may
|
|
200
|
-
still be active. Use the run row's status (via `getRun` or by tracking
|
|
201
|
-
pulses) as authoritative.
|
|
202
|
-
|
|
203
|
-
If a run was started with `attributes.yolo: true`, you do not need to
|
|
204
|
-
register a resolver — the server auto-accepts every proposal
|
|
205
|
-
server-side and materializes file edits to disk under `projectRoot`.
|
|
206
|
-
For `ask_user` under yolo the server cannot supply a meaningful answer
|
|
207
|
-
on the user's behalf; yolo runs that emit `ask_user` proposals park
|
|
208
|
-
indefinitely (or until cancelled). Treat `ask_user` + yolo as a client
|
|
209
|
-
configuration error.
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## 6. Reading bodies and run state
|
|
214
|
-
|
|
215
|
-
`getEntries` returns metadata only by default. Two ways to get bodies:
|
|
216
|
-
|
|
217
|
-
1. **`getEntries` with `withBody: true`** — bodies for matched rows
|
|
218
|
-
inline. Bandwidth scales with what you query for; bound it with
|
|
219
|
-
`pattern` / `limit`.
|
|
220
|
-
2. **`getRun(run)`** — full structured snapshot of one run with
|
|
221
|
-
bodies, telemetry, history, latest prompt and summary. Use on
|
|
222
|
-
initial open of a run document or after long disconnects.
|
|
223
|
-
|
|
224
|
-
```json
|
|
225
|
-
{ "method": "getRun", "params": { "run": "gfast_1777422716094" } }
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**`getRun` response shape (pinned):**
|
|
229
|
-
|
|
230
|
-
```json
|
|
231
|
-
{
|
|
232
|
-
"run": "gfast_1777422716094",
|
|
233
|
-
"turn": 4,
|
|
234
|
-
"status": 200,
|
|
235
|
-
"model": "gfast",
|
|
236
|
-
"temperature": null,
|
|
237
|
-
"persona": null,
|
|
238
|
-
"context_limit": null,
|
|
239
|
-
"context": {
|
|
240
|
-
"telemetry": {
|
|
241
|
-
"prompt_tokens": 1928,
|
|
242
|
-
"completion_tokens": 75,
|
|
243
|
-
"total_tokens": 2003,
|
|
244
|
-
"cost": 0
|
|
245
|
-
},
|
|
246
|
-
"reasoning": [ { "path": "reasoning://N", "body": "...", "turn": N } ],
|
|
247
|
-
"content": [ { "path": "content://N", "body": "...", "turn": N } ],
|
|
248
|
-
"history": [ {
|
|
249
|
-
"tool": "set",
|
|
250
|
-
"path": "log://turn_N/set/notes.md",
|
|
251
|
-
"status": 200,
|
|
252
|
-
"body": "...",
|
|
253
|
-
"attributes": { "action": "set", "status": 200, "...": "..." },
|
|
254
|
-
"turn": N
|
|
255
|
-
} ]
|
|
256
|
-
},
|
|
257
|
-
"last_user_prompt": "...",
|
|
258
|
-
"last_summary": "..."
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
- `attributes` on `history` rows is always a parsed object (never a
|
|
263
|
-
JSON string).
|
|
264
|
-
- `context.reasoning` and `context.content` carry the model's per-turn
|
|
265
|
-
reasoning and assistant content respectively. Empty arrays for runs
|
|
266
|
-
whose model didn't surface those channels.
|
|
267
|
-
- `last_summary` is the body of the most recent `log://turn_N/update/*`
|
|
268
|
-
entry. `last_user_prompt` is the body of the most recent `prompt://*`
|
|
269
|
-
entry (the active user prompt for this run).
|
|
270
|
-
|
|
271
|
-
For incremental updates inside a session, prefer the pulse +
|
|
272
|
-
`getEntries` flow over polling `getRun`.
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
## 7. Terminal detection & telemetry
|
|
277
|
-
|
|
278
|
-
A run's `status` field on its row is authoritative. Terminal statuses:
|
|
279
|
-
`200, 204, 413, 422, 499, 500`. Any other value is in-flight (typically
|
|
280
|
-
`102`).
|
|
281
|
-
|
|
282
|
-
**Status updates land at `log://turn_N/update/<slug>` with:**
|
|
283
|
-
- `attributes.action = "update"`
|
|
284
|
-
- `attributes.status = <int>` — the integer status code (e.g. `145`,
|
|
285
|
-
`156`, `167`, `200`)
|
|
286
|
-
- `body` — the human-readable summary text
|
|
287
|
-
|
|
288
|
-
Latest `update` entry's body is the latest summary. Terminal status is
|
|
289
|
-
detected when:
|
|
290
|
-
1. The `runs.status` row reaches a terminal value (read via `getRun`),
|
|
291
|
-
**or**
|
|
292
|
-
2. The latest `log://turn_N/update/*` entry's `attributes.status` is in
|
|
293
|
-
the terminal set.
|
|
294
|
-
|
|
295
|
-
(1) is the authoritative read; (2) is a convenience for UIs that are
|
|
296
|
-
already watching the entry stream.
|
|
297
|
-
|
|
298
|
-
**Errors land at `log://turn_N/error/<slug>` with:**
|
|
299
|
-
- `attributes.action = "error"`
|
|
300
|
-
- Body carrying the error detail; `outcome` may carry a short label
|
|
301
|
-
(e.g. `not_found`, `validation`).
|
|
302
|
-
|
|
303
|
-
There is no separate `update://` or `error://` URI scheme — these are
|
|
304
|
-
log channels under the audit trail. Filter via `pattern: "log://**/update/**"`
|
|
305
|
-
or `pattern: "log://**/error/**"` if you only want one channel.
|
|
306
|
-
|
|
307
|
-
**Per-turn telemetry** (token counts, model alias, cached tokens, etc.)
|
|
308
|
-
is in the `turns` table. Surface it by:
|
|
309
|
-
- Calling `getRun(run)` and reading `context.telemetry` (aggregated
|
|
310
|
-
across all turns of the run), **or**
|
|
311
|
-
- Querying the SQLite store directly if your client runs alongside the
|
|
312
|
-
server (private optimization, not a wire contract).
|
|
313
|
-
|
|
314
|
-
Per-turn breakdowns (rather than aggregated) require a direct DB read
|
|
315
|
-
for now; we may add a wire RPC if a need surfaces.
|
|
316
|
-
|
|
317
|
-
A common UI pattern:
|
|
318
|
-
|
|
319
|
-
- Maintain a `Map<runAlias, lastSeenId>`.
|
|
320
|
-
- On `run/changed`: `getEntries(run, { since })`, update `lastSeenId`,
|
|
321
|
-
render new entries inline.
|
|
322
|
-
- For runs in your foreground UI, periodically (every ~2s, or on
|
|
323
|
-
pulse) fetch bodies for newly-arrived rows via `getEntries` with
|
|
324
|
-
`withBody: true` filtered by the new `id` range.
|
|
325
|
-
- Detect terminal by watching for `attributes.action === "update"`
|
|
326
|
-
with `attributes.status` in the terminal set, or by polling
|
|
327
|
-
`runs.status` via `getRun` on a low cadence.
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## 8. Other notifications
|
|
332
|
-
|
|
333
|
-
Beyond `run/changed`, the server emits:
|
|
334
|
-
|
|
335
|
-
| Notification | Purpose |
|
|
336
|
-
|---------------------|--------------------------------------------------------|
|
|
337
|
-
| `ui/render` | **Advisory only.** Streaming model output for live thinking displays. Payload shape and cadence are not part of the wire contract; clients may ignore. The entry stream + bodies is the durable record. |
|
|
338
|
-
| `ui/notify` | Toast-level operator messages. `params: { message, level }`. |
|
|
339
|
-
| `stream/cancelled` | Server-initiated stream abort; client should kill its local process if it owned the stream. |
|
|
340
|
-
|
|
341
|
-
A minimal client can ignore all three and still function — the entry
|
|
342
|
-
store carries the durable record.
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
|
-
## 9. Migrating from the typed-notification protocol
|
|
347
|
-
|
|
348
|
-
The legacy protocol shipped three typed notifications:
|
|
349
|
-
|
|
350
|
-
- `run/state` — fired after each turn with status, summary, history,
|
|
351
|
-
unknowns, telemetry.
|
|
352
|
-
- `run/progress` — turn-status pings ("thinking", "processing").
|
|
353
|
-
- `run/proposal` — pending proposal payload + metadata.
|
|
354
|
-
|
|
355
|
-
All three are **gone**. Their information is fully derivable from the
|
|
356
|
-
entry store:
|
|
357
|
-
|
|
358
|
-
| Old surface | New equivalent |
|
|
359
|
-
|-------------------------|------------------------------------------------------------------|
|
|
360
|
-
| `run/state.status` | `runs.status` row field (via `getRun`), or latest `log://turn_N/update/*` with `attributes.status` in terminal set |
|
|
361
|
-
| `run/state.summary` | latest `log://turn_N/update/*` entry body, or `getRun.last_summary` |
|
|
362
|
-
| `run/state.history` | `getEntries(run, { pattern: "log://**" })` |
|
|
363
|
-
| `run/state.unknowns` | `getEntries(run, { pattern: "unknown://**" })` |
|
|
364
|
-
| `run/state.telemetry` | `getRun(run).context.telemetry` (aggregated) |
|
|
365
|
-
| `run/progress` | (drop — pulse cadence is sufficient) |
|
|
366
|
-
| `run/proposal.proposed` | `getEntries(run, { state: "proposed", since })` |
|
|
367
|
-
|
|
368
|
-
If your client previously closed a document the moment a `run/state`
|
|
369
|
-
arrived with `status >= 200`: do **not** apply the same logic to a
|
|
370
|
-
resolve-RPC response. Track `runs.status` instead.
|
|
371
|
-
|
|
372
|
-
---
|
|
373
|
-
|
|
374
|
-
## 10. Multi-client semantics
|
|
375
|
-
|
|
376
|
-
Multiple clients may connect to the same server simultaneously.
|
|
377
|
-
Conflict resolution is **last-write-wins** at the entry-store level —
|
|
378
|
-
two clients resolving the same `(run, path)` proposal will both
|
|
379
|
-
succeed; the second resolution's `state` and `body` overwrite the
|
|
380
|
-
first. The server does not lock or arbitrate.
|
|
381
|
-
|
|
382
|
-
For UI safety: implement optimistic local state on resolve, but
|
|
383
|
-
re-render from `getEntries` on pulse to absorb any concurrent client's
|
|
384
|
-
write. The pulse will always reach you eventually; don't pessimistically
|
|
385
|
-
lock.
|
|
386
|
-
|
|
387
|
-
---
|
|
388
|
-
|
|
389
|
-
## 11. Reference
|
|
390
|
-
|
|
391
|
-
- Server source of truth: `src/server/ClientConnection.js`,
|
|
392
|
-
`src/plugins/rpc/rpc.js`.
|
|
393
|
-
- Entry store schema: `migrations/001_initial_schema.sql`.
|
|
394
|
-
- Pulse emission point: `hooks.entry.changed` → `run/changed`.
|
|
395
|
-
- Protocol version constant: `src/server/protocol.js`
|
|
396
|
-
(`RUMMY_PROTOCOL_VERSION`).
|