@plurnk/plurnk-service 0.25.0 → 0.27.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 +66 -46
- package/dist/Paths.d.ts +0 -1
- package/dist/Paths.d.ts.map +1 -1
- package/dist/Paths.js +3 -6
- package/dist/Paths.js.map +1 -1
- package/dist/content/path-mimetype.js +2 -2
- package/dist/content/path-mimetype.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +17 -2
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +11 -5
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Engine.d.ts +4 -5
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +149 -49
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +3 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +8 -1
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +39 -15
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +4 -1
- package/dist/core/caps/DbNotifyCaps.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +2 -2
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/fork.js +1 -1
- package/dist/core/fork.js.map +1 -1
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +107 -46
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.js +2 -2
- package/dist/core/packet-wire.js +5 -5
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plugin-trust.d.ts +4 -0
- package/dist/core/plugin-trust.d.ts.map +1 -0
- package/dist/core/plugin-trust.js +23 -0
- package/dist/core/plugin-trust.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +78 -20
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +17 -14
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.js +6 -6
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Plurnk.js +4 -4
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/_entry-chunk.d.ts +11 -0
- package/dist/schemes/_entry-chunk.d.ts.map +1 -0
- package/dist/schemes/_entry-chunk.js +100 -0
- package/dist/schemes/_entry-chunk.js.map +1 -0
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +6 -2
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-graph.js +1 -1
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +14 -6
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.js +2 -2
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-semantic.d.ts +22 -2
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +96 -9
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/exec-env.d.ts +5 -0
- package/dist/schemes/exec-env.d.ts.map +1 -0
- package/dist/schemes/exec-env.js +29 -0
- package/dist/schemes/exec-env.js.map +1 -0
- package/dist/server/Daemon.d.ts +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +11 -1
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/dsl.d.ts +2 -2
- package/dist/server/dsl.js +2 -2
- package/dist/server/envelope.d.ts +1 -0
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +5 -5
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/methods/entry_read.d.ts.map +1 -1
- package/dist/server/methods/entry_read.js +29 -7
- package/dist/server/methods/entry_read.js.map +1 -1
- package/dist/server/methods/log_read.d.ts.map +1 -1
- package/dist/server/methods/log_read.js +14 -1
- package/dist/server/methods/log_read.js.map +1 -1
- package/dist/server/methods/loop_inject.d.ts +5 -0
- package/dist/server/methods/loop_inject.d.ts.map +1 -0
- package/dist/server/methods/loop_inject.js +58 -0
- package/dist/server/methods/loop_inject.js.map +1 -0
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +6 -3
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/op_edit.js +1 -1
- package/dist/server/methods/op_edit.js.map +1 -1
- package/dist/server/methods/op_find.js +1 -1
- package/dist/server/methods/op_find.js.map +1 -1
- package/dist/server/methods/op_fold.js +1 -1
- package/dist/server/methods/op_open.js +1 -1
- package/dist/server/methods/run_fork.d.ts +5 -0
- package/dist/server/methods/run_fork.d.ts.map +1 -0
- package/dist/server/methods/run_fork.js +42 -0
- package/dist/server/methods/run_fork.js.map +1 -0
- package/dist/server/methods/session_constraints.js +6 -6
- package/dist/server/methods/session_constraints.js.map +1 -1
- package/dist/server/methods/session_create.d.ts +1 -0
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +32 -0
- package/dist/server/methods/session_create.js.map +1 -1
- package/migrations/0000-00-00.01_schema.sql +29 -15
- package/package.json +108 -111
- package/requirements.md +1 -2
package/SPEC.md
CHANGED
|
@@ -21,7 +21,7 @@ Canonical meanings. When a doc, comment, test name, or commit message uses one o
|
|
|
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`, `OPEN`, `FOLD`, `EXEC`. One turn produces zero or more ops. |
|
|
23
23
|
| **statement** | Synonym for parsed op. The AST shape `PlurnkStatement` from `@plurnk/plurnk-grammar`. |
|
|
24
|
-
| **action** | One executed op. Action and op are the same thing in different states (op = parsed; action = executed). The execution produces a log_entries row at `log
|
|
24
|
+
| **action** | One executed op. Action and op are the same thing in different states (op = parsed; action = executed). The execution produces a log_entries row at `log:///<L>/<T>/<S>/<op>`. |
|
|
25
25
|
| **dispatch** | The engine routing a statement to its scheme's op handler. |
|
|
26
26
|
|
|
27
27
|
### §storage-terms Storage terms
|
|
@@ -128,7 +128,7 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
|
|
|
128
128
|
|
|
129
129
|
**Question.** A session holds many runs — model, client, plurnk (§lifecycle-terms, §authority-terms) — over one shared manifest. What keeps one run's activity out of another's conversation; what are the *only* ways a run's work reaches another; and does the engine's own work obey the boundary or get a privileged back channel?
|
|
130
130
|
|
|
131
|
-
**Decision — isolation by run; the model is not privileged.** A packet renders exactly one run's log — the assembling run's — against the session's shared manifest (§packet). A run cannot see another's log: isolation is *structural*, a consequence of "a run owns its log entries" (§lifecycle-terms) and "one packet, one run," never an `origin` filter at render time. `origin` (§authority-terms) is **attribution** — the delta's provenance (§env-delta) — never read to
|
|
131
|
+
**Decision — isolation by run; the model is not privileged.** A packet renders exactly one run's log — the assembling run's — against the session's shared manifest (§packet). A run cannot see another's log: isolation is *structural*, a consequence of "a run owns its log entries" (§lifecycle-terms) and "one packet, one run," never an `origin` filter at render time. `origin` (§authority-terms) is **attribution** — the delta's provenance (§env-delta) — never read to filter a row. {§actor-boundary-isolation} {§actor-boundary-origin-not-filter}
|
|
132
132
|
|
|
133
133
|
**Two doors, and only two.** A run's work reaches another run by exactly two channels, and a private log is reachable no other way:
|
|
134
134
|
- the **environment door** — a write to a *shared entry* surfaces to every run sharing it as a folded, attributed delta (§env-delta). *State.*
|
|
@@ -144,25 +144,31 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
|
|
|
144
144
|
|
|
145
145
|
**Migration path.** Largely realized: `Engine.dispatch` is origin-agnostic; client ops run in a per-connection client loop (`_dispatchAsClient`); plurnk EDITs already carry `origin=plurnk`. The keystone is **built** — `dispatchAsPlurnk` spawns the session's reserved `plurnk` run and fires ops through dispatch, mirroring `_dispatchAsClient`. What remains is *repatriation* — the inline plurnk dispatches still bolted into the model's loop (the §env-delta materialization, the manifest build, git auto-add) move onto it.
|
|
146
146
|
|
|
147
|
-
**The keystone's first use: operator reference docs.** `PLURNK_MD_<ALIAS>=<path>` (§operator-config) materializes `<path>` as a `plurnk
|
|
147
|
+
**The keystone's first use: operator reference docs.** `PLURNK_MD_<ALIAS>=<path>` (§operator-config) materializes `<path>` as a `plurnk:///<ALIAS>.md` entry — a `dispatchAsPlurnk` EDIT in the plurnk run, **not** the model's — and the model's turn-0 foists a READ of it. The model reads the doc inline while the materializing EDIT stays out of its log: idiomatic context injection, an ordinary entry + READ rather than a bespoke packet section. The same `PLURNK_MD_*` convention cascades to clients. {§actor-boundary-doc-injection}
|
|
148
|
+
|
|
149
|
+
**Manifest preview.** `PLURNK_MANIFEST_ITEMS` foists a turn-0 READ of `plurnk:///manifest.json` into the model's first turn — the same plurnk-origin foist as the docs — so a run opens with the session catalog instead of blank. `-1` reads the full manifest; a positive `N` slices to the first N items (jsonpath `$[0:N]` — the catalog is JSON); unset / `0` foists nothing. The READ is sequenced *after* the per-turn manifest write, so it hits the catalog rather than 404ing. {§actor-boundary-manifest-preview}
|
|
148
150
|
|
|
149
151
|
### §machine-processes The machine and its processes: session, run, fork
|
|
150
152
|
|
|
151
|
-
**Question.** §actor-boundary isolates runs and lets the runtime self-host, but it stands on an ownership model it never states: what does a *session* own versus a *run*; what is shared versus private; and what does a fork carry? Unstated, the downstream questions — which run `log.read` reads, what a fork copies, where a per-client
|
|
153
|
+
**Question.** §actor-boundary isolates runs and lets the runtime self-host, but it stands on an ownership model it never states: what does a *session* own versus a *run*; what is shared versus private; and what does a fork carry? Unstated, the downstream questions — which run `log.read` reads, what a fork copies, where a per-client window onto the workspace would live — grow subtle, then metastasize. Drawn once, they vanish.
|
|
152
154
|
|
|
153
|
-
**Decision — the session is the world; a run is a log on it.** A **session** is the world: one shared filesystem — the `session`-scoped entries, surfaced as `plurnk
|
|
155
|
+
**Decision — the session is the world; a run is a log on it.** A **session** is the world: one shared filesystem — the `session`-scoped entries, surfaced as `plurnk:///manifest.json` (§packet) — under one membership overlay (§membership). Exactly one filesystem and one overlay per session; neither is per-run. A **run** is a process whose entire private memory is its **log** (§lifecycle-terms) — its loops, turns, and rows, each row carrying its own content, attribution (`origin`/`source`, §env-delta), and fold-state (`indexed`). A run owns **no entries** and **no membership**; even its visibility is not a possession but a bit on its own rows. It is a *history over the shared world, not a world*.
|
|
154
156
|
|
|
155
157
|
**One filesystem.** The entries are the session's: `entries.session_id`, never a run. A write by any run is a write to the one filesystem every run reads; there is no per-run entry set. {§machine-processes-one-filesystem}
|
|
156
158
|
|
|
157
159
|
**One overlay.** Membership — `git ls-files ∪ add − ignore` with read-only (§membership) — is the session's: `session_constraints.session_id`, never a run. It is workspace *curation*, and the workspace *is* the session; two runs are two conversations about one curated workspace and see the same one. Divergent membership is a different session, never a per-run overlay. {§machine-processes-one-overlay}
|
|
158
160
|
|
|
159
|
-
**A run is its log — and nothing beside.** The run-private state is the log and only the log. *What I am looking at* (OPEN/FOLD) is `log_entries.indexed`, a bit on the run's own rows, toggled by ordinary `log
|
|
161
|
+
**A run is its log — and nothing beside.** The run-private state is the log and only the log. *What I am looking at* (OPEN/FOLD) is `log_entries.indexed`, a bit on the run's own rows, toggled by ordinary `log:///` ops — not a second store, and never membership (§open-fold). *What I last saw* needs no shadow either: a run learns its world moved through log entries (§env-delta) — a sibling's write broadcast into its log, an out-of-band disk change detected against the entry's own content and broadcast the same way — never through a per-run snapshot the run cannot see. The log is the whole of a run's memory. {§machine-processes-run-is-its-log}
|
|
162
|
+
|
|
163
|
+
**A run's log is private to packets, not to the session.** Isolation (§actor-boundary) governs what an *actor* sees — its own run, never a sibling's. It does not wall off the *wire*: any connection may read any run's log in its session by id — `log.read({ runId })`, ownership-verified, defaulting to the connection's own run. This is how a conversation client reads the **model** run, where the conversation lives: `loop.run` returns its `modelRunId`, and `session.runs` enumerates a session's runs for a connection that did not drive it live. The read is observation, never packet membership — no actor sees it. {§machine-processes-model-run-readable}
|
|
164
|
+
|
|
165
|
+
**A run carries its actor.** Each run records its `origin` — `model` (the conversation), `client` (a connection's own run), or `plurnk` (the runtime's self-hosting run) — set once at creation and inherited by a fork. `session.runs` returns it, so a conversation client identifies the model run by its actor, not by parsing a renameable name. {§machine-processes-run-origin}
|
|
160
166
|
|
|
161
167
|
**Fork — copy the log, share the world.** A fork is a new run in the *same* session (`runs.parent_run_id`, §lifecycle-terms). It copies the **log** — the rows, their fold-state riding along — so the branch inherits everything the parent observed (§env-delta makes a run's timeline self-contained for exactly this) and diverges freely after. {§machine-processes-fork-copies-the-log} It shares the **world** — the one filesystem, the one overlay — live and uncopied, because the run never owned it. {§machine-processes-fork-shares-the-world}
|
|
162
168
|
|
|
163
169
|
**A session cannot be forked.** There is nothing to branch — a session *is* the shared ground. `runs` carries `parent_run_id`; `sessions` carries no parent. Parallel histories over one workspace are forks of its runs; a divergent workspace is a new session. {§machine-processes-no-fork-session}
|
|
164
170
|
|
|
165
|
-
**Rationale.** The model falls out of one correction: *a run is a history over a shared world, not a world.* Entries are the world (session); the log is the history (run); forking a history need not copy the world, and a run accumulates nothing the log does not already hold. The overlay's session home is forced the same way — it is the world's curation, and the world is shared; per-run it fragments the one manifest, forks the membership read-gate (the §membership security line), and duplicates what FOLD already does at the right level. Every "which run / what's copied / where's the per-client
|
|
171
|
+
**Rationale.** The model falls out of one correction: *a run is a history over a shared world, not a world.* Entries are the world (session); the log is the history (run); forking a history need not copy the world, and a run accumulates nothing the log does not already hold. The overlay's session home is forced the same way — it is the world's curation, and the world is shared; per-run it fragments the one manifest, forks the membership read-gate (the §membership security line), and duplicates what FOLD already does at the right level. Every "which run / what's copied / where's the per-client window" answers itself once the world/log line is drawn.
|
|
166
172
|
|
|
167
173
|
**Migration path.** Mostly stating what the schema already carries: `runs.parent_run_id` and the parentless `sessions` exist (§lifecycle-terms); `session_constraints` is session-level (§membership); §env-delta already makes a run's timeline self-contained, so a fork's log copy suffices. Additive: `run.fork` over the wire (the engine fork is built). Two repatriations: §actor-boundary's "read-only overlay scopes a run's writable surface" becomes a *session* policy bounding every run uniformly; and the §env-delta environment door has shed its per-run snapshot — a run's only memory is its log, so drift is pulled from the shared log (other actors' edits since the run's last turn) and the filesystem narrates its own through the `plurnk` run, both already log entries, never a per-run shadow.
|
|
168
174
|
|
|
@@ -358,7 +364,7 @@ entry.channels[r.channel] = { mimetype: r.mimetype, tokens: tokenize(r.content),
|
|
|
358
364
|
if (isBody) await EntryGraph.populateFrom(db, sessionId, r.entry_id, result.symbols ?? [], result.references ?? []);
|
|
359
365
|
```
|
|
360
366
|
|
|
361
|
-
`hint` short-circuits detection. The service consumes `totalLines` (extent), `symbols`/`references` (the `@graph` index), and `deepJson`/`deepXml` (matcher dispatch); never a rendered preview — content reaches the model on READ. Because this pass runs every assembly over every entry, any content change — by any writer — is reflected in the next packet's index. The `@graph` index is NOT engine *ranking* (the anti-pattern): it's a complete, unranked index the model queries via `FIND @<sym`, the manifest paradigm applied to structure, uniform across schemes (`file
|
|
367
|
+
`hint` short-circuits detection. The service consumes `totalLines` (extent), `symbols`/`references` (the `@graph` index), and `deepJson`/`deepXml` (matcher dispatch); never a rendered preview — content reaches the model on READ. Because this pass runs every assembly over every entry, any content change — by any writer — is reflected in the next packet's index. The `@graph` index is NOT engine *ranking* (the anti-pattern): it's a complete, unranked index the model queries via `FIND @<sym`, the manifest paradigm applied to structure, uniform across schemes (`file:///` is the primary case).
|
|
362
368
|
|
|
363
369
|
**Conformance.** Mimetype-specific behavioral tests live in each handler's own surface. plurnk-service intg covers integration: the engine routes through `Mimetypes.process` with the right hint and the catalog reflects `totalLines`; tests use auto-discovery (production handler set); a custom-handler test injects a stub `BaseHandler` via `loader + discovery`.
|
|
364
370
|
|
|
@@ -378,7 +384,7 @@ Schemes MAY declare multiple channels (`exec`: stdout/stderr/stdin; `http`: body
|
|
|
378
384
|
|
|
379
385
|
### §no-visibility Entries carry no visibility
|
|
380
386
|
|
|
381
|
-
Every entry is uniformly listed in `plurnk
|
|
387
|
+
Every entry is uniformly listed in `plurnk:///manifest.json` (§packet) and READable — entries have no per-run open/folded state. Context curation is the model's, on the **log** (via OPEN/FOLD, §open-fold), never on entries.
|
|
382
388
|
|
|
383
389
|
### §channel-mimetype Mimetype is a (scheme, channel) property — never a default
|
|
384
390
|
|
|
@@ -399,27 +405,27 @@ Rules:
|
|
|
399
405
|
5. Non-default channel EDIT requires entry to exist (404 if absent); default-channel EDIT creates. {§channel-selection-fragment-on-nonexistent-404}
|
|
400
406
|
| URI | Channel |
|
|
401
407
|
|---|---|
|
|
402
|
-
| `known
|
|
403
|
-
| `known
|
|
404
|
-
| `exec
|
|
405
|
-
| `exec
|
|
408
|
+
| `known:///france/capital` | body (default) |
|
|
409
|
+
| `known:///france/capital#symbols` | symbols |
|
|
410
|
+
| `exec:///sh/1/1/2#stdout` | stdout |
|
|
411
|
+
| `exec:///sh/1/1/2#stderr` | stderr |
|
|
406
412
|
| `sse://feed/y#data` | data |
|
|
407
|
-
| `log
|
|
413
|
+
| `log:///N/T/A` | (no channel concept; atomic log row) |
|
|
408
414
|
|
|
409
415
|
Op implications:
|
|
410
416
|
|
|
411
417
|
- EDIT to undeclared channel → 400; read-only channel → 405.
|
|
412
418
|
- COPY/MOVE with fragment is per-channel; design deferred until needed.
|
|
413
419
|
|
|
414
|
-
RPC params carry fragments inline via the `target` string (`{ target: "known
|
|
420
|
+
RPC params carry fragments inline via the `target` string (`{ target: "known:///x#stderr" }`).
|
|
415
421
|
|
|
416
422
|
**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`.
|
|
417
423
|
|
|
418
424
|
```
|
|
419
425
|
<<notes.md:...:notes.md — file scheme (bare)
|
|
420
|
-
<<exec
|
|
421
|
-
<<exec
|
|
422
|
-
<<log
|
|
426
|
+
<<exec:///sh/1/1/2:...:exec:///sh/1/1/2 — exec default (stdout)
|
|
427
|
+
<<exec:///sh/1/1/2#stderr:...:exec:///sh/1/1/2#stderr — non-default
|
|
428
|
+
<<log:///1/1/0:...:log:///1/1/0 — atomic log row
|
|
423
429
|
```
|
|
424
430
|
|
|
425
431
|
### §channel-state Channel state — metadata, not gating
|
|
@@ -462,7 +468,7 @@ AST: `{ op: "READ", target, body: MatcherBody | null, signal: tags | null, lineM
|
|
|
462
468
|
|
|
463
469
|
AST: `{ op: "OPEN"|"FOLD", target, body: MatcherBody | null, signal: tags | null, lineMarker? }`.
|
|
464
470
|
|
|
465
|
-
OPEN/FOLD operate on the **log** (`log
|
|
471
|
+
OPEN/FOLD operate on the **log** (`log:///`) — the model's context-curation surface (§packet). FOLD collapses a log row to its path; OPEN restores its body. Non-destructive: rows and bodies persist, re-OPENable. Entries carry no visibility (§no-visibility), so OPEN/FOLD against an entry scheme returns 501.
|
|
466
472
|
|
|
467
473
|
### §copy COPY (engine-orchestrated)
|
|
468
474
|
|
|
@@ -510,11 +516,13 @@ AST: `{ op: "EXEC", target (cwd), body: string | null (command), signal: string
|
|
|
510
516
|
|
|
511
517
|
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`. {§exec-registry-resolves}
|
|
512
518
|
|
|
513
|
-
**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** (lifecycle §proposal): the run waits for a human gate, then spawns and writes stdout/stderr to channels of an `exec
|
|
519
|
+
**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** (lifecycle §proposal): 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 what the model sees at subsequent turn boundaries (§channel-state). {§exec-host-proposes}
|
|
514
520
|
|
|
515
521
|
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. {§exec-readpure-inline}
|
|
516
522
|
|
|
517
|
-
`SEND[499](exec
|
|
523
|
+
`SEND[499](exec:///<runtime>/<loop>/<turn>/<seq>)` cancels in-flight subprocess via subscription registry's stored AbortController (§stream-control).
|
|
524
|
+
|
|
525
|
+
**Scoped environment.** An EXEC subprocess inherits the *project's* environment — its `.env`, the standard shell vars — so the model's commands run as the project expects; but never plurnk's own secrets: the provider API keys and `PLURNK_*` config are stripped before the spawn, so a model-run command can't `printenv` the engine's keys. The service owns the scoping policy (the denylist); the executor spawns with the env it is handed. {§exec-env-scoped}
|
|
518
526
|
|
|
519
527
|
### §proposal The proposal lifecycle
|
|
520
528
|
|
|
@@ -550,6 +558,8 @@ READ on a streaming scheme is a subscription, not a one-shot. Scheme opens the c
|
|
|
550
558
|
|
|
551
559
|
Subscription registry is plurnk-service runtime state (its own SQLite table). Exists ONLY for cancellation routing. Channel state (§channel-state) + log entries (§no-chunk-rows) carry lifecycle.
|
|
552
560
|
|
|
561
|
+
FOLD/OPEN toggles `log_entries.indexed` (§open-fold) — a per-run render bit, never the subscription registry. FOLDing a streaming entry's log row collapses its body out of the packet but leaves the live stream running: curation is render-only, never cancellation. {§subscriptions-fold-keeps-subscription}
|
|
562
|
+
|
|
553
563
|
### §chunk-accumulation Chunk accumulation
|
|
554
564
|
|
|
555
565
|
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. {§chunk-accumulation-chunks-accumulate}
|
|
@@ -653,9 +663,9 @@ Plugin discovery (§plugin-discovery) registers whatever's in `node_modules/@plu
|
|
|
653
663
|
| `Known.ts` | `@plurnk/plurnk-schemes-known` | Primary narrative entries; session-scoped. |
|
|
654
664
|
| `Unknown.ts` | `@plurnk/plurnk-schemes-unknown` | Open questions / decomposition. |
|
|
655
665
|
| `Skill.ts` | `@plurnk/plurnk-schemes-skill` | Skill docs; same shape as known. |
|
|
656
|
-
| `Plurnk.ts` | may stay in-tree | `plurnk
|
|
657
|
-
| `Log.ts` | may stay in-tree | Read-only coordinate-addressed (`log
|
|
658
|
-
| `File.ts` | `@plurnk/plurnk-schemes-file` | Filesystem-backed. **Model is never trained on `file
|
|
666
|
+
| `Plurnk.ts` | may stay in-tree | `plurnk:///prompt/<loop_id>` carries each loop's prompt. Model-origin writes to `plurnk:///prompt/*` rejected in-handler. |
|
|
667
|
+
| `Log.ts` | may stay in-tree | Read-only coordinate-addressed (`log:///<L>/<T>/<S>`). Renders as JSON meta line in packet log; status ≥ 400 mirrors to `packet.user.telemetry.errors[]` (§telemetry). |
|
|
668
|
+
| `File.ts` | `@plurnk/plurnk-schemes-file` | Filesystem-backed. **Model is never trained on `file:///` and never sees it.** Bare paths are model-facing; `file:///` accepted as input, renders bare. |
|
|
659
669
|
| `Exec.ts` | stays in-tree | Dispatches EXEC op to runtime executors registered via [plurnk-execs](https://github.com/plurnk/plurnk-execs). |
|
|
660
670
|
|
|
661
671
|
**Executors in-tree:** none. Framework + every runtime are siblings. `Exec.ts` dispatches by the EXEC op's `runtime` slot (`sh` default, `node`, `python`) to the matching sibling. Today's registry is hardcoded; plugin discovery migration tracked in [plurnk-execs#1](https://github.com/plurnk/plurnk-execs/issues/1).
|
|
@@ -703,7 +713,8 @@ Model selection: separate alias cascade in `ProviderRegistry` (§provider-instan
|
|
|
703
713
|
| `PLURNK_MIN_CYCLES` | `3` | enforced | Min repetitions before cycle detection fires (§engine-rails). |
|
|
704
714
|
| `PLURNK_MAX_CYCLE_PERIOD` | `4` | enforced | Max period length cycle detection examines (§engine-rails). |
|
|
705
715
|
| `PLURNK_PERSONA` | `persona.md` | enforced | Path to the default persona file. Tail of the persona cascade: loops.persona > runs.persona > sessions.persona > this file. |
|
|
706
|
-
| `PLURNK_MD_<ALIAS>` | (unset) | enforced | Operator reference doc: materializes `<path>` as `plurnk
|
|
716
|
+
| `PLURNK_MD_<ALIAS>` | (unset) | enforced | Operator reference doc: materializes `<path>` as `plurnk:///<ALIAS>.md`, auto-READ into every model run's turn 0 (§actor-boundary). `~` expands to home. |
|
|
717
|
+
| `PLURNK_MANIFEST_ITEMS` | `0` | enforced | Turn-0 manifest preview foisted into the model's first turn. `-1` = full `plurnk:///manifest.json`; positive `N` = the first N items (jsonpath slice); `0` / unset = off (§actor-boundary-manifest-preview). |
|
|
707
718
|
| `PLURNK_PROPOSAL_TIMEOUT_MS` | `300000` | enforced | ms wait for a proposed entry (status=202) to be resolved before timing out. |
|
|
708
719
|
| `PLURNK_PROVIDERS_REASON_LEVEL` | `0` | enforced | Reasoning **magnitude** sent to the providers: `0` = none, positive = effort/budget the provider module translates to wire format (o-series tiers, Anthropic `budget_tokens`). The on/off is the `PLURNK_PROVIDERS_REASONING` gate. |
|
|
709
720
|
| `PLURNK_PLAN` | `0` | enforced | Enable the grammar's `<<PLAN` op — advertised in the `# Plurnk System Tools` packet section (§tools). `1` on, `0` off. |
|
|
@@ -976,7 +987,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
976
987
|
- **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. {§tokenomics-tokens-stored-at-write}
|
|
977
988
|
- **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 the rendered packet; render-weight is the accurate measure. {§tokenomics-render-weight-budget}
|
|
978
989
|
- **Per-turn weight.** A markdown table groups render-weight by turn — the `loop/turn` coordinate prefix — oldest first, the grinder's rollback unit. The model sees which turns are fat and what the rail folds first, and can FOLD ahead of it. {§tokenomics-turn-totals}
|
|
979
|
-
- **Heaviest entries.** A second table lists the ten heaviest log entries by render-weight, each by its `log
|
|
990
|
+
- **Heaviest entries.** A second table lists the ten heaviest log entries by render-weight, each by its `log:///<coord>/<op>` handle — the FOLD targets behind the turn weight. The handle carries the turn, so the two tables interlock. {§tokenomics-largest-entries}
|
|
980
991
|
- **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. {§tokenomics-context-percent}
|
|
981
992
|
- **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.
|
|
982
993
|
- **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. {§tokenomics-over-budget-floor}
|
|
@@ -996,21 +1007,30 @@ Each entry: question, answer, rationale, migration path.
|
|
|
996
1007
|
|
|
997
1008
|
**The boundary is the client's.** The client owns the model's filesystem access in both directions: reads are membership-gated (a file is invisible to the model unless it is a member), and writes are proposals the client accepts or rejects (`yolo` auto-accepts). Writing an entry never implies writing to disk — entries are canonical in the store; disk only moves when the client accepts a side-effecting proposal, and only where `project_root` is set (null = headless, client owns materialization).
|
|
998
1009
|
|
|
999
|
-
**
|
|
1010
|
+
**Tier — session is the world; permissions are the session's.** Membership, the overlay, and the git flags are **session-tier** (`session_constraints.session_id`, service/session config) — never per-run. Every run in a session shares one world (§machine-processes: one filesystem, one overlay); a run is a *log* — a perspective over that world — owning no membership of its own. A declaration reshapes the one world for every run, never per-connection. `runs.origin` is attribution (whose perspective), not a permission.
|
|
1000
1011
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1012
|
+
**Workspace identity.** No `projects` table; `sessions.project_root TEXT` (nullable = headless) anchors the workspace. `entries.scope` unchanged (`∈ {'agent','session'}`). Workspace = session; no users/auth/multi-tenant.
|
|
1013
|
+
|
|
1014
|
+
**git is the substrate.** {§membership-git-membership} git-tracked files (`git ls-files`) are members with no explicit overlay — channel-less markers, disk is truth. git absent → no fs-walk (non-git/headless get no substrate membership); `pick` is then the sole source.
|
|
1015
|
+
|
|
1016
|
+
**Membership is a declared forest of repos.** {§membership-forest} A workspace is not one git repo but a **forest**: membership is the union, over a session-declared set of repos, of each repo's `ls-files` (gitlinks/mode-160000 filtered), each path-prefixed by the repo's path relative to `project_root`. The root need not itself be a repo — a non-git parent of ninety repos resolves to all ninety. A worktree, a submodule, a buried repo are not special cases: each is just another declared repo, resolved `rev-parse --show-toplevel` → `ls-files` in the tree it points at.
|
|
1004
1017
|
- **Membership-gated edits.** {§membership-edit-membership-gate} EDIT is bounded by membership exactly as READ is. An existing **member** is read from disk before diffing, so its baseline is real content, never empty — silent overwrite is structurally prevented. An existing **non-member** is refused (403) *before* any read or write: the model never reads a file it can't see (no leak into the proposal) and never overwrites one (no wiping a gitignored `.env` it never added). A **new path** stays open — proposal→accept adds it to the manifest. Reaching past membership is `EXEC[sh]`'s job, not the file scheme's.
|
|
1005
1018
|
|
|
1006
|
-
|
|
1007
|
-
-
|
|
1008
|
-
-
|
|
1009
|
-
-
|
|
1019
|
+
**The overlay — `pick | view | hide | repo`, removed by `drop`.** A `session_constraints` table (effect ∈ {pick, view, hide, repo}, target) is the client's supersede over git; `drop` removes any declaration. Resolved membership is `(⋃ repo ls-files ∪ pick) − hide`, with `view` enforced at the edit gate.
|
|
1020
|
+
- **`repo`** {§membership-overlay-repo} — declare a git repo (a folder); its `ls-files` join membership, path-prefixed. Submodules/nested repos are separate `repo` declarations — no recursion; the client owns the scan and the security call.
|
|
1021
|
+
- **`pick`** {§membership-overlay-pick} — admit an untracked file git misses: a targeted client-dictated `node:fs` glob scan over untracked matches (files only), 'constraint' origin, reconciled like git members. Enumerated, so the manifest stays exhaustive. git-absent, `pick` is the *sole* membership source.
|
|
1022
|
+
- **`hide`** {§membership-overlay-hide} — exclude a tracked file: resolution drops matches (`node:path.matchesGlob`) and reconciles so the entry set *equals* the member set. The lever to exclude a committed-but-sensitive tracked file; `entries.membership_origin` keeps reconciliation off model-created members.
|
|
1023
|
+
- **`view`** {§membership-overlay-view} — keep a member readable but refuse `File.edit`, 403'd at the membership check before any diff. (Admitting an untracked file as `view` rides on `pick`'s scan.)
|
|
1024
|
+
|
|
1025
|
+
**Sync is idempotent and change-gated.** {§membership-change-gated-sync} Per turn, membership materializes every member's disk content into its entry — but the *work* is gated on a cheap per-member change-detect: a member unchanged on disk since its last sync is not re-read, re-tokenized, or rewritten. **Coverage is exhaustive — every member is detected every turn — but work is proportional to change**, so a ninety-repo forest costs detection, not a full re-read. Invariant: after a pass every member's entry equals its disk content; a no-change pass is a no-op.
|
|
1026
|
+
|
|
1027
|
+
**EMI divergence signal.** {§membership-emi-divergence-signal} The detector that gates the work *is* the one that fires this — one mechanism, not a second full read. When the change-detect finds a member moved out-of-band, the delta detector (§env-delta) surfaces it as a system `EDIT` log row naming the file, `source="file"` — the model sees what changed without diffing the manifest against memory. The model's own edits are write-through (the entry equals disk after a File write), so the scan never mis-attributes them as external divergence.
|
|
1028
|
+
|
|
1029
|
+
**Permission flags.** {§membership-git-flags} `PLURNK_GIT_ALLOWED` is the hard ceiling: `=0` denies all git membership service-wide, un-re-enableable — the sandbox/benchmark lockout. `PLURNK_GIT_AUTO` is the default declaration: `=1` (default) declares an implicit `repo` at `project_root` (no-op if it isn't a git tree); `=0` declares nothing — service/clients `repo`-declare explicitly. `ALLOWED` gates `AUTO`.
|
|
1010
1030
|
|
|
1011
|
-
**Rationale.**
|
|
1031
|
+
**Rationale.** Session is the right scope unit; membership *is* the curation, outsourced and tiered: git bounds it by tracking, the client supersedes by overlay, the model curates its own render by READ/FOLD — the engine curates nothing. The forest falls out of "session = world": one workspace can be many repos, so membership is their union, declared not guessed (the scan and its security are the client's). Exhaustiveness is a property of *coverage*, not *work*: every member is checked every turn so no drift hides, but unchanged members cost only a detect — the full-repo cost is git's to bound (what it tracks) and the client's to bound (`hide`), never the engine's to pay re-reading what hasn't moved.
|
|
1012
1032
|
|
|
1013
|
-
**Migration path.** Tenancy / cross-session shared workspaces require a `workspaces` table
|
|
1033
|
+
**Migration path.** `session_constraints.effect` gains `repo`; the three renames (`add`→`pick`, `ignore`→`hide`, `read-only`→`view`) are wire-surface changes on `session.constrain`. Forest resolution iterates declared repos (was one `ls-files` at root). The change-detect adds a per-member stored signal — mtime+size or content hash, and *that choice is the EMI reliability bound* — gating the existing materialize. `PLURNK_GIT_ALLOWED` replaces `PLURNK_GIT_ENABLED`; `PLURNK_GIT_AUTO` is new. Tenancy / cross-session shared workspaces still require a `workspaces` table lifting constraints off `session_constraints`.
|
|
1014
1034
|
|
|
1015
1035
|
### §grinder Budget enforcement: the grinder
|
|
1016
1036
|
|
|
@@ -1025,7 +1045,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
1025
1045
|
|
|
1026
1046
|
**What the model sees.** A `budget_overflow` telemetry event (§telemetry), 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 budget readout (§tokenomics) — its turn and entry weights — 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. {§grinder-event-model-terms} Per the gamification policy (§telemetry), the *strike* the overflow triggers stays engine-internal; the model sees the hidden entries, never the accounting.
|
|
1027
1047
|
|
|
1028
|
-
**Rationale.** The model owns curation (§tokenomics); the grinder is the exceptional backstop. It only *
|
|
1048
|
+
**Rationale.** The model owns curation (§tokenomics); the grinder is the exceptional backstop. It only *folds* — reversibly — the prior turn's render; nothing is deleted, so the model can OPEN it back and log history stays intact. Rummy's §1316 spec described clearing log *bodies*, but its code instead folded the prior turn whole — because body-clearing is destructive (it deletes the read result) and bespoke. The code was the lesson; plurnk follows it.
|
|
1029
1049
|
|
|
1030
1050
|
**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.
|
|
1031
1051
|
|
|
@@ -1099,9 +1119,9 @@ type Packet = {
|
|
|
1099
1119
|
};
|
|
1100
1120
|
```
|
|
1101
1121
|
|
|
1102
|
-
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-origin `EDIT` against `plurnk
|
|
1122
|
+
**Prompt as a first-class entry.** Each loop's prompt is written on loop start as a plurnk-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/FOLD-able like any other.
|
|
1103
1123
|
|
|
1104
|
-
**The entry catalog.** `plurnk
|
|
1124
|
+
**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. {§packet-manifest-catalog}
|
|
1105
1125
|
|
|
1106
1126
|
### §telemetry user.telemetry — model-facing runtime telemetry
|
|
1107
1127
|
|
|
@@ -1129,7 +1149,7 @@ Slot for telemetry the model MUST react to immediately. Rendered at the bottom o
|
|
|
1129
1149
|
| `max_commands_exceeded` | Single emission exceeded `PLURNK_MAX_COMMANDS` cap; overflow ops dropped without dispatch | `source: "engine:rail"`, `kind`, `emitted`, `dropped` |
|
|
1130
1150
|
| `budget_overflow` | Assembled packet exceeded the budget ceiling; entries moved out of the window to fit | `source: "engine:rail"`, `kind`, `hidden` (per-scheme `[{scheme, count}]` — entries removed from the window) |
|
|
1131
1151
|
|
|
1132
|
-
Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeeping are all engine-internal — they drive abandonment silently per the gamification policy above. Action-bound failures (handler returned 4xx/5xx or threw) mirror as `action_failure` kind on the next packet. Full detail queryable via `log
|
|
1152
|
+
Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeeping are all engine-internal — they drive abandonment silently per the gamification policy above. Action-bound failures (handler returned 4xx/5xx or threw) mirror as `action_failure` kind on the next packet. Full detail queryable via `log:///`. {§telemetry-no-error-scheme}
|
|
1133
1153
|
|
|
1134
1154
|
**No `error://` scheme.** Actionless failures route to telemetry, not a queryable scheme namespace.
|
|
1135
1155
|
|
|
@@ -1224,7 +1244,7 @@ Body: one match per line as `<line>:\t<value>` — the same `N:\t` form READ emi
|
|
|
1224
1244
|
- `<0>` / `<-1>` → `[]` for READ
|
|
1225
1245
|
- Out-of-range → 416; malformed JSON → 400
|
|
1226
1246
|
|
|
1227
|
-
**Killer composition.** `<<READ(log
|
|
1247
|
+
**Killer composition.** `<<READ(log:///N/M/K)<P>::READ` picks the P-th match from a prior matcher result — matcher rx is `application/json`, structural `<L>` selects the P-th element. {§slice-semantics-compose-pattern}
|
|
1228
1248
|
|
|
1229
1249
|
### §json-edit Structural EDIT on JSON
|
|
1230
1250
|
|
|
@@ -1259,10 +1279,10 @@ When effective mimetype is `application/json`, EDIT dispatches through `applyJso
|
|
|
1259
1279
|
|
|
1260
1280
|
`resolveEntryMimetype` (exported from `@plurnk/plurnk-schemes`): pathname extension → `Mimetypes.detect({ ext })` (with `text/plain` normalized to `text/markdown` per the text-primitive rule §markdown-primitive); falls back to scheme manifest channel default when no extension.
|
|
1261
1281
|
|
|
1262
|
-
- `known
|
|
1263
|
-
- `known
|
|
1264
|
-
- `known
|
|
1265
|
-
- `known
|
|
1282
|
+
- `known:///users.json` → `application/json` (extension wins)
|
|
1283
|
+
- `known:///notes.md` → `text/markdown` (extension; matches default)
|
|
1284
|
+
- `known:///config.yaml` → `application/yaml`
|
|
1285
|
+
- `known:///users` (no suffix) → `text/markdown` (Known manifest default)
|
|
1266
1286
|
|
|
1267
1287
|
Same rule applies across Known, Unknown, Skill, Plurnk, File. Effective mimetype is stored in `entry_channels.mimetype` on write and drives `<L>` and matcher dispatch on read. {§ext-mimetype-extension-mimetype}
|
|
1268
1288
|
|
|
@@ -1295,7 +1315,7 @@ Carried from the contract walk; durable.
|
|
|
1295
1315
|
- **COPY `<L>`** → source range, symmetric with READ `<L>`.
|
|
1296
1316
|
- **READ rx** prefixes each line with `N:\t` per §render-rule. `sliceLinesRaw` (used by COPY) returns the lines without prefix.
|
|
1297
1317
|
- **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).
|
|
1298
|
-
- **OPEN/FOLD** operate on the **log** (`log
|
|
1318
|
+
- **OPEN/FOLD** operate on the **log** (`log:///`), not entries (§open-fold) — FOLD collapses a log row to its path, OPEN restores its body. Aimed at an entry scheme they return 501.
|
|
1299
1319
|
- **SEND[410]** deletes as a side-effect (not the model idiom; §move): with `#fragment`, that channel only; without, the whole entry. **SEND[499]** is owned by the streaming scheme that holds the subscription.
|
|
1300
1320
|
- **File scheme** reads disk content with mimetype detected via `Mimetypes.detect({ path })` (plumbed through `PlurnkSchemeContext.mimetypes`). Binary mimetypes → 415 on READ and EDIT.
|
|
1301
1321
|
|
package/dist/Paths.d.ts
CHANGED
package/dist/Paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Paths.d.ts","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,OAAO,KAAK;;IAItB,MAAM,CAAC,UAAU,SAA8C;IAC/D,MAAM,CAAC,kBAAkB,SAA6C;
|
|
1
|
+
{"version":3,"file":"Paths.d.ts","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,OAAO,OAAO,KAAK;;IAItB,MAAM,CAAC,UAAU,SAA8C;IAC/D,MAAM,CAAC,kBAAkB,SAA6C;IAOtE,MAAM,CAAC,cAAc,SAAkC;IAIvD,MAAM,CAAC,mBAAmB,SAAuC;IA8BjE,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAW5D"}
|
package/dist/Paths.js
CHANGED
|
@@ -15,11 +15,8 @@ export default class Paths {
|
|
|
15
15
|
static #GRAMMAR_ROOT = dirname(fileURLToPath(import.meta.resolve("@plurnk/plurnk-grammar/package.json")));
|
|
16
16
|
static migrations = resolve(Paths.#PACKAGE_ROOT, "migrations");
|
|
17
17
|
static instructionsSystem = resolve(Paths.#GRAMMAR_ROOT, "plurnk.md");
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// Plumbed to the provider per generate() when PLURNK_PROVIDERS_GBNF is
|
|
21
|
-
// enabled; the service holds no opinion about its content or root.
|
|
22
|
-
static grammarGbnf = resolve(Paths.#GRAMMAR_ROOT, "dist/plurnk.gbnf");
|
|
18
|
+
// (GBNF artifact resolution moved to Engine.#grammarConstraint — the env value
|
|
19
|
+
// SELECTS the variant from @plurnk/plurnk-grammar; no hardcoded default here, #225.)
|
|
23
20
|
// packet.system.persona DEFAULT. Cascade at packet-build time is
|
|
24
21
|
// loops.persona > runs.persona > sessions.persona > this file
|
|
25
22
|
// RPC overrides on loop.run / session.attach / session.create populate
|
|
@@ -51,7 +48,7 @@ export default class Paths {
|
|
|
51
48
|
}
|
|
52
49
|
// Operator reference docs auto-READ into every model run at turn 0.
|
|
53
50
|
// `PLURNK_MD_<ALIAS>=<path>` materializes <path>'s markdown as a
|
|
54
|
-
// `plurnk
|
|
51
|
+
// `plurnk:///<ALIAS>.md` entry the model READs — an idiomatic, userland way
|
|
55
52
|
// to inject standing context (an ordinary entry + READ op, not a bespoke
|
|
56
53
|
// packet section like persona). `~` expands to home; relative paths resolve
|
|
57
54
|
// against the package root. Resolved fresh each call so it tracks the env.
|
package/dist/Paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Paths.js","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,gDAAgD;AAChD,+EAA+E;AAC/E,6EAA6E;AAC7E,oCAAoC;AACpC,EAAE;AACF,8EAA8E;AAC9E,mDAAmD;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,MAAM,CAAC,OAAO,OAAO,KAAK;IACtB,MAAM,CAAC,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC9E,MAAM,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,CAAC,CAAC;IAE1G,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC/D,MAAM,CAAC,kBAAkB,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IACtE
|
|
1
|
+
{"version":3,"file":"Paths.js","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// A pathname's extension drives the effective mimetype (via the sibling
|
|
6
6
|
// Mimetypes detect service); the scheme manifest's channel default is the
|
|
7
|
-
// fallback when no extension is present. `known
|
|
8
|
-
// application/json; `known
|
|
7
|
+
// fallback when no extension is present. `known:///users.json` →
|
|
8
|
+
// application/json; `known:///notes` → scheme default.
|
|
9
9
|
import { PathMimetype as _PathMimetype } from "@plurnk/plurnk-schemes";
|
|
10
10
|
export default class PathMimetype {
|
|
11
11
|
static resolveEntryMimetype(pathname, schemeDefault, mimetypes) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-mimetype.js","sourceRoot":"","sources":["../../src/content/path-mimetype.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,iFAAiF;AACjF,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,
|
|
1
|
+
{"version":3,"file":"path-mimetype.js","sourceRoot":"","sources":["../../src/content/path-mimetype.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,iFAAiF;AACjF,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,iEAAiE;AACjE,uDAAuD;AACvD,OAAO,EAAE,YAAY,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvE,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,MAAM,CAAC,oBAAoB,CAAC,QAAgB,EAAE,aAAqB,EAAE,SAAgC;QACjG,OAAO,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;CACJ"}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { Db } from "./Db.ts";
|
|
2
2
|
export type ChannelState = "static" | "active" | "closed" | "errored";
|
|
3
|
+
export interface StreamCoordinate {
|
|
4
|
+
loop_seq: number;
|
|
5
|
+
turn_seq: number;
|
|
6
|
+
sequence: number;
|
|
7
|
+
}
|
|
3
8
|
export interface StreamEventPayload {
|
|
4
9
|
entryId: number;
|
|
5
10
|
target: string;
|
|
6
11
|
channel: string;
|
|
7
12
|
state: ChannelState;
|
|
8
13
|
contentLength: number;
|
|
14
|
+
loop_seq?: number;
|
|
15
|
+
turn_seq?: number;
|
|
16
|
+
sequence?: number;
|
|
17
|
+
mimetype?: string;
|
|
9
18
|
}
|
|
10
19
|
export type StreamEventNotify = (sessionId: number, event: StreamEventPayload) => void;
|
|
11
20
|
export interface WakeRunPayload {
|
|
@@ -17,6 +26,9 @@ export interface WakeRunPayload {
|
|
|
17
26
|
closeStatus: number;
|
|
18
27
|
scheme: string;
|
|
19
28
|
summary: string;
|
|
29
|
+
loop_seq?: number;
|
|
30
|
+
turn_seq?: number;
|
|
31
|
+
sequence?: number;
|
|
20
32
|
}
|
|
21
33
|
export type WakeRunNotify = (payload: WakeRunPayload) => void;
|
|
22
34
|
export interface TelemetryEventPayload {
|
|
@@ -26,17 +38,20 @@ export interface TelemetryEventPayload {
|
|
|
26
38
|
export type TelemetryEventNotify = (sessionId: number, payload: TelemetryEventPayload) => void;
|
|
27
39
|
export default class ChannelWrite {
|
|
28
40
|
#private;
|
|
29
|
-
static appendToChannel(db: Db, { entryId, channel, chunk, notify }: {
|
|
41
|
+
static appendToChannel(db: Db, { entryId, channel, chunk, notify, coordinate, mimetype }: {
|
|
30
42
|
entryId: number;
|
|
31
43
|
channel: string;
|
|
32
44
|
chunk: string;
|
|
33
45
|
notify?: StreamEventNotify;
|
|
46
|
+
coordinate?: StreamCoordinate;
|
|
47
|
+
mimetype?: string;
|
|
34
48
|
}): Promise<void>;
|
|
35
|
-
static setChannelState(db: Db, { entryId, channel, state, notify }: {
|
|
49
|
+
static setChannelState(db: Db, { entryId, channel, state, notify, coordinate }: {
|
|
36
50
|
entryId: number;
|
|
37
51
|
channel: string;
|
|
38
52
|
state: ChannelState;
|
|
39
53
|
notify?: StreamEventNotify;
|
|
54
|
+
coordinate?: StreamCoordinate;
|
|
40
55
|
}): Promise<void>;
|
|
41
56
|
static openSubscription(db: Db, { runId, entryId, scheme, handle }: {
|
|
42
57
|
runId: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAMtE,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAQvF,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAQ9D,MAAM,WAAW,qBAAqB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAW/F,MAAM,CAAC,OAAO,OAAO,YAAY;;WAiBhB,eAAe,CACxB,EAAE,EAAE,EAAE,EACN,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;QAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7L,OAAO,CAAC,IAAI,CAAC;WAaH,eAAe,CACxB,EAAE,EAAE,EAAE,EACN,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAC;QAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;QAAC,UAAU,CAAC,EAAE,gBAAgB,CAAA;KAAE,GACtK,OAAO,CAAC,IAAI,CAAC;WASH,gBAAgB,CACzB,EAAE,EAAE,EAAE,EACN,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACvG,OAAO,CAAC,MAAM,CAAC;WAML,iBAAiB,CAC1B,EAAE,EAAE,EAAE,EACN,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACvE,OAAO,CAAC,IAAI,CAAC;WAOH,kBAAkB,CAC3B,EAAE,EAAE,EAAE,EACN,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACjE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;WAKZ,sBAAsB,CAC/B,EAAE,EAAE,EAAE,EACN,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAIpE"}
|
|
@@ -9,28 +9,34 @@ export default class ChannelWrite {
|
|
|
9
9
|
static #channelMeta(db) { return db.channel_meta; }
|
|
10
10
|
static #appendStmt(db) { return db.append_to_channel; }
|
|
11
11
|
static #stateStmt(db) { return db.set_channel_state; }
|
|
12
|
+
static #mimetypeStmt(db) { return db.set_channel_mimetype; }
|
|
12
13
|
static #openSubStmt(db) { return db.open_subscription; }
|
|
13
14
|
static #closeSubStmt(db) { return db.close_subscription; }
|
|
14
15
|
static #findActiveStmt(db) { return db.find_active_subscription; }
|
|
15
16
|
static #execTerminalStmt(db) { return db.find_exec_close_status; }
|
|
16
17
|
// The entry's target URI for stream notifications (#179). A NULL scheme is
|
|
17
18
|
// a filesystem entry (the file scheme stores scheme=NULL), so it decodes to
|
|
18
|
-
// file
|
|
19
|
+
// file:///.
|
|
19
20
|
static #targetUri(scheme, pathname) {
|
|
20
21
|
return `${scheme === null ? "file" : scheme}://${pathname}`;
|
|
21
22
|
}
|
|
22
|
-
static async appendToChannel(db, { entryId, channel, chunk, notify }) {
|
|
23
|
+
static async appendToChannel(db, { entryId, channel, chunk, notify, coordinate, mimetype }) {
|
|
23
24
|
const result = await ChannelWrite.#appendStmt(db).run({ chunk, entry_id: entryId, channel });
|
|
24
25
|
if (result.changes === 0)
|
|
25
26
|
return;
|
|
27
|
+
// #226 — a streaming scheme labels the channel with the body's per-call
|
|
28
|
+
// type (http's Content-Type varies per fetch). Conditional UPDATE: only
|
|
29
|
+
// writes when it changed, so labelling every chunk is a steady-state no-op.
|
|
30
|
+
if (mimetype !== undefined)
|
|
31
|
+
await ChannelWrite.#mimetypeStmt(db).run({ mimetype, entry_id: entryId, channel });
|
|
26
32
|
if (notify === undefined)
|
|
27
33
|
return;
|
|
28
34
|
const meta = await ChannelWrite.#channelMeta(db).get({ entry_id: entryId, channel });
|
|
29
35
|
if (meta === undefined)
|
|
30
36
|
return;
|
|
31
|
-
notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength });
|
|
37
|
+
notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength, mimetype: meta.mimetype, ...coordinate });
|
|
32
38
|
}
|
|
33
|
-
static async setChannelState(db, { entryId, channel, state, notify }) {
|
|
39
|
+
static async setChannelState(db, { entryId, channel, state, notify, coordinate }) {
|
|
34
40
|
const result = await ChannelWrite.#stateStmt(db).run({ state, entry_id: entryId, channel });
|
|
35
41
|
if (result.changes === 0)
|
|
36
42
|
return;
|
|
@@ -39,7 +45,7 @@ export default class ChannelWrite {
|
|
|
39
45
|
const meta = await ChannelWrite.#channelMeta(db).get({ entry_id: entryId, channel });
|
|
40
46
|
if (meta === undefined)
|
|
41
47
|
return;
|
|
42
|
-
notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength });
|
|
48
|
+
notify(meta.session_id, { entryId, target: ChannelWrite.#targetUri(meta.scheme, meta.pathname), channel, state: meta.state, contentLength: meta.contentLength, mimetype: meta.mimetype, ...coordinate });
|
|
43
49
|
}
|
|
44
50
|
static async openSubscription(db, { runId, entryId, scheme, handle }) {
|
|
45
51
|
const row = await ChannelWrite.#openSubStmt(db).get({ run_id: runId, entry_id: entryId, scheme, handle });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,sFAAsF;AACtF,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,2EAA2E;AAC3E,gCAAgC;
|
|
1
|
+
{"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,sFAAsF;AACtF,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,2EAA2E;AAC3E,gCAAgC;AA0EhC,MAAM,CAAC,OAAO,OAAO,YAAY;IAC7B,MAAM,CAAC,YAAY,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,YAA0B,CAAC,CAAC,CAAC;IACjF,MAAM,CAAC,WAAW,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,iBAA+B,CAAC,CAAC,CAAC;IACrF,MAAM,CAAC,UAAU,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,iBAA+B,CAAC,CAAC,CAAC;IACpF,MAAM,CAAC,aAAa,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,oBAAkC,CAAC,CAAC,CAAC;IAC1F,MAAM,CAAC,YAAY,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,iBAA+B,CAAC,CAAC,CAAC;IACtF,MAAM,CAAC,aAAa,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,kBAAgC,CAAC,CAAC,CAAC;IACxF,MAAM,CAAC,eAAe,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,wBAAsC,CAAC,CAAC,CAAC;IAChG,MAAM,CAAC,iBAAiB,CAAC,EAAM,IAAgB,OAAO,EAAE,CAAC,sBAAoC,CAAC,CAAC,CAAC;IAEhG,2EAA2E;IAC3E,4EAA4E;IAC5E,YAAY;IACZ,MAAM,CAAC,UAAU,CAAC,MAAqB,EAAE,QAAgB;QACrD,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,QAAQ,EAAE,CAAC;IAChE,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,eAAe,CACxB,EAAM,EACN,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAqI;QAE5L,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7F,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,OAAO;QACjC,wEAAwE;QACxE,wEAAwE;QACxE,4EAA4E;QAC5E,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,YAAY,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/G,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QACjC,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACrG,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC/B,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;IAC7M,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,eAAe,CACxB,EAAM,EACN,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAwH;QAErK,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAC5F,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,OAAO;QACjC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QACjC,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACrG,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC/B,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;IAC7M,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,gBAAgB,CACzB,EAAM,EACN,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAsE;QAEtG,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiB,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1H,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QACjG,OAAO,GAAG,CAAC,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAC1B,EAAM,EACN,EAAE,cAAc,EAAE,MAAM,EAA8C;QAEtE,MAAM,YAAY,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,4EAA4E;IAC5E,sEAAsE;IACtE,mDAAmD;IACnD,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAC3B,EAAM,EACN,EAAE,SAAS,EAAE,QAAQ,EAA2C;QAEhE,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,GAAG,CAA2B,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxH,OAAO,GAAG,EAAE,YAAY,IAAI,IAAI,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAC/B,EAAM,EACN,EAAE,KAAK,EAAE,OAAO,EAAsC;QAEtD,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,GAAG,CAAiD,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7I,OAAO,GAAG,IAAI,IAAI,CAAC;IACvB,CAAC;CACJ"}
|
package/dist/core/Engine.d.ts
CHANGED
|
@@ -2,10 +2,9 @@ import type { PlurnkStatement } from "@plurnk/plurnk-grammar";
|
|
|
2
2
|
import type SchemeRegistry from "./SchemeRegistry.ts";
|
|
3
3
|
import { Mimetypes } from "@plurnk/plurnk-mimetypes";
|
|
4
4
|
import type { Db } from "./Db.ts";
|
|
5
|
-
import type { LoopFlags } from "./scheme-types.ts";
|
|
5
|
+
import type { WriterTier, LoopFlags } from "./scheme-types.ts";
|
|
6
6
|
import type ExecutorRegistry from "./ExecutorRegistry.ts";
|
|
7
7
|
import type { StreamEventNotify, TelemetryEventNotify, WakeRunNotify } from "./ChannelWrite.ts";
|
|
8
|
-
type Origin = "model" | "client" | "plurnk" | "plugin";
|
|
9
8
|
type ChatMessage = {
|
|
10
9
|
role: "system" | "user" | "assistant";
|
|
11
10
|
content: string;
|
|
@@ -18,7 +17,7 @@ type DispatchContext = {
|
|
|
18
17
|
loopId: number;
|
|
19
18
|
turnId: number;
|
|
20
19
|
sequence: number;
|
|
21
|
-
origin:
|
|
20
|
+
origin: WriterTier;
|
|
22
21
|
onDispatch?: (logEntryId: number) => void;
|
|
23
22
|
};
|
|
24
23
|
type DispatchResult = {
|
|
@@ -85,7 +84,7 @@ export default class Engine {
|
|
|
85
84
|
maxStrikes?: number;
|
|
86
85
|
minCycles?: number;
|
|
87
86
|
maxCyclePeriod?: number;
|
|
88
|
-
origin?:
|
|
87
|
+
origin?: WriterTier;
|
|
89
88
|
signal?: AbortSignal;
|
|
90
89
|
onDispatch?: (logEntryId: number) => void;
|
|
91
90
|
}): Promise<{
|
|
@@ -102,7 +101,7 @@ export default class Engine {
|
|
|
102
101
|
sessionId: number;
|
|
103
102
|
runId: number;
|
|
104
103
|
loopId: number;
|
|
105
|
-
origin?:
|
|
104
|
+
origin?: WriterTier;
|
|
106
105
|
signal?: AbortSignal;
|
|
107
106
|
onDispatch?: (logEntryId: number) => void;
|
|
108
107
|
turnNumber?: number;
|