@plurnk/plurnk-service 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SPEC.md +44 -56
- package/dist/content/line-marker.d.ts +2 -17
- package/dist/content/line-marker.d.ts.map +1 -1
- package/dist/content/line-marker.js +11 -319
- package/dist/content/line-marker.js.map +1 -1
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +9 -33
- package/dist/content/matcher.js.map +1 -1
- package/dist/content/mimetype-binary.d.ts +0 -1
- package/dist/content/mimetype-binary.d.ts.map +1 -1
- package/dist/content/mimetype-binary.js +13 -82
- package/dist/content/mimetype-binary.js.map +1 -1
- package/dist/content/path-mimetype.d.ts +0 -1
- package/dist/content/path-mimetype.d.ts.map +1 -1
- package/dist/content/path-mimetype.js +10 -44
- package/dist/content/path-mimetype.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +2 -0
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +8 -2
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Engine.d.ts +2 -0
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +72 -109
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +26 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -0
- package/dist/core/ExecutorRegistry.js +99 -0
- package/dist/core/ExecutorRegistry.js.map +1 -0
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +15 -1
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/git-membership.js +7 -7
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/packet-wire.d.ts +0 -5
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +2 -54
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/resolveForLoop.d.ts +0 -9
- package/dist/core/resolveForLoop.d.ts.map +1 -1
- package/dist/core/resolveForLoop.js +7 -29
- package/dist/core/resolveForLoop.js.map +1 -1
- package/dist/core/results.d.ts +6 -34
- package/dist/core/results.d.ts.map +1 -1
- package/dist/core/results.js +19 -44
- package/dist/core/results.js.map +1 -1
- package/dist/core/scheme-types.d.ts +2 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/core/types.d.ts +2 -26
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -16
- package/dist/core/types.js.map +1 -1
- package/dist/schemes/EffectPolicy.d.ts +6 -0
- package/dist/schemes/EffectPolicy.d.ts.map +1 -0
- package/dist/schemes/EffectPolicy.js +18 -0
- package/dist/schemes/EffectPolicy.js.map +1 -0
- package/dist/schemes/Exec.d.ts +2 -4
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +54 -36
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +9 -13
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.d.ts +2 -4
- package/dist/schemes/Known.d.ts.map +1 -1
- package/dist/schemes/Known.js +0 -6
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Plurnk.d.ts +2 -4
- package/dist/schemes/Plurnk.d.ts.map +1 -1
- package/dist/schemes/Plurnk.js +0 -6
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Skill.d.ts +2 -4
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +0 -6
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Unknown.d.ts +2 -4
- package/dist/schemes/Unknown.d.ts.map +1 -1
- package/dist/schemes/Unknown.js +0 -6
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +2 -3
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +6 -7
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +12 -14
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +1 -3
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +1 -59
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +13 -0
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/clientTurn.js +1 -1
- package/dist/server/clientTurn.js.map +1 -1
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/op_hide.js +1 -1
- package/dist/server/methods/op_hide.js.map +1 -1
- package/dist/server/methods/op_show.js +1 -1
- package/dist/server/methods/op_show.js.map +1 -1
- package/dist/server/noProposals.d.ts +6 -0
- package/dist/server/noProposals.d.ts.map +1 -0
- package/dist/server/noProposals.js +37 -0
- package/dist/server/noProposals.js.map +1 -0
- package/migrations/0000-00-00.01_schema.sql +0 -13
- package/package.json +27 -34
package/SPEC.md
CHANGED
|
@@ -16,7 +16,7 @@ Canonical meanings. When a doc, comment, test name, or commit message uses one o
|
|
|
16
16
|
|---|---|
|
|
17
17
|
| **agent** | The plurnk runtime singleton. Owns agent-scoped state (default scheme registry, agent-wide entries). One per process. |
|
|
18
18
|
| **session** | Durable user-named workspace. Persists across runs and process restarts. Identity: `sessions.id` + unique `sessions.name`. |
|
|
19
|
-
| **run** | A stretch of work within a session. Multiple runs per session. May fork from another run via `parent_run_id`. Owns
|
|
19
|
+
| **run** | A stretch of work within a session. Multiple runs per session. May fork from another run via `parent_run_id`. Owns the log entries. |
|
|
20
20
|
| **loop** | One model-driven or client-driven iteration within a run. Status ∈ {102, 200, 499}. Many loops per run. The model runs inside a loop; each client RPC has its own loop. |
|
|
21
21
|
| **turn** | One round-trip with the LLM (or one client RPC dispatch). One assembled prompt sent, one parsed response handled. Many turns per loop. Identity: `(loop_id, sequence)`. |
|
|
22
22
|
| **op** | One DSL operation the model emits. Parsed into a `PlurnkStatement`. Examples: `EDIT`, `READ`, `SEND`, `FIND`, `COPY`, `MOVE`, `SHOW`, `HIDE`, `EXEC`. One turn produces zero or more ops. |
|
|
@@ -35,14 +35,13 @@ Canonical meanings. When a doc, comment, test name, or commit message uses one o
|
|
|
35
35
|
| **mimetype** | A channel's content type. Drives the render-time handler that produces `preview`/`symbols`. Consumption surface §4.5; author contract: [plurnk-mimetypes](https://github.com/plurnk/plurnk-mimetypes). |
|
|
36
36
|
| **provider** | An LLM transport. Implements `generate({messages, signal})` against a wire protocol. Consumption surface §2; author contract: [plurnk-providers](https://github.com/plurnk/plurnk-providers). |
|
|
37
37
|
|
|
38
|
-
### §0.3 State /
|
|
38
|
+
### §0.3 State / status
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Independent axes on entries and channels. Confusion across them is a recurring source of bugs.
|
|
41
41
|
|
|
42
42
|
| Term | Type | Meaning |
|
|
43
43
|
|---|---|---|
|
|
44
44
|
| **status** | HTTP int | Outcome of an operation. Carried on `log_entries.status_rx`, returned from op handlers. Per the catalogue (§3.5). |
|
|
45
|
-
| **visibility** | `0 \| 1` | Per-`(run, entry, channel)` bit. `1 = indexed` (appears in `packet.system.index`), `0 = hidden` (not rendered, recallable via explicit READ). |
|
|
46
45
|
| **channel state** | `static \| active \| closed \| errored` | Streaming lifecycle of a channel's content. Metadata, not gating — engine renders content regardless of state. |
|
|
47
46
|
| **entry state** | `proposed \| resolved \| cancelled` | Proposal lifecycle. `proposed` = pending client accept; `resolved` = side effect happened; `cancelled` = client rejected. Distinct from channel state. |
|
|
48
47
|
| **outcome** | `string \| null` | Short reason for `failed`/`cancelled` (`"permission:403"`, `"aborted"`, `"not_found"`). Opaque to most callers. |
|
|
@@ -73,7 +72,6 @@ Three independent axes on entries and channels. Confusion across them is a recur
|
|
|
73
72
|
| Term | Meaning |
|
|
74
73
|
|---|---|
|
|
75
74
|
| **packet** | The turn's full exchange shape: `{system, user, assistant, assistantRaw}`. Persisted on `turns.packet`. |
|
|
76
|
-
| **index** | `packet.system.index`. Entry list visible to the model this turn. Built from `visibility` lattice + mimetype.preview. |
|
|
77
75
|
| **log** | `packet.system.log`. Chronological list of `log_entries` in scope this turn. |
|
|
78
76
|
| **render** | The act of computing the packet from current DB state at turn boundaries. Mimetype handlers fire at render time. |
|
|
79
77
|
|
|
@@ -102,7 +100,7 @@ Dependency direction (from root to leaf):
|
|
|
102
100
|
- `plurnk-mimetypes` — handler base classes, discovery, fitting algorithm, matcher dispatch. Handler children are per-mimetype: `plurnk-mimetypes-text-{python,typescript,markdown,html,csv,plain}`, `plurnk-mimetypes-application-{json,yaml,toml,pdf}`, …
|
|
103
101
|
- `plurnk-schemes` — scheme-author types (`SchemeManifest`, `WriterTier`, `LoopFlags`), result-shape contracts (`EntryResult` / `ProposalResult` / `PassthroughResult`), slicing primitives, matcher helpers, `schemeError(...)` constructor. Future scheme children: `plurnk-schemes-http`, `plurnk-schemes-git`, …
|
|
104
102
|
- `plurnk-execs` — `BaseExecutor`, `SubprocessExecutor`, runtime resolver, discovery. Children declare runtimes: `plurnk-execs-sh`, future `plurnk-execs-search`, `plurnk-execs-node`, …
|
|
105
|
-
- **`plurnk-service`** (this repo) — consumes all of the above. Implements the engine, dispatches ops through scheme handlers, hosts the in-tree set of schemes (`plurnk`, `log`, `exec`, `known`, `unknown`, `skill`, `file`), discovers installed mimetype handlers + provider vendors + executor siblings at boot, hosts the daemon (`bin/plurnk-service.
|
|
103
|
+
- **`plurnk-service`** (this repo) — consumes all of the above. Implements the engine, dispatches ops through scheme handlers, hosts the in-tree set of schemes (`plurnk`, `log`, `exec`, `known`, `unknown`, `skill`, `file`), discovers installed mimetype handlers + provider vendors + executor siblings at boot, hosts the daemon (`bin/plurnk-service.ts` over WebSocket + JSON-RPC), and projects packets to the wire per `Packet.json`. Most of the substantive runtime work lives here.
|
|
106
104
|
- **`plurnk`** (client) — terminal UI consuming the daemon's RPC surface. Renders `telemetry/event` notifications, subscribes to log/stream/proposal events. No engine logic of its own.
|
|
107
105
|
|
|
108
106
|
The grammar is the contract. The frameworks consume the contract and add author-facing surfaces. The service consumes the frameworks and runs the engine. The client consumes the service and renders to humans. Each tier is its own published package; each tier's evolution happens in its own repo.
|
|
@@ -252,7 +250,7 @@ Engine → scheme guarantees:
|
|
|
252
250
|
|
|
253
251
|
Author-facing contract: [plurnk-mimetypes](https://github.com/plurnk/plurnk-mimetypes). Below: firing semantics + consumption surface.
|
|
254
252
|
|
|
255
|
-
**Firing semantics.** Render-time consumers. Engine invokes during packet assembly; handlers read current channel content (possibly mid-stream), produce structural view, result lands in
|
|
253
|
+
**Firing semantics.** Render-time consumers. Engine invokes during packet assembly; handlers read current channel content (possibly mid-stream), produce structural view, result lands in the manifest catalog. Schemes do NOT call mimetype handlers at write time. {§4-schemes-do-not-invoke-handlers}
|
|
256
254
|
|
|
257
255
|
### §4.1 Manifest
|
|
258
256
|
|
|
@@ -284,7 +282,7 @@ No mimetype handlers ship in-tree. Framework + every handler are siblings.
|
|
|
284
282
|
|
|
285
283
|
### §4.5 Consumption surface
|
|
286
284
|
|
|
287
|
-
plurnk-service is mimetype-illiterate. Engine hands channel content + mimetype label + budget to `Mimetypes.process({content, hint}, {budget})`; uses `result.
|
|
285
|
+
plurnk-service is mimetype-illiterate. Engine hands channel content + mimetype label + budget to `Mimetypes.process({content, hint}, {budget})`; the manifest build uses `result.totalLines` for each channel's `lines`. Content reaches the model on READ, not as a rendered preview.
|
|
288
286
|
|
|
289
287
|
**Required dependencies** (hard deps in `package.json`):
|
|
290
288
|
|
|
@@ -335,15 +333,13 @@ Every entry has named channels. **Channels are append-only content stores** keye
|
|
|
335
333
|
|
|
336
334
|
EDIT writes one channel per call — the channel resolved from the path's fragment (or the scheme's `defaultChannel` when no fragment). {§5.1-edit-writes-only-body}
|
|
337
335
|
|
|
338
|
-
No stored `preview` channel
|
|
336
|
+
No stored `preview` channel — channel content is pulled on READ, never previewed.
|
|
339
337
|
|
|
340
338
|
Schemes MAY declare multiple channels (`exec`: stdout/stderr/stdin; `http`: body/header; SSE: per-event-type). Each goes in `manifest.channels` with mimetype pinned; rendered independently.
|
|
341
339
|
|
|
342
|
-
### §5.2
|
|
340
|
+
### §5.2 Entries carry no visibility
|
|
343
341
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
Render-time index includes only `indexed=1` channels for the current run. {§5.2-render-filters-by-indexed}
|
|
342
|
+
Every entry is uniformly listed in `plurnk://manifest.json` (§15) and READable — entries have no per-run shown/hidden state. Context curation is the model's, on the **log** (SHOW/HIDE collapse/expand log rows, §6.3), never on entries.
|
|
347
343
|
|
|
348
344
|
### §5.3 Mimetype is a (scheme, channel) property — never a default
|
|
349
345
|
|
|
@@ -362,14 +358,12 @@ Rules:
|
|
|
362
358
|
3. Unknown channel name → 400. {§5.5-unknown-channel-400}
|
|
363
359
|
4. Schemes without `defaultChannel` reject fragment-less EDIT/READ.
|
|
364
360
|
5. Non-default channel EDIT requires entry to exist (404 if absent); default-channel EDIT creates. {§5.5-fragment-on-nonexistent-404}
|
|
365
|
-
6. Fragment-targeted SHOW/HIDE flips only the named channel; fragment-less flips all per §5.2. {§5.5-fragment-targeted-show-hide}
|
|
366
|
-
|
|
367
361
|
| URI | Channel |
|
|
368
362
|
|---|---|
|
|
369
363
|
| `known://france/capital` | body (default) |
|
|
370
364
|
| `known://france/capital#preview` | preview |
|
|
371
|
-
| `exec://
|
|
372
|
-
| `exec://
|
|
365
|
+
| `exec://sh/1/1/2#stdout` | stdout |
|
|
366
|
+
| `exec://sh/1/1/2#stderr` | stderr |
|
|
373
367
|
| `sse://feed/y#data` | data |
|
|
374
368
|
| `log://N/T/A` | (no channel concept; atomic log row) |
|
|
375
369
|
|
|
@@ -380,12 +374,12 @@ Op implications:
|
|
|
380
374
|
|
|
381
375
|
RPC params carry fragments inline via the `target` string (`{ target: "known://x#stderr" }`).
|
|
382
376
|
|
|
383
|
-
**Wire rendering: default channel is path-only.** Heredoc fence omits `#channel` when channel matches `defaultChannel`. Single-channel entries render path-only; multi-channel entries render the default path-only and only non-default carries `#name`.
|
|
377
|
+
**Wire rendering: default channel is path-only.** Heredoc fence omits `#channel` when channel matches `defaultChannel`. Single-channel entries render path-only; multi-channel entries render the default path-only and only non-default carries `#name`.
|
|
384
378
|
|
|
385
379
|
```
|
|
386
380
|
<<notes.md:...:notes.md — file scheme (bare)
|
|
387
|
-
<<exec://
|
|
388
|
-
<<exec://
|
|
381
|
+
<<exec://sh/1/1/2:...:exec://sh/1/1/2 — exec default (stdout)
|
|
382
|
+
<<exec://sh/1/1/2#stderr:...:exec://sh/1/1/2#stderr — non-default
|
|
389
383
|
<<log://1/1/0:...:log://1/1/0 — atomic log row
|
|
390
384
|
```
|
|
391
385
|
|
|
@@ -398,7 +392,7 @@ Each channel has `state ∈ {static, active, closed, errored}`. Metadata only, n
|
|
|
398
392
|
- `closed` — stream ended cleanly. Content final.
|
|
399
393
|
- `errored` — stream ended in error. Content may be partial; reads return what accumulated.
|
|
400
394
|
|
|
401
|
-
Schemes own transitions; UPDATE `entry_channels.state` as connection lifecycle progresses. {§5.6-schemes-own-state-transitions}
|
|
395
|
+
Schemes own transitions; UPDATE `entry_channels.state` as connection lifecycle progresses. {§5.6-schemes-own-state-transitions} State does not gate reads — schemes return accumulated `content` regardless (§5.6-state-is-metadata).
|
|
402
396
|
|
|
403
397
|
Model uses state to anticipate growth between turns. Clients use state for UI (spinner / red border / etc.).
|
|
404
398
|
|
|
@@ -413,9 +407,7 @@ Per-op semantics. AST shapes from `@plurnk/plurnk-grammar`'s `PlurnkStatement`.
|
|
|
413
407
|
AST: `{ op: "EDIT", target, body: string | null, signal: tags | null, lineMarker? }`.
|
|
414
408
|
|
|
415
409
|
- Resolves target channel from fragment (§5.5); unknown channel → 400; undeclared in manifest → engine crash (§5.3).
|
|
416
|
-
- Writes body; `body: null` clears. {§6.1-null-clears}
|
|
417
|
-
- Sets `indexed=1` for written channel in current run. {§6.1-indexed}
|
|
418
|
-
- Returns `{ status: 201, entryId }` for new entries; `{ status: 200, entryId }` for content updates. {§6.1-status-201-200}
|
|
410
|
+
- Writes body; `body: null` clears. {§6.1-null-clears}- Returns `{ status: 201, entryId }` for new entries; `{ status: 200, entryId }` for content updates. {§6.1-status-201-200}
|
|
419
411
|
- A write that changes nothing — identical content and no new tag — returns `{ status: 304, entryId }`, mirroring SHOW/HIDE's no-op (§6.3). {§6.1-noop-304}
|
|
420
412
|
- Tags from `signal[]` apply additively via `entry_tags` (scheme may vary). {§6.1-tags-additive}
|
|
421
413
|
|
|
@@ -431,8 +423,7 @@ AST: `{ op: "READ", target, body: MatcherBody | null, signal: tags | null, lineM
|
|
|
431
423
|
|
|
432
424
|
AST: `{ op: "SHOW"|"HIDE", target, body: MatcherBody | null, signal: tags | null, lineMarker? }`.
|
|
433
425
|
|
|
434
|
-
|
|
435
|
-
- Returns 200 on transition {§6.3-flip-200}, 304 on no-op {§6.3-noop-304}, 404 if entry absent {§6.3-absent-404}.
|
|
426
|
+
SHOW/HIDE operate on the **log** (`log://`) — the model's context-curation surface (§15). HIDE collapses a log row to its path; SHOW restores its body. Non-destructive: rows and bodies persist, re-SHOWable. Entries carry no visibility (§5.2), so SHOW/HIDE against an entry scheme returns 501.
|
|
436
427
|
|
|
437
428
|
### §6.4 COPY (engine-orchestrated)
|
|
438
429
|
|
|
@@ -445,7 +436,6 @@ Engine orchestrates over CRUD primitives (§3.2, §3.4):
|
|
|
445
436
|
3. Mimetype compat — channels' mimetypes must be accepted by `dst_scheme.manifest.channels`. Mismatch → 415.
|
|
446
437
|
4. Tags: `signal` non-null replaces source tags {§6.4-signal-replaces-source-tags}; null/empty carries source tags {§6.4-no-signal-carries-source-tags}.
|
|
447
438
|
5. `dst_scheme.writeEntry({channels, tags})`.
|
|
448
|
-
6. Dest `indexed=1` in current run.
|
|
449
439
|
|
|
450
440
|
Returns 201 on success. Same- and cross-scheme COPY share the orchestrator. {§6.4-cross-scheme-copy}
|
|
451
441
|
|
|
@@ -479,9 +469,13 @@ AST: `{ op: "SEND", target: ParsedPath | null, body: SendBody | null, signal: nu
|
|
|
479
469
|
|
|
480
470
|
AST: `{ op: "EXEC", target (cwd), body: string | null (command), signal: string | null (runtime tag) }`.
|
|
481
471
|
|
|
482
|
-
Engine routes unconditionally to `exec` scheme (path slot is `cwd`, not a URI).
|
|
472
|
+
Engine routes unconditionally to `exec` scheme (path slot is `cwd`, not a URI). The runtime slot (`signal`) selects an executor, resolved against the boot-time `ExecutorRegistry` — siblings discovered and probed at startup, availability cached, default `sh`. Unknown or unavailable runtime → 501 carrying the probe `detail`. {§6.8-registry-resolves}
|
|
473
|
+
|
|
474
|
+
**Effect-gating.** Each executor declares an `effect` (`pure` | `read` | `host`); the service maps it to policy (`EffectPolicy`). A `host` runtime (subprocess; file-backed sqlite) mutates the host → **propose**: the run waits for a human gate, then spawns and writes stdout/stderr to channels of an `exec://<runtime>/<loop>/<turn>/<seq>` entry (the executor is the URI authority; the coordinate that follows matches the op's log-row coordinate, e.g. `exec://sh/1/1/2`), returning `102 Processing` immediately. Channel state transitions (`active` → `closed`/`errored`) drive the model's view at subsequent turn boundaries (§5.6). {§6.8-host-proposes}
|
|
483
475
|
|
|
484
|
-
`
|
|
476
|
+
A `read` runtime (observes external state, e.g. search) or `pure` runtime (no observable effect, e.g. `:memory:` sqlite) is side-effect-free → **auto-run** in-process: no proposal, no human gate, no notification. The run is awaited synchronously and its channel content rides back as the EXEC result body the same turn — not streamed to the entry for a next-turn read. {§6.8-readpure-inline}
|
|
477
|
+
|
|
478
|
+
`SEND[499](exec://<runtime>/<loop>/<turn>/<seq>)` cancels in-flight subprocess via subscription registry's stored AbortController (§7.7).
|
|
485
479
|
|
|
486
480
|
---
|
|
487
481
|
|
|
@@ -497,7 +491,7 @@ Subscription registry is plurnk-service runtime state (its own SQLite table). Ex
|
|
|
497
491
|
|
|
498
492
|
### §7.2 Chunk accumulation
|
|
499
493
|
|
|
500
|
-
SSE event types, WS message types, exec stdout/stderr each map to a named channel. Channel record (`ChannelContent`): `content`, `mimetype`, `tokens`. Active-connection state lives in the subscription registry, not on the channel.
|
|
494
|
+
SSE event types, WS message types, exec stdout/stderr each map to a named channel. Channel record (`ChannelContent`): `content`, `mimetype`, `tokens`. Active-connection state lives in the subscription registry, not on the channel. Chunks accumulate into the channel as they arrive — not buffered until close. {§7.2-chunks-accumulate}
|
|
501
495
|
|
|
502
496
|
### §7.3 No per-chunk log rows
|
|
503
497
|
|
|
@@ -513,10 +507,6 @@ Per-channel previews use `SchemeRegistration.channel_orientations` (grammar): `"
|
|
|
513
507
|
|
|
514
508
|
`<<READ(sse://feed/x#data)<N-M>:…:READ` pulls a slice into a log row when the model wants depth beyond the preview.
|
|
515
509
|
|
|
516
|
-
### §7.6 HIDE is pure archive
|
|
517
|
-
|
|
518
|
-
`<<HIDE(sse://feed/x)::HIDE` demotes from index. Connection persists; channels keep updating. HIDE never tears down a subscription.
|
|
519
|
-
|
|
520
510
|
### §7.7 SEND for stream control
|
|
521
511
|
|
|
522
512
|
- **Cancel:** `<<SEND[499](sse://feed/x)::SEND` — scheme tears down via AbortController.
|
|
@@ -552,7 +542,7 @@ No generator. SQLite-optimal: STRICT (3.37+), `INTEGER PRIMARY KEY` aliasing, ex
|
|
|
552
542
|
### §8.2 SQL/TS responsibility boundary
|
|
553
543
|
|
|
554
544
|
**Lives in SQL:**
|
|
555
|
-
-
|
|
545
|
+
- Render queries — log assembly + the manifest catalog.
|
|
556
546
|
- Cross-scope path collision (CHECK/trigger → 409).
|
|
557
547
|
- Cost rollups (denormalized pico-units; atomic on turn close).
|
|
558
548
|
- Sequence number issuance (1-based per grammar).
|
|
@@ -640,7 +630,7 @@ Plugin discovery (§9) registers whatever's in `node_modules/@plurnk/*`.
|
|
|
640
630
|
|
|
641
631
|
## §12 Operator Configuration
|
|
642
632
|
|
|
643
|
-
Env-var cascade: `.env.example` < `.env` < `.env.<config>` (via `--config=`) < shell < CLI flags. `bin/plurnk-service.
|
|
633
|
+
Env-var cascade: `.env.example` < `.env` < `.env.<config>` (via `--config=`) < shell < CLI flags. `bin/plurnk-service.ts` auto-loads `.env.example`; zero-setup boot.
|
|
644
634
|
|
|
645
635
|
Model selection: separate alias cascade in `ProviderRegistry` (§2.3). `PLURNK_MODEL_<alias>=<provider>/<model-id>` declares; `PLURNK_MODEL=<alias>` selects. Aliases live in `.env`, not `.env.example` (operator-specific).
|
|
646
636
|
|
|
@@ -670,7 +660,7 @@ Feature-flag bools use `process.env.X === "1"` exactly — never `=== "true"`.
|
|
|
670
660
|
|
|
671
661
|
External plugins declare their own env vars in their own `.env.example`; service merges at boot via the cascade.
|
|
672
662
|
|
|
673
|
-
**Admin CLI flag derivation.** `bin/plurnk-service.
|
|
663
|
+
**Admin CLI flag derivation.** `bin/plurnk-service.ts` auto-derives flags from `.env.example`: every `PLURNK_*` becomes `--<kebab-cased-name>` (prefix stripped, lowercased, underscores → dashes). Comment immediately above (no blank line) becomes `-h` description. Non-`PLURNK_*` vars in `.env.example` are bugs — vendor config belongs in the vendor's package namespace.
|
|
674
664
|
|
|
675
665
|
---
|
|
676
666
|
|
|
@@ -891,9 +881,9 @@ Each entry: question, answer, rationale, migration path.
|
|
|
891
881
|
|
|
892
882
|
**Question.** Rummy uses priority-ordered filter chains for packet assembly. Plurnk assembles directly in `Engine.#buildIndex` / `#buildLog`.
|
|
893
883
|
|
|
894
|
-
**Decision.** Engine-direct. Plugin-driven assembly is out of scope.
|
|
884
|
+
**Decision.** Engine-direct. Plugin-driven assembly is out of scope.
|
|
895
885
|
|
|
896
|
-
**Rationale.** Channel + mimetype split already extends rendering
|
|
886
|
+
**Rationale.** Channel + mimetype split already extends rendering. Filter chain would add indirection nothing exercises. Schemes-as-URI-handlers + mimetypes-as-renderers earn extensibility through different shapes than rummy's tag-per-plugin pattern.
|
|
897
887
|
|
|
898
888
|
**Migration path.** If a plugin needs to inject a packet section, grow a single `packet.augment` hook called after `#buildIndex`; plugins return system/user augmentation objects merged into the packet. Additive — engine-direct base stays.
|
|
899
889
|
|
|
@@ -910,9 +900,9 @@ Each entry: question, answer, rationale, migration path.
|
|
|
910
900
|
|
|
911
901
|
- **Provider tokens, stored at write.** `provider.countTokens` is the source of truth; `entry_channels.tokens` (via `_entry-crud`) and `log_entries.tokens` (via `Engine.#writeLog`) are populated at write as a write-time snapshot. A `ceil(len/DIVISOR)` fallback (the divisor tripwire) applies only when no provider tokenizer is wired. {§14.2-tokens-stored-at-write}
|
|
912
902
|
- **Render-weight budget.** The budget headline — `ceiling`, `tokenUsage`, `tokensFree` — is measured from the *assembled packet* (placeholders substituted after measuring), so it reflects what the model actually receives. A `SUM` of stored content-depth would mis-price previews; render-weight is the accurate measure. {§14.2-render-weight-budget}
|
|
913
|
-
- **Per-scheme balance.** A markdown table groups the model's context by scheme —
|
|
903
|
+
- **Per-scheme balance.** A markdown table groups the model's context by scheme — render-weight `tokens` per scheme — anchored `repo, known, unknown, log`, tail sorted by tokens. The model sees at a glance what's eating its window. {§14.2-per-scheme-balance}
|
|
914
904
|
- **Context-window percent.** The headline carries usage as a percent of the ceiling — `usage Y (P%)` — a fullness gauge beside the absolutes. Reads the ceiling already in hand; no extra provider call. {§14.2-context-percent}
|
|
915
|
-
- **Depth re-counted at render.** The manifest re-tokenizes each entry's `tokens` through the live provider at build — never the write-time snapshot — so a model change between loops can't stale the catalog. Every token figure in the packet is render-fresh, manifest and budget alike; nothing trusts a cross-loop cached total.
|
|
905
|
+
- **Depth re-counted at render.** The manifest re-tokenizes each entry's `tokens` through the live provider at build — never the write-time snapshot — so a model change between loops can't stale the catalog. Every token figure in the packet is render-fresh, manifest and budget alike; nothing trusts a cross-loop cached total.
|
|
916
906
|
- **Over-budget is honest.** When usage exceeds the ceiling, `free` floors at 0 and the percent passes 100 — the readout shows the overshoot rather than a negative free, so the model knows it's over and curates down. {§14.2-over-budget-floor}
|
|
917
907
|
|
|
918
908
|
**Rejected / obviated.**
|
|
@@ -934,7 +924,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
934
924
|
|
|
935
925
|
- **Identity on the session.** No `projects` table; `sessions.project_root TEXT` (nullable = headless). `entries.scope` unchanged (`∈ {'agent','session'}`). Workspace = session; no users/auth/multi-tenant.
|
|
936
926
|
- **git-ls-files membership.** git present → tracked files (`git ls-files`) are members with no explicit `add` — channel-less markers, disk is truth. git absent → no fs-walk (non-git/headless get no substrate membership).
|
|
937
|
-
- **EMI eager + relevance-bounded.** Materializes
|
|
927
|
+
- **EMI eager + relevance-bounded.** Materializes active git members at prompt-composition, re-reading disk so a divergent member reflects current content.
|
|
938
928
|
- **Disk-read-first edits.** Disk writes route through `File.edit`, which reads disk before diffing — an untouched member's baseline is its real content, never empty. Silent overwrite is structurally prevented.
|
|
939
929
|
|
|
940
930
|
**Deferred — promised, not yet built.** Each carries an anchor with a deliberately-red test so the deferral is tracked, never silently covered.
|
|
@@ -942,25 +932,24 @@ Each entry: question, answer, rationale, migration path.
|
|
|
942
932
|
- **Constraint overlay — the client supersede.** {§14.3-constraint-overlay} A `session_constraints` table layering *add* (members git misses), *ignore* (drop ones it tracks), and *read-only* (member for read, writes rejected) over the git substrate; glob matching via `node:path.matchesGlob`. git-absent, these `effect='add'` constraints are the *sole* membership source — so this overlay is not merely the override knob, it is the entire membership mechanism without git.
|
|
943
933
|
- **EMI divergence signal.** {§14.3-emi-divergence-signal} When EMI re-reads a member whose disk content changed out-of-band, emit a synthetic log entry so the model sees the change (the re-read is built; only the signal is deferred).
|
|
944
934
|
|
|
945
|
-
**Rationale.** No users/tenants. Session is the right scope unit. git+constraints membership keeps the model out of fs-walk territory. EMI's eager-relevance-bounded firing prevents stale
|
|
935
|
+
**Rationale.** No users/tenants. Session is the right scope unit. git+constraints membership keeps the model out of fs-walk territory. EMI's eager-relevance-bounded firing prevents stale content without per-turn full-repo cost.
|
|
946
936
|
|
|
947
937
|
**Migration path.** Tenancy / cross-session shared workspaces require a `workspaces` table joining sessions to workspace id, with constraints lifted off `session_constraints`. Disk co-location semantics unchanged.
|
|
948
938
|
|
|
949
939
|
### §14.4 Budget enforcement: the grinder
|
|
950
940
|
|
|
951
|
-
**Question.** §14.2 surfaces the budget honestly and the model curates against `tokensFree` — almost always enough.
|
|
941
|
+
**Question.** §14.2 surfaces the budget honestly and the model curates against `tokensFree` — almost always enough. Two states defeat self-regulation, neither the model's doing: a jumbo prompt (the turn-0 environment), and an unexpectedly large read. (A jumbo repo is no longer its own case — with no index nothing auto-renders the repo; it surfaces only as a large `manifest.json` READ, which the model chunks like any big read.) What enforces the ceiling when the signal isn't enough?
|
|
952
942
|
|
|
953
|
-
**Decision — a pre-LLM grinder, fired only on actual overflow.** In `Engine.runTurn`, after the packet is assembled (`#buildRequestPacket`) and before `provider.generate`, the assembled render-weight (§14.2) is measured against the ceiling. At or under → the packet ships untouched; the grinder never trims speculatively or "helpfully." {§14.4-overflow-only} On overflow it
|
|
943
|
+
**Decision — a pre-LLM grinder, fired only on actual overflow.** In `Engine.runTurn`, after the packet is assembled (`#buildRequestPacket`) and before `provider.generate`, the assembled render-weight (§14.2) is measured against the ceiling. At or under → the packet ships untouched; the grinder never trims speculatively or "helpfully." {§14.4-overflow-only} On overflow it reverts the prior turn, then hard-stops if that isn't enough:
|
|
954
944
|
|
|
955
945
|
- **Prior-turn rollback.** The immediately-prior turn's log entries — the latest emissions, the ones that pushed the packet over — are hidden (`indexed=0`, the same flag the model's own HIDE uses); the prior turn fit by induction, so reverting it usually lands back under. Hidden, not deleted: rows and bodies persist and are re-SHOWable, so log *history* is preserved while the render shrinks. {§14.4-layer1-rollback}
|
|
956
|
-
- **
|
|
957
|
-
- **Hard stop.** If the packet still overflows with only the manifest left, the loop abandons at 499 (`engine_loop_cancel`) — the path `maxTurns` and the strike threshold already use. No third pass. {§14.4-hard-413-abort}
|
|
946
|
+
- **Hard stop.** If the packet still overflows after the prior-turn rollback, the loop abandons at 499 (`engine_loop_cancel`) — the path `maxTurns` and the strike threshold already use. No further passes. {§14.4-hard-413-abort}
|
|
958
947
|
|
|
959
948
|
**Strike coupling.** A grinder fire bumps the engine's `turnErrors` — the same internal counter cycle detection feeds — so an overflow counts toward the strike streak that ends a runaway loop at 499. This is the pressure that keeps self-curation the path of least resistance. {§14.4-strike-coupling} **Turn 0/1 is exempt:** the first turn's overflow precedes any model action — it's the environment, not the model — so it never strikes. {§14.4-soft-turn-0-1}
|
|
960
949
|
|
|
961
950
|
**What the model sees.** A `budget_overflow` telemetry event (§15.1), in the model's own terms: which of its entries left the window, by scheme. No mechanism vocabulary — no "layer," no "grinder," no "reclaim" — and no advice. The engine reports *what happened to the model's world*; the per-scheme budget table (§14.2) is the diagnostic surface, and the model — which can see what changed in its repo, its reads, its turn — diagnoses the cause the engine can't attribute. {§14.4-event-model-terms} Per the gamification policy (§15.1), the *strike* the overflow triggers stays engine-internal; the model sees the hidden entries, never the accounting.
|
|
962
951
|
|
|
963
|
-
**Rationale.** The model owns curation (§14.2); the grinder is the exceptional backstop. It only *hides* — reversibly — the prior turn's render
|
|
952
|
+
**Rationale.** The model owns curation (§14.2); the grinder is the exceptional backstop. It only *hides* — reversibly — the prior turn's render; nothing is deleted, so the model can SHOW it back and log history stays intact. Rummy's §1316 spec described clearing log *bodies*, but its code instead hid the prior turn whole — because body-clearing is destructive (it deletes the read result) and bespoke. The code was the lesson; plurnk follows it.
|
|
964
953
|
|
|
965
954
|
**Migration path.** None on mechanism. Speculative or non-overflow trimming is a different feature, deliberately excluded — the grinder fires only in response to actual overflow.
|
|
966
955
|
|
|
@@ -991,9 +980,9 @@ type Packet = {
|
|
|
991
980
|
};
|
|
992
981
|
```
|
|
993
982
|
|
|
994
|
-
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a system-origin `EDIT` against `plurnk://prompt/<loop_id>` (indexable, body channel, text/markdown). At render time the current loop's
|
|
983
|
+
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a system-origin `EDIT` against `plurnk://prompt/<loop_id>` (indexable, body channel, text/markdown). At render time the current loop's prompt body materializes into `packet.user.prompt`; the entry itself stays READ/HIDE-able like any other.
|
|
995
984
|
|
|
996
|
-
**The entry catalog.** `plurnk://manifest.json` is a real session entry the model READs to discover what's available — rewritten every turn as a live view of the full entry set. Built in the schemes layer (`_entry-manifest`) and materialized like any entry (the engine only orchestrates the per-turn write — the same pattern as git membership), so it's
|
|
985
|
+
**The entry catalog.** `plurnk://manifest.json` is a real session entry the model READs to discover what's available — rewritten every turn as a live view of the full entry set. Built in the schemes layer (`_entry-manifest`) and materialized like any entry (the engine only orchestrates the per-turn write — the same pattern as git membership), so it's READable and queryable. Body is `application/json`: a flat, **complete, unranked** array — one item per entry across all schemes, every entry listed in no relevance order, each `{ path, channels: { <name>: { mimetype, tokens, lines } } }`. The model ranks and filters the catalog itself by querying it (task-aware); the catalog never ranks for it — the instant it did, it would be an index again. `tokens` is the provider's write-time count (budget depth), `lines` the content extent from `Mimetypes.process().totalLines`. The engine counts neither. It does not list itself. {§15-manifest-catalog}
|
|
997
986
|
|
|
998
987
|
### §15.1 user.telemetry — model-facing runtime telemetry
|
|
999
988
|
|
|
@@ -1031,7 +1020,7 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
|
|
|
1031
1020
|
|
|
1032
1021
|
### §15.2 user.system_requirements — static per-turn rules
|
|
1033
1022
|
|
|
1034
|
-
Rendered at the END of the user packet under `# Plurnk System Requirements` — closest to the assistant turn so the contract the model has to honor is the most recent text it sees. Contains rules the grammar block doesn't cover (canonical example: "Conclude the loop with `<<SEND[200]:answer:SEND`").
|
|
1023
|
+
Rendered at the END of the user packet under `# Plurnk System Requirements` {§15.2-requirements-render-last} — closest to the assistant turn so the contract the model has to honor is the most recent text it sees. The header is omitted entirely when the requirements string is empty. {§15.2-requirements-omitted-when-empty} Contains rules the grammar block doesn't cover (canonical example: "Conclude the loop with `<<SEND[200]:answer:SEND`").
|
|
1035
1024
|
|
|
1036
1025
|
**Sourcing:** caller supplies the string via `runLoop({ requirements })` / `runTurn({ requirements })`. Plurnk-service exposes `PATHS.defaultRequirements` (resolves `PLURNK_REQUIREMENTS` env → in-package `requirements.md`). No DB cascade — same string every turn.
|
|
1037
1026
|
|
|
@@ -1061,10 +1050,10 @@ Glob anchoring (`TODO*` starts-with, `*TODO*` contains, `*.log` ends-with, `[Tt]
|
|
|
1061
1050
|
|
|
1062
1051
|
### §16.2 Matcher result shape (uniform across dialects)
|
|
1063
1052
|
|
|
1064
|
-
Body:
|
|
1053
|
+
Body: one match per line as `<line>:\t<value>` — the same `N:\t` form READ emits, so `<L>` can page the result set. Empty → 204. Mimetype = `text/markdown` regardless of source dialect.
|
|
1065
1054
|
|
|
1066
|
-
-
|
|
1067
|
-
-
|
|
1055
|
+
- `<line>` — 1-indexed source line, shifted back to source coordinates when matching inside an `<L>` slice.
|
|
1056
|
+
- `<value>` — the extracted match, rendered bare when it is a single-line string, else JSON-encoded (preserving the one-match-per-line invariant). Polymorphic per dialect:
|
|
1068
1057
|
- **bare regex** → string (full match)
|
|
1069
1058
|
- **anon captures** → array `[c1, c2, …]`
|
|
1070
1059
|
- **named captures** → object `{name: v, …}`. Mixed anon+named uses positional keys `"1"`, `"2"` alongside names.
|
|
@@ -1072,7 +1061,6 @@ Body: JSON array of `{line, matched, matching?}`. Empty → 204. Mimetype = `app
|
|
|
1072
1061
|
- **jsonpath** → JSON value at the path
|
|
1073
1062
|
- **xpath text/attr** → string
|
|
1074
1063
|
- **xpath node** → serialized XML
|
|
1075
|
-
- `matching` — per-instance discriminator. Present for jsonpath wildcards (bracket form like `$['users'][0]['name']`) and xpath multi-match (`(//user)[1]`). Omitted otherwise.
|
|
1076
1064
|
|
|
1077
1065
|
| Dialect | Extracts | Natural use |
|
|
1078
1066
|
|---|---|---|
|
|
@@ -1174,7 +1162,7 @@ Carried from the contract walk; durable.
|
|
|
1174
1162
|
- **COPY `<L>`** → source range, symmetric with READ `<L>`.
|
|
1175
1163
|
- **READ rx** prefixes each line with `N:\t` per §16.6. `sliceLinesRaw` (used by COPY) returns the lines without prefix.
|
|
1176
1164
|
- **FIND body matcher** applies to entry content (all dialects), per-candidate via `Matcher.matchAgainstContent` → `Mimetypes.query` (status 200 = content hit → entry selected). Scope + tags select candidates in SQL; the path-glob is the (target).
|
|
1177
|
-
- **SHOW/HIDE**
|
|
1165
|
+
- **SHOW/HIDE** operate on the **log** (`log://`), not entries (§6.3) — HIDE collapses a log row to its path, SHOW restores its body. Aimed at an entry scheme they return 501.
|
|
1178
1166
|
- **SEND[410]** deletes as a side-effect (not the model idiom; §6.5): with `#fragment`, that channel only; without, the whole entry. **SEND[499]** is owned by the streaming scheme that holds the subscription.
|
|
1179
1167
|
- **File scheme** reads disk content with mimetype detected via `Mimetypes.detect({ path })` (plumbed through `PlurnkSchemeContext.mimetypes`). Binary mimetypes → 415 on READ and EDIT.
|
|
1180
1168
|
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
+
import type { SliceResult, JsonSliceResult, LineEditResult as EditResult } from "@plurnk/plurnk-schemes";
|
|
1
2
|
import type { LineMarker } from "@plurnk/plurnk-grammar";
|
|
2
|
-
export
|
|
3
|
-
status: number;
|
|
4
|
-
text?: string;
|
|
5
|
-
startLine?: number;
|
|
6
|
-
error?: string;
|
|
7
|
-
}
|
|
8
|
-
export interface JsonSliceResult {
|
|
9
|
-
status: number;
|
|
10
|
-
body?: string;
|
|
11
|
-
error?: string;
|
|
12
|
-
}
|
|
13
|
-
export interface EditResult {
|
|
14
|
-
status: number;
|
|
15
|
-
result?: string;
|
|
16
|
-
error?: string;
|
|
17
|
-
}
|
|
3
|
+
export type { SliceResult, JsonSliceResult, EditResult };
|
|
18
4
|
export default class LineMarkerOps {
|
|
19
|
-
#private;
|
|
20
5
|
static sliceLines(content: string, marker: LineMarker): SliceResult;
|
|
21
6
|
static sliceJsonItems(content: string, marker: LineMarker): JsonSliceResult;
|
|
22
7
|
static applyJsonItemEdit(content: string, marker: LineMarker, body: string): EditResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"line-marker.d.ts","sourceRoot":"","sources":["../../src/content/line-marker.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"line-marker.d.ts","sourceRoot":"","sources":["../../src/content/line-marker.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,IAAI,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;AAEzD,MAAM,CAAC,OAAO,OAAO,aAAa;IAC9B,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,WAAW;IACnE,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,eAAe;IAC3E,MAAM,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU;IACvF,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,WAAW;IACtE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU;CAC5F"}
|