@plurnk/plurnk-service 0.15.0 → 0.17.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 +29 -11
- package/dist/Paths.d.ts +1 -0
- package/dist/Paths.d.ts.map +1 -1
- package/dist/Paths.js +5 -0
- package/dist/Paths.js.map +1 -1
- package/dist/content/edited-span.d.ts +2 -0
- package/dist/content/edited-span.d.ts.map +1 -0
- package/dist/content/edited-span.js +33 -0
- package/dist/content/edited-span.js.map +1 -0
- package/dist/content/index.d.ts +1 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +1 -0
- package/dist/content/index.js.map +1 -1
- package/dist/core/Engine.d.ts +5 -0
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +153 -11
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +9 -1
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +21 -28
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +2 -0
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +52 -0
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +69 -18
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts +15 -0
- package/dist/core/git-state.d.ts.map +1 -0
- package/dist/core/git-state.js +65 -0
- package/dist/core/git-state.js.map +1 -0
- package/dist/core/packet-wire.d.ts +1 -0
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +27 -7
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/scheme-types.d.ts +9 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/schemes/Exec.d.ts +4 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +17 -1
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts +2 -11
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +38 -7
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +2 -10
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +5 -1
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +1 -0
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +8 -2
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/server/ClientConnection.d.ts.map +1 -1
- package/dist/server/ClientConnection.js +5 -2
- package/dist/server/ClientConnection.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +10 -0
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/MethodRegistry.d.ts +5 -0
- package/dist/server/MethodRegistry.d.ts.map +1 -1
- package/dist/server/MethodRegistry.js.map +1 -1
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +1 -0
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/session_attach.d.ts.map +1 -1
- package/dist/server/methods/session_attach.js +0 -3
- package/dist/server/methods/session_attach.js.map +1 -1
- package/dist/server/methods/session_constraints.d.ts +6 -0
- package/dist/server/methods/session_constraints.d.ts.map +1 -0
- package/dist/server/methods/session_constraints.js +63 -0
- package/dist/server/methods/session_constraints.js.map +1 -0
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +1 -3
- package/dist/server/methods/session_create.js.map +1 -1
- package/migrations/0000-00-00.01_schema.sql +35 -1
- package/package.json +11 -15
package/SPEC.md
CHANGED
|
@@ -338,7 +338,7 @@ Every entry is uniformly listed in `plurnk://manifest.json` (§15) and READable
|
|
|
338
338
|
|
|
339
339
|
Mimetype is declared by scheme manifest (§3.1) or supplied per-call for dynamic schemes. Writing a channel without a declared mimetype throws. No default mimetype anywhere.
|
|
340
340
|
|
|
341
|
-
- Cross-mimetype COPY/MOVE → 415, never coerces (§6.4).
|
|
341
|
+
- Cross-mimetype COPY/MOVE → 415, never coerces (§6.4). {§5.3-cross-mimetype-415}
|
|
342
342
|
|
|
343
343
|
### §5.5 Channel selection in the DSL
|
|
344
344
|
|
|
@@ -730,12 +730,17 @@ registry.register("loop.run", {
|
|
|
730
730
|
|
|
731
731
|
| Method | Params | Result | Notes |
|
|
732
732
|
|------------------------|---------------------|-------------------|-------|
|
|
733
|
-
| `session.create` | `name?: string`, `projectRoot?: string`, `persona?: string` | `{ id, name, projectRoot, persona }` | Creates new session; auto-name if unprovided. Optional `projectRoot` pins the workspace (null/omitted = headless). Optional `persona` sets the session-level persona override. |
|
|
733
|
+
| `session.create` | `name?: string`, `projectRoot?: string`, `persona?: string` | `{ id, name, runId, runName, projectRoot, persona }` | Creates new session + its first run; auto-name if unprovided. Returns the auto-created run's identity so clients skip the pending-dance ({§13.5-session-create}). Optional `projectRoot` pins the workspace (null/omitted = headless). Optional `persona` sets the session-level persona override. |
|
|
734
734
|
| `session.list` | none | `{ sessions: Session[] }` | Lists all sessions. |
|
|
735
735
|
| `session.attach` | `id: number`, `runId?: number`, `runName?: string`, `persona?: string` | `{ id, name, runId, runName }` | Binds this connection to an existing session. Optional `runId` resumes that specific run (must belong to the session). Optional `runName` reuses-or-creates by name within the session. Both omitted → new auto-named run. Optional `persona` sets run-level persona only when a NEW run is created. {§13.5-session-attach} |
|
|
736
736
|
| `session.runs` | `id?: number` | `{ runs: Run[] }` | Lists runs in a session (defaults to attached session); most-recent first. |
|
|
737
737
|
| `session.set_root` | `projectRoot: string \| null` | `{ projectRoot }` | Update the workspace pointer on the attached session. Null reverts to headless. |
|
|
738
738
|
| `session.set_persona` | `persona: string \| null` | `{ persona }` | Update the session-level persona. Null clears the override (falls through to PLURNK_PERSONA file). |
|
|
739
|
+
| `session.constrain` | `effect: "add" \| "ignore" \| "read-only"`, `glob: string` | `{ effect, glob }` | Add a workspace membership constraint (§14.3 overlay): `add` admits files git misses, `ignore` drops tracked matches, `read-only` admits for read but refuses edits. Immediate. |
|
|
740
|
+
| `session.unconstrain` | `effect: "add" \| "ignore" \| "read-only"`, `glob: string` | `{ effect, glob }` | Remove a membership constraint — the inverse of `session.constrain`. Immediate. |
|
|
741
|
+
| `session.constraints` | none | `{ constraints }` | List the attached session's membership constraints. |
|
|
742
|
+
|
|
743
|
+
**Re-binding.** `session.create` and `session.attach` may be called on a connection that already has a session attached — the connection switches in place, releasing the prior client loop (closed at 200). No reconnect needed to change session or run. {§13.5-rebind}
|
|
739
744
|
|
|
740
745
|
**Auto-envelope.** Clients calling a `requiresInit: true` method without first attaching get auto-created session → run → client loop. Records persist normally; auto-created ≠ auto-deleted. Cleanup is a future `session.delete` / `session.archive` endpoint. {§13.5-auto-envelope}
|
|
741
746
|
|
|
@@ -745,6 +750,7 @@ registry.register("loop.run", {
|
|
|
745
750
|
|-------------------|-------------------------------------|------------------------|-------|
|
|
746
751
|
| `loop.run` | `prompt: string`, `maxTurns?: number`, `alias?: string`, `flags?: LoopFlags`, `persona?: string` | `{ loopId, turnIds, finalStatus, hitMaxTurns, reason }` | Model-driven loop. Optional `alias` overrides the boot-time `PLURNK_MODEL`. Optional `flags` carries per-loop flags (currently `{yolo?: boolean}`; more arrive as wired — see §0.5). Optional `persona` sets the loop-level persona (highest precedence in the cascade). Streams `log/entry` and `loop/proposal` notifications during. `longRunning: true`. {§13.5-loop-run} |
|
|
747
752
|
| `loop.resolve` | `logEntryId: number`, `decision: "accept" \| "reject" \| "cancel"`, `body?: string`, `outcome?: string` | `{ status, logEntryId }` | Resolve a pending proposal (status=202 log entry). Engine.dispatch unpauses on resolution. |
|
|
753
|
+
| `loop.cancel` | `reason?: string` | `{ cancelled, runId, reason }` | Abort the attached run's active drain. `{cancelled: true}` if a drain was running, `{false}` if idle. Cancelled loops close at 499; queued-but-unclaimed loops stay enqueued. Default reason `user_cancelled`. {§13.5-loop-cancel} |
|
|
748
754
|
| `providers.list` | none | `{ aliases: ProviderAlias[] }` | Lists configured `PLURNK_MODEL_<alias>` entries with `{alias, provider, model, active}`. Clients use to populate model-selection UI. |
|
|
749
755
|
|
|
750
756
|
**Reads**
|
|
@@ -791,6 +797,8 @@ Server-initiated events on the same WebSocket.
|
|
|
791
797
|
| `loop/proposal` | `{ logEntryId, sessionId, runId, loopId, turnId, op, target, body, attrs, flags }` | Dispatch pauses on status=202. Carries `flags` so server-YOLO clients can suppress review UI. Client responds with `loop.resolve` (or `PLURNK_PROPOSAL_TIMEOUT_MS` fires). |
|
|
792
798
|
| `session/created` | `{ id, name, projectRoot, persona }` | Any client creates a session. |
|
|
793
799
|
| `stream/event` | `{ entryId, channel, state, contentLength }` | Channel content grows or state transitions. {§13.6-stream-event-on-channel-change} |
|
|
800
|
+
| `stream/concluded` | `{ entryId, target, subscriptionId, scheme, closeStatus, summary, wakeAction, wakeLoopId? }` | A streaming subscription closed (subprocess finished / errored / cancelled). `wakeAction` says whether the daemon opened a fresh loop to surface the conclusion to the model. {§13.6-stream-concluded} |
|
|
801
|
+
| `telemetry/event` | `{ loopId, event: TelemetryEvent }` | A TelemetryEvent (parse error, engine-rail strike/cycle/sudden-death, scheme/provider failure) was buffered — the same envelope the model sees on the next packet, delivered live for client surfacing. {§13.6-telemetry-event} |
|
|
794
802
|
|
|
795
803
|
`stream/event` carries metadata only, never content. Clients fetch via `entry.read({target})`. **Every notification envelope carries its `sessionId`** (and `runId` where the emitter has it) so a multi-session client — one connection, many sessions — can route it ({§13.6-envelope-carries-sessionid}); the broadcast stays session-scoped too.
|
|
796
804
|
|
|
@@ -911,15 +919,15 @@ Each entry: question, answer, rationale, migration path.
|
|
|
911
919
|
|
|
912
920
|
- **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.
|
|
913
921
|
- **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).
|
|
914
|
-
- **EMI eager +
|
|
915
|
-
- **
|
|
916
|
-
|
|
917
|
-
**Deferred — promised, not yet built.** Each carries an anchor with a deliberately-red test so the deferral is tracked, never silently covered.
|
|
922
|
+
- **EMI eager + exhaustive.** Materializes *every* active git member at prompt-composition, re-reading disk so a divergent member reflects current content. git's `ls-files` is the whole membership bound — the engine adds no relevance pass of its own. Allowing git *is* the curation.
|
|
923
|
+
- **Membership-gated edits.** {§14.3-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.
|
|
918
924
|
|
|
919
|
-
- **Constraint overlay —
|
|
920
|
-
- **
|
|
925
|
+
- **Constraint overlay — `ignore`.** {§14.3-constraint-ignore} A `session_constraints` table (effect ∈ {add, ignore, read-only}, glob) is the client's supersede over git. `ignore` drops tracked matches: resolution computes `git ls-files − ignore` (`node:path.matchesGlob`) and **reconciles** — registers the desired, un-registers any git-origin member no longer desired (untracked or newly ignored) — 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.
|
|
926
|
+
- **Constraint overlay — `read-only`.** {§14.3-constraint-readonly} A `read-only` glob keeps a matching member readable but refuses `File.edit` — 403'd at the membership check, before any diff. A file admitted for reading without granting writes. (Admitting an *untracked* file as read-only rides on `add`'s scan.)
|
|
927
|
+
- **Constraint overlay — `add`.** {§14.3-constraint-add} `add` globs admit members git misses: a targeted, client-dictated `node:fs` glob scan enumerates untracked matches (files only), registered with 'constraint' origin and reconciled like git members. Enumerated, so the manifest stays exhaustive. git-absent, `add` is the *sole* membership source — the whole mechanism without git.
|
|
928
|
+
- **EMI divergence signal.** {§14.3-emi-divergence-signal} When git membership re-reads a member whose disk content changed out-of-band, the build-time delta detector (§14.5) 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 accepted edits advance the run's watermark, so they're never mis-attributed as external divergence.
|
|
921
929
|
|
|
922
|
-
**Rationale.** No users/tenants. Session is the right scope unit. git+constraints membership keeps the model out of fs-walk territory. EMI
|
|
930
|
+
**Rationale.** No users/tenants. Session is the right scope unit. git+constraints membership keeps the model out of fs-walk territory — and *is* the curation, outsourced: git bounds membership by tracking, the client supersedes by constraint, the model curates its view by READ/FOLD. The engine curates nothing. EMI re-reads every member each turn; the full-repo cost is git's to bound (what it tracks) and the client's to bound (`ignore`), never the engine's to bound by relevance.
|
|
923
931
|
|
|
924
932
|
**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.
|
|
925
933
|
|
|
@@ -944,7 +952,7 @@ Each entry: question, answer, rationale, migration path.
|
|
|
944
952
|
|
|
945
953
|
**Question.** The manifest (§15) is a live directory of what *exists*, re-derived each turn — but it carries *state*, not *events*. When a session entry changes out-of-band between turns — an exec stream grows, a sibling run edits a shared entry, a tracked file diverges on disk (§14.3) — the model's prior READ is now stale, and the manifest's new line count is a fact it must *diff against its own memory* to notice. The manifest also cannot say *who* changed it; with more than one actor in a session, provenance is load-bearing. What surfaces change — losslessly, attributably, without curating?
|
|
946
954
|
|
|
947
|
-
**Decision — a per-run delta
|
|
955
|
+
**Decision — a per-run delta of system-authored EDITs.** When a session entry changes out-of-band, the engine records it the way the model records its *own* edits: a `log_entries` row, op=`EDIT`, `origin=system`, carrying the new `source` attribution (the cause). The body is exactly an EDIT's body — **the edited area as it looks now, line-numbered, with a couple lines of context** (the result-rendering all EDITs share, §14.6). The set is **exhaustive and unranked** — every change, no relevance order — but **not content-free**: showing the edited region of a change that *happened* is a faithful record, not the index regrowing. The engine may inform (the §14.3 EMI divergence is the precedent), never rank or select what the model retains; the one line it must never cross is *ranking*. Volume is the model's to manage by FOLD (and the grinder's under budget), not the engine's to manage by gutting the payload.
|
|
948
956
|
|
|
949
957
|
**Form — a log entry, `origin=system`, the change translated to DSL.** A delta is a `log_entries` row: an **`EDIT`** ("an EDIT happened to X"), `origin=system`, carrying the new **`source`** column — the cause. A log entry, not a transient frame section, because a run's timeline must be **self-contained** (a forked run carries everything it observed). `source` renders as `run="<id>"` / `run="file"` in the meta line, **omitted when the cause is the owning run itself**. It is a third attribution axis, distinct from `run_id` (whose log owns the row) and `origin` (the actor *type*).
|
|
950
958
|
|
|
@@ -954,10 +962,20 @@ Each entry: question, answer, rationale, migration path.
|
|
|
954
962
|
|
|
955
963
|
**Detection — announced vs ambient.** A run *knows* when it writes, so run-caused changes are **broadcast** (the existing `stream/event`, §13.6 — already session-scoped and carrying its `runId`) and **queued** per sibling run, drained into deltas at that run's next turn. Nothing announces an external disk edit, so ambient fs changes are **scooped at pre-turn** by the §14.3 EMI scan. Attribution falls out of detection: the broadcast carries the run; the scan attributes to the scheme.
|
|
956
964
|
|
|
957
|
-
**Rationale.** "The model knows its world moved" becomes a property of *reading reality at build*, not of *remembering to emit* — 100% coverage by construction
|
|
965
|
+
**Rationale.** "The model knows its world moved" becomes a property of *reading reality at build*, not of *remembering to emit* — 100% coverage by construction. The engine records each change faithfully — as the EDIT it was, showing its result — and hands the model the wheel; it never ranks, selects, or folds on the model's behalf. The model FOLDs what it doesn't need; the grinder folds under budget.
|
|
958
966
|
|
|
959
967
|
**Migration path.** Additive. The `source` column defaults null — existing rows and the current render are unaffected; materialization and the broadcast/EMI detection layer on without touching the op surface.
|
|
960
968
|
|
|
969
|
+
### §14.6 EDIT log rows render their result, not their input
|
|
970
|
+
|
|
971
|
+
**Question.** An EDIT's log row exists so the model has a record of what it did. Re-emitting the model's *input* statement (the tx heredoc) records the *intent* but not the *outcome* — the model still has to READ the entry back to confirm "did it land, what does it look like now." And a system delta-EDIT (§14.5) has no input statement at all. What should an EDIT row's body be?
|
|
972
|
+
|
|
973
|
+
**Decision — the edited area as it looks now.** An EDIT row renders the **resulting span**: the edited region of the entry *after* the write, line-numbered, with a couple of lines of context above and below. The model sees post-edit state inline — no confirming READ — and the same rendering serves the model's own EDITs and the system delta-EDITs (§14.5) identically. The meta line still carries op + target, so "I EDITed X" stays legible; the body says "and here's X now."
|
|
974
|
+
|
|
975
|
+
**Scope.** The span is computed at edit time — the write range and the result are both known then — and stored on the EDIT's `rx`; the render reads it. A large span is bounded like any rendered slice, and FOLD collapses it to the coordinate when the model doesn't need it.
|
|
976
|
+
|
|
977
|
+
**Migration path.** Changes what EDIT rows *show* (input → output); the op surface and EDIT's behaviour are unchanged. Tests asserting the input-heredoc render move to the resulting-span render.
|
|
978
|
+
|
|
961
979
|
---
|
|
962
980
|
|
|
963
981
|
## §15 Packet shape
|
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":"AAaA,MAAM,CAAC,OAAO,OAAO,KAAK;;IAItB,MAAM,CAAC,UAAU,SAA8C;IAC/D,MAAM,CAAC,kBAAkB,SAA6C;IAKtE,MAAM,CAAC,cAAc,SAAkC;IAIvD,MAAM,CAAC,mBAAmB,SAAuC;CAuBpE"}
|
|
1
|
+
{"version":3,"file":"Paths.d.ts","sourceRoot":"","sources":["../src/Paths.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,OAAO,OAAO,KAAK;;IAItB,MAAM,CAAC,UAAU,SAA8C;IAC/D,MAAM,CAAC,kBAAkB,SAA6C;IAKtE,MAAM,CAAC,WAAW,SAAoD;IAKtE,MAAM,CAAC,cAAc,SAAkC;IAIvD,MAAM,CAAC,mBAAmB,SAAuC;CAuBpE"}
|
package/dist/Paths.js
CHANGED
|
@@ -14,6 +14,11 @@ export default class Paths {
|
|
|
14
14
|
static #GRAMMAR_ROOT = dirname(fileURLToPath(import.meta.resolve("@plurnk/plurnk-grammar/package.json")));
|
|
15
15
|
static migrations = resolve(Paths.#PACKAGE_ROOT, "migrations");
|
|
16
16
|
static instructionsSystem = resolve(Paths.#GRAMMAR_ROOT, "plurnk.md");
|
|
17
|
+
// The GBNF artifact (the full multi-op root) for grammar-constrained
|
|
18
|
+
// sampling, resolved from the grammar package like the sysprompt above.
|
|
19
|
+
// Plumbed to the provider per generate() when PLURNK_PROVIDERS_GBNF is
|
|
20
|
+
// enabled; the service holds no opinion about its content or root.
|
|
21
|
+
static grammarGbnf = resolve(Paths.#GRAMMAR_ROOT, "dist/plurnk.gbnf");
|
|
17
22
|
// packet.system.persona DEFAULT. Cascade at packet-build time is
|
|
18
23
|
// loops.persona > runs.persona > sessions.persona > this file
|
|
19
24
|
// RPC overrides on loop.run / session.attach / session.create populate
|
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;AAE7C,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,iEAAiE;IACjE,gEAAgE;IAChE,uEAAuE;IACvE,iEAAiE;IACjE,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,sBAAsB,EAAE,CAAC;IACvD,uEAAuE;IACvE,uEAAuE;IACvE,0EAA0E;IAC1E,MAAM,CAAC,mBAAmB,GAAG,KAAK,CAAC,2BAA2B,EAAE,CAAC;IAEjE,yEAAyE;IACzE,kEAAkE;IAClE,mEAAmE;IACnE,oDAAoD;IACpD,MAAM,CAAC,sBAAsB;QACzB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QACvC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IACtD,CAAC;IAED,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,CAAC,2BAA2B;QAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAC5C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAC3D,CAAC"}
|
|
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;AAE7C,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,qEAAqE;IACrE,wEAAwE;IACxE,uEAAuE;IACvE,mEAAmE;IACnE,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC;IACtE,iEAAiE;IACjE,gEAAgE;IAChE,uEAAuE;IACvE,iEAAiE;IACjE,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,sBAAsB,EAAE,CAAC;IACvD,uEAAuE;IACvE,uEAAuE;IACvE,0EAA0E;IAC1E,MAAM,CAAC,mBAAmB,GAAG,KAAK,CAAC,2BAA2B,EAAE,CAAC;IAEjE,yEAAyE;IACzE,kEAAkE;IAClE,mEAAmE;IACnE,oDAAoD;IACpD,MAAM,CAAC,sBAAsB;QACzB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QACvC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IACtD,CAAC;IAED,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,CAAC,2BAA2B;QAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAC5C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAC3D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edited-span.d.ts","sourceRoot":"","sources":["../../src/content/edited-span.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,EAAE,SAAS,MAAM,EAAE,gBAAW,KAAG,MAc3E,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { diffLines } from "diff";
|
|
2
|
+
// §14.6 — the resulting span: the changed region of `updated` (relative to
|
|
3
|
+
// `original`), line-numbered (1-indexed), with `context` lines of padding above
|
|
4
|
+
// and below. Diff to find the changed lines, render their post-edit state.
|
|
5
|
+
// Shared by EDIT result rendering (`_entry-ops`) and the environment-delta
|
|
6
|
+
// materialization (`Engine`, §14.5) — both show "the edited area as it is now."
|
|
7
|
+
export const editedSpan = (original, updated, context = 2) => {
|
|
8
|
+
const rows = [];
|
|
9
|
+
for (const part of diffLines(original, updated)) {
|
|
10
|
+
if (part.removed)
|
|
11
|
+
continue; // removed lines aren't in the new content
|
|
12
|
+
const ls = part.value.split("\n");
|
|
13
|
+
if (ls.length > 1 && ls[ls.length - 1] === "")
|
|
14
|
+
ls.pop(); // drop trailing-newline artifact
|
|
15
|
+
for (const t of ls)
|
|
16
|
+
rows.push({ text: t, changed: part.added === true });
|
|
17
|
+
}
|
|
18
|
+
let lo = -1, hi = -1;
|
|
19
|
+
for (let i = 0; i < rows.length; i++)
|
|
20
|
+
if (rows[i].changed) {
|
|
21
|
+
if (lo < 0)
|
|
22
|
+
lo = i;
|
|
23
|
+
hi = i;
|
|
24
|
+
}
|
|
25
|
+
if (lo < 0) {
|
|
26
|
+
lo = 0;
|
|
27
|
+
hi = rows.length - 1;
|
|
28
|
+
} // no added hunk (defensive) → whole
|
|
29
|
+
const start = Math.max(0, lo - context);
|
|
30
|
+
const end = Math.min(rows.length - 1, hi + context);
|
|
31
|
+
return rows.slice(start, end + 1).map((r, i) => `${start + i + 1}:\t${r.text}`).join("\n");
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=edited-span.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edited-span.js","sourceRoot":"","sources":["../../src/content/edited-span.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,2EAA2E;AAC3E,gFAAgF;AAChF,2EAA2E;AAC3E,2EAA2E;AAC3E,gFAAgF;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAE,OAAe,EAAE,OAAO,GAAG,CAAC,EAAU,EAAE;IACjF,MAAM,IAAI,GAAyC,EAAE,CAAC;IACtD,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;QAC9C,IAAI,IAAI,CAAC,OAAO;YAAE,SAAS,CAAE,0CAA0C;QACvE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE;YAAE,EAAE,CAAC,GAAG,EAAE,CAAC,CAAE,iCAAiC;QAC3F,KAAK,MAAM,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAAC,IAAI,EAAE,GAAG,CAAC;gBAAE,EAAE,GAAG,CAAC,CAAC;YAAC,EAAE,GAAG,CAAC,CAAC;QAAC,CAAC;IAC1F,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;QAAC,EAAE,GAAG,CAAC,CAAC;QAAC,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAAC,CAAC,CAAE,oCAAoC;IACnF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/F,CAAC,CAAC"}
|
package/dist/content/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export { default as Matcher } from "./matcher.ts";
|
|
|
6
6
|
export type { MatchResult } from "./matcher.ts";
|
|
7
7
|
export { default as ReadResolve } from "./read-resolve.ts";
|
|
8
8
|
export type { ReadSliceResult } from "./read-resolve.ts";
|
|
9
|
+
export { editedSpan } from "./edited-span.ts";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC5D,YAAY,EAAE,UAAU,IAAI,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEnG,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC5D,YAAY,EAAE,UAAU,IAAI,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEnG,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/content/index.js
CHANGED
|
@@ -7,4 +7,5 @@ export { default as LineMarkerOps } from "./line-marker.js";
|
|
|
7
7
|
export { default as PathMimetype } from "./path-mimetype.js";
|
|
8
8
|
export { default as Matcher } from "./matcher.js";
|
|
9
9
|
export { default as ReadResolve } from "./read-resolve.js";
|
|
10
|
+
export { editedSpan } from "./edited-span.js";
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,2EAA2E;AAC3E,uEAAuE;AACvE,wEAAwE;AAExE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,2EAA2E;AAC3E,uEAAuE;AACvE,wEAAwE;AAExE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/core/Engine.d.ts
CHANGED
|
@@ -68,6 +68,11 @@ export default class Engine {
|
|
|
68
68
|
tokenize?: (text: string) => number;
|
|
69
69
|
});
|
|
70
70
|
setExecutors(executors: ExecutorRegistry): void;
|
|
71
|
+
loopUsage(loopId: number): Promise<{
|
|
72
|
+
promptTokens: number;
|
|
73
|
+
completionTokens: number;
|
|
74
|
+
costPico: number;
|
|
75
|
+
}>;
|
|
71
76
|
runLoop({ provider, messages, persona, requirements, sessionId, runId, loopId, maxTurns, maxStrikes, minCycles, maxCyclePeriod, origin, signal, onDispatch, }: {
|
|
72
77
|
provider: Provider;
|
|
73
78
|
messages: ChatMessage[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAA4D,MAAM,wBAAwB,CAAC;AAMxH,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAiB,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Engine.js
CHANGED
|
@@ -4,15 +4,19 @@ import { Mimetypes, emptyRegistry } from "@plurnk/plurnk-mimetypes";
|
|
|
4
4
|
import EntryCrud from "../schemes/_entry-crud.js";
|
|
5
5
|
import EntryManifest from "../schemes/_entry-manifest.js";
|
|
6
6
|
import GitMembership from "./git-membership.js";
|
|
7
|
+
import GitState from "./git-state.js";
|
|
7
8
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
8
|
-
import { LineMarkerOps, MimetypeBinary } from "../content/index.js";
|
|
9
|
+
import { LineMarkerOps, MimetypeBinary, editedSpan } from "../content/index.js";
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import Paths from "../Paths.js";
|
|
12
|
+
import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
|
|
9
13
|
// Shared module imported by both Engine and bin/digest.ts, so wire
|
|
10
14
|
// projection and digest projection are structurally one function — no
|
|
11
15
|
// drift between wire and digest possible.
|
|
12
16
|
import PacketWire from "./packet-wire.js";
|
|
13
17
|
// SPEC §3.6: writer must be in target scheme's manifest.writableBy.
|
|
14
18
|
// OPEN/FOLD/READ/FIND are not gated — they curate the log or read, never mutating an entry.
|
|
15
|
-
const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC"]);
|
|
19
|
+
const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC", "KILL"]);
|
|
16
20
|
const DEFAULT_MAX_STRIKES = 3;
|
|
17
21
|
const DEFAULT_MAX_COMMANDS = 99;
|
|
18
22
|
const DEFAULT_BUDGET_CEILING = 0.9;
|
|
@@ -228,6 +232,8 @@ class Engine {
|
|
|
228
232
|
// status code but has no way to surface why the loop degraded.
|
|
229
233
|
// Per-grammar 0.17.0 protocol — see SPEC §15.1.
|
|
230
234
|
#telemetryEventNotify;
|
|
235
|
+
// Cached plurnk GBNF — read once on the first constrained generate (#189).
|
|
236
|
+
#gbnfCache = null;
|
|
231
237
|
constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify, tokenize }) {
|
|
232
238
|
this.#db = db;
|
|
233
239
|
this.#schemes = schemes;
|
|
@@ -252,6 +258,28 @@ class Engine {
|
|
|
252
258
|
setExecutors(executors) {
|
|
253
259
|
this.#executors = executors;
|
|
254
260
|
}
|
|
261
|
+
// Grammar-constrained sampling (#189): when PLURNK_PROVIDERS_GBNF is enabled
|
|
262
|
+
// (the only knob — default-on in .env.example), hand the provider the plurnk
|
|
263
|
+
// GBNF (the full shipped multi-op root, read once + cached). The provider
|
|
264
|
+
// attaches it iff the backend supports it and silently drops it otherwise —
|
|
265
|
+
// capability is providers' concern, not ours. Pure plumbing grammar→provider.
|
|
266
|
+
async #grammarConstraint() {
|
|
267
|
+
if (process.env.PLURNK_PROVIDERS_GBNF !== "1")
|
|
268
|
+
return undefined;
|
|
269
|
+
this.#gbnfCache ??= await readFile(Paths.grammarGbnf, "utf8");
|
|
270
|
+
return this.#gbnfCache;
|
|
271
|
+
}
|
|
272
|
+
// Per-loop usage totals (#197): SUM the loop's turns (usage is stored per
|
|
273
|
+
// turn, §14.2). Surfaced on loop.run + loop/terminated so clients render real
|
|
274
|
+
// token/cost numbers. costPico is the stored pico-dollar unit.
|
|
275
|
+
async loopUsage(loopId) {
|
|
276
|
+
const row = await this.#db.engine_loop_usage.get({ loop_id: loopId });
|
|
277
|
+
return {
|
|
278
|
+
promptTokens: row?.prompt ?? 0,
|
|
279
|
+
completionTokens: row?.completion ?? 0,
|
|
280
|
+
costPico: row?.cost_pico ?? 0,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
255
283
|
#pushTelemetry(sessionId, loopId, event) {
|
|
256
284
|
const existing = this.#telemetryBuffer.get(loopId);
|
|
257
285
|
if (existing === undefined)
|
|
@@ -450,7 +478,7 @@ class Engine {
|
|
|
450
478
|
};
|
|
451
479
|
await this.dispatch({
|
|
452
480
|
statement: promptStmt, sessionId, runId, loopId, turnId,
|
|
453
|
-
sequence: nextActionIndex, origin: "system",
|
|
481
|
+
sequence: nextActionIndex, origin: "system", onDispatch,
|
|
454
482
|
});
|
|
455
483
|
nextActionIndex++;
|
|
456
484
|
}
|
|
@@ -472,7 +500,7 @@ class Engine {
|
|
|
472
500
|
pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
|
|
473
501
|
};
|
|
474
502
|
// SPEC §14.3 D4/D5 — git-ls-files workspace membership, resolved at
|
|
475
|
-
// prompt-composition (EMI is eager +
|
|
503
|
+
// prompt-composition (EMI is eager + exhaustive — git is the only bound). When the
|
|
476
504
|
// session's project_root is a git working tree, tracked files are
|
|
477
505
|
// members without a client `add`; active members are materialized
|
|
478
506
|
// (disk → body channel) so they appear in the manifest below. No-ops
|
|
@@ -483,19 +511,27 @@ class Engine {
|
|
|
483
511
|
channels: { body: { content: await EntryManifest.buildManifestBody(systemCtx), mimetype: "application/json" } },
|
|
484
512
|
tags: [],
|
|
485
513
|
}, systemCtx, "plurnk");
|
|
514
|
+
// §14.5 — pre-seed environment deltas (changes since this run last
|
|
515
|
+
// reconciled) as system EDIT rows, before the packet composes; advance
|
|
516
|
+
// the action index past them so model ops continue after.
|
|
517
|
+
nextActionIndex += await this.#materializeEnvironmentDeltas({ sessionId, runId, loopId, turnId, fromSequence: nextActionIndex });
|
|
518
|
+
// SPEC §15.1 — git working-tree state for the telemetry section, read once
|
|
519
|
+
// (a service-side `git status` shell-out) and threaded into the budget
|
|
520
|
+
// rebuild too so it isn't re-shelled on overflow.
|
|
521
|
+
const gitStatus = await GitState.status(this.#db, sessionId, this.#loopAborts.get(loopId)?.signal);
|
|
486
522
|
// Build the spec'd packet (Packet.json) request half. #buildLog
|
|
487
523
|
// queries log_entries scoped to the run — the prompt entry just
|
|
488
524
|
// written (if turn 1) is part of that query result.
|
|
489
525
|
let requestPacket = await this.#buildRequestPacket({
|
|
490
526
|
initialMessages: messages, persona, requirements, runId, loopId,
|
|
491
|
-
currentTurnSeq: seq, provider,
|
|
527
|
+
currentTurnSeq: seq, provider, gitStatus,
|
|
492
528
|
});
|
|
493
529
|
// SPEC §14.4 — budget grinder, pre-LLM: reclaim window on actual overflow.
|
|
494
530
|
const enforced = await this.#enforceBudget({
|
|
495
531
|
packet: requestPacket, provider, runId, loopId, turnId, sessionId, turnNumber,
|
|
496
532
|
rebuild: (telemetryErrors) => this.#buildRequestPacket({
|
|
497
533
|
initialMessages: messages, persona, requirements, runId, loopId,
|
|
498
|
-
currentTurnSeq: seq, provider, telemetryErrors,
|
|
534
|
+
currentTurnSeq: seq, provider, telemetryErrors, gitStatus,
|
|
499
535
|
}),
|
|
500
536
|
});
|
|
501
537
|
requestPacket = enforced.packet;
|
|
@@ -511,7 +547,13 @@ class Engine {
|
|
|
511
547
|
return { turnId, status: 413, statuses: [], fingerprint: "", budgetStruck: enforced.struck, budgetHardStop: true };
|
|
512
548
|
}
|
|
513
549
|
const modelMessages = this.#packetToWireMessages(requestPacket);
|
|
514
|
-
|
|
550
|
+
// maxTokens = remaining context window (loop policy, plurnk-providers#10).
|
|
551
|
+
// The 0.28.0 EOS-forcing root terminates the turn at the status SEND, but a
|
|
552
|
+
// grammar can't bound degeneration *inside* a statement body — this caps the
|
|
553
|
+
// decode at the free window so a runaway can't reach the context wall.
|
|
554
|
+
const genCeiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
555
|
+
const maxTokens = genCeiling === null ? undefined : Math.max(1, genCeiling - requestPacket.system.tokens - requestPacket.user.tokens);
|
|
556
|
+
const response = await provider.generate({ messages: modelMessages, signal, grammar: await this.#grammarConstraint(), maxTokens });
|
|
515
557
|
// Engine splits wire-level response: emission (content, reasoning,
|
|
516
558
|
// parsed ops) → packet.assistant per Packet.json §assistant;
|
|
517
559
|
// call-metadata (usage, finishReason, model) → Turn columns per
|
|
@@ -672,7 +714,7 @@ class Engine {
|
|
|
672
714
|
// and §user) BEFORE the provider call. The same packet object is then
|
|
673
715
|
// completed with assistant + assistantRaw after the model responds, so
|
|
674
716
|
// the stored packet and the wire payload share one source of truth.
|
|
675
|
-
async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, telemetryErrors: presetTelemetry, }) {
|
|
717
|
+
async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, gitStatus, telemetryErrors: presetTelemetry, }) {
|
|
676
718
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
677
719
|
const system_definition = byRole("system");
|
|
678
720
|
// user.prompt sources from the loop's most recent prompt entry first
|
|
@@ -707,7 +749,7 @@ class Engine {
|
|
|
707
749
|
const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
708
750
|
const scratch = {
|
|
709
751
|
system: { system_definition, persona, log },
|
|
710
|
-
user: { prompt, telemetry: { budget: "", errors: telemetryErrors }, system_requirements: requirements },
|
|
752
|
+
user: { prompt, telemetry: { budget: "", errors: telemetryErrors, git: gitStatus }, system_requirements: requirements },
|
|
711
753
|
};
|
|
712
754
|
const sections = PacketWire.measureBudgetSections(scratch, countTokens);
|
|
713
755
|
scratch.user.telemetry.budget = this.#renderBudget(sections, ceiling);
|
|
@@ -721,7 +763,7 @@ class Engine {
|
|
|
721
763
|
.replace(TOKEN_PERCENT_PLACEHOLDER, String(percent))
|
|
722
764
|
.replace(TOKENS_FREE_PLACEHOLDER, String(tokensFree));
|
|
723
765
|
const system = { tokens: 0, system_definition, persona, log };
|
|
724
|
-
const user = { tokens: 0, prompt, telemetry: { budget, errors: telemetryErrors }, system_requirements: requirements };
|
|
766
|
+
const user = { tokens: 0, prompt, telemetry: { budget, errors: telemetryErrors, git: gitStatus }, system_requirements: requirements };
|
|
725
767
|
system.tokens = countTokens(PacketWire.renderSystemContent(system));
|
|
726
768
|
user.tokens = countTokens(PacketWire.renderUserContent(user));
|
|
727
769
|
return { system, user };
|
|
@@ -873,6 +915,47 @@ class Engine {
|
|
|
873
915
|
source: r.source,
|
|
874
916
|
}));
|
|
875
917
|
}
|
|
918
|
+
// §14.5 — at pre-turn build, reconcile each session entry against this run's
|
|
919
|
+
// watermark. First sight sets it silently; a content change materializes a
|
|
920
|
+
// delta-EDIT (origin=system, the §14.6 result span) at the next sequence and
|
|
921
|
+
// advances the mark. Excludes plurnk:// (manifest/prompt) and bare/file
|
|
922
|
+
// entries (scheme NULL — the EMI's territory). Returns the count so the
|
|
923
|
+
// caller advances nextActionIndex past the pre-seeded deltas.
|
|
924
|
+
async #materializeEnvironmentDeltas(args) {
|
|
925
|
+
const { sessionId, runId, loopId, turnId, fromSequence } = args;
|
|
926
|
+
const rows = await this.#db.engine_list_session_entries.all({ session_id: sessionId });
|
|
927
|
+
let written = 0;
|
|
928
|
+
for (const r of rows) {
|
|
929
|
+
// plurnk:// is engine-derived (manifest/prompt) — skip. File entries
|
|
930
|
+
// (scheme=null) ARE included: an out-of-band disk divergence, re-read by
|
|
931
|
+
// git membership, surfaces here as the §14.3 EMI signal (source="file").
|
|
932
|
+
if (r.scheme === "plurnk")
|
|
933
|
+
continue;
|
|
934
|
+
const wm = await this.#db.engine_get_watermark.get({
|
|
935
|
+
run_id: runId, entry_id: r.entry_id, channel: r.channel,
|
|
936
|
+
});
|
|
937
|
+
if (wm === undefined) {
|
|
938
|
+
await this.#db.engine_set_watermark.run({ run_id: runId, entry_id: r.entry_id, channel: r.channel, content: r.content });
|
|
939
|
+
continue; // first sight — reconcile silently, no delta
|
|
940
|
+
}
|
|
941
|
+
if (wm.content === r.content)
|
|
942
|
+
continue; // unchanged
|
|
943
|
+
const span = editedSpan(wm.content, r.content);
|
|
944
|
+
await this.#db.engine_insert_log_entry.get({
|
|
945
|
+
run_id: runId, loop_id: loopId, turn_id: turnId,
|
|
946
|
+
sequence: fromSequence + written, origin: "system", source: r.scheme === null ? "file" : null,
|
|
947
|
+
op: "EDIT", suffix: "", signal: null,
|
|
948
|
+
scheme: r.scheme, username: null, password: null, hostname: null, port: null,
|
|
949
|
+
pathname: r.pathname, params: null, fragment: null, lineMarker: null,
|
|
950
|
+
tx: "", mimetype_tx: "text/plain",
|
|
951
|
+
rx: JSON.stringify({ status: 200, entryId: r.entry_id, channel: r.channel, span }), mimetype_rx: "application/json",
|
|
952
|
+
status_rx: 200, tokens: 0, state: "resolved", outcome: null, attrs: "{}",
|
|
953
|
+
});
|
|
954
|
+
await this.#db.engine_set_watermark.run({ run_id: runId, entry_id: r.entry_id, channel: r.channel, content: r.content });
|
|
955
|
+
written++;
|
|
956
|
+
}
|
|
957
|
+
return written;
|
|
958
|
+
}
|
|
876
959
|
async dispatch(context) {
|
|
877
960
|
const { statement, sessionId, runId, loopId, turnId, sequence, origin, onDispatch } = context;
|
|
878
961
|
const schemeCtx = {
|
|
@@ -910,6 +993,12 @@ class Engine {
|
|
|
910
993
|
else if (statement.op === "MOVE") {
|
|
911
994
|
result = await this.#handleMove(statement, schemeCtx);
|
|
912
995
|
}
|
|
996
|
+
else if (statement.op === "KILL") {
|
|
997
|
+
result = await this.#handleKill(statement, schemeCtx);
|
|
998
|
+
}
|
|
999
|
+
else if (statement.op === "PLAN") {
|
|
1000
|
+
result = this.#handlePlan(statement);
|
|
1001
|
+
}
|
|
913
1002
|
else if (statement.op === "EXEC") {
|
|
914
1003
|
// EXEC's target slot is `cwd`, not a scheme address.
|
|
915
1004
|
// Per plurnk.md the op routes unconditionally to the
|
|
@@ -1294,6 +1383,51 @@ class Engine {
|
|
|
1294
1383
|
return { status: delResult.status };
|
|
1295
1384
|
return copyResult;
|
|
1296
1385
|
}
|
|
1386
|
+
// KILL — scheme-polymorphic destroy (plurnk-grammar#203 / 0.28.0). Entry-KILL
|
|
1387
|
+
// permanently deletes the entry: the canonical delete now, MOVE→/dev/null
|
|
1388
|
+
// retired from the model's vocabulary. Process-KILL (exec://) aborts the
|
|
1389
|
+
// running spawn's controller (the same teardown loop.cancel rides), addressed
|
|
1390
|
+
// by coordinate pathname (#203). The KILL body is an opaque
|
|
1391
|
+
// annotation with no runtime meaning; it survives into the log row's tx for
|
|
1392
|
+
// free via the statement serialization. Status: 200 killed · 404 unknown ·
|
|
1393
|
+
// 405 log:// (append-only) · 403 writableBy (the #checkWritable gate, KILL ∈
|
|
1394
|
+
// MUTATING_OPS) · 200/404 exec (killed / not running) · 501 no-kill/delete scheme.
|
|
1395
|
+
async #handleKill(statement, ctx) {
|
|
1396
|
+
if (statement.op !== "KILL")
|
|
1397
|
+
throw new Error("unreachable");
|
|
1398
|
+
const path = statement.target;
|
|
1399
|
+
if (path === null)
|
|
1400
|
+
return { status: 400, error: "KILL requires a target path" };
|
|
1401
|
+
const schemeName = this.#schemeNameOf(path);
|
|
1402
|
+
if (schemeName === null)
|
|
1403
|
+
return { status: 400, error: "KILL target must be a URL path with a scheme" };
|
|
1404
|
+
if (schemeName === "log")
|
|
1405
|
+
return { status: 405, error: "log:// is append-only; KILL must bounce" };
|
|
1406
|
+
if (schemeName === "exec") {
|
|
1407
|
+
const execHandler = this.#schemes.get("exec");
|
|
1408
|
+
if (execHandler === undefined || typeof execHandler.kill !== "function")
|
|
1409
|
+
return { status: 501 };
|
|
1410
|
+
return execHandler.kill(pathnameFromPath(path));
|
|
1411
|
+
}
|
|
1412
|
+
const handler = this.#schemes.get(schemeName);
|
|
1413
|
+
if (handler === undefined || typeof handler.deleteEntry !== "function")
|
|
1414
|
+
return { status: 501 };
|
|
1415
|
+
const delResult = await handler.deleteEntry(pathnameFromPath(path), ctx);
|
|
1416
|
+
return { status: delResult.status };
|
|
1417
|
+
}
|
|
1418
|
+
// PLAN — undocumented reasoning op (plurnk-grammar 0.30.0, the 11th op). A pure
|
|
1419
|
+
// no-op: the body is the model's in-content reasoning, which lands in the log
|
|
1420
|
+
// row's tx for free via statement serialization, no runtime effect (PLAN ∉
|
|
1421
|
+
// MUTATING_OPS). NOT taught in plurnk.md by design — reserved for a future model
|
|
1422
|
+
// that must reason in the content field (no separate reasoning channel). gemma
|
|
1423
|
+
// reasons on its own channel via the relaxed root, is never shown PLAN, and never
|
|
1424
|
+
// emits it; this handler exists only so the op resolves cleanly if we ever
|
|
1425
|
+
// deliberately reach for it (which would also require a plurnk.md override).
|
|
1426
|
+
#handlePlan(statement) {
|
|
1427
|
+
if (statement.op !== "PLAN")
|
|
1428
|
+
throw new Error("unreachable");
|
|
1429
|
+
return { status: 200 };
|
|
1430
|
+
}
|
|
1297
1431
|
async #copyOrchestration({ statement, srcPath, dstPath, ctx }) {
|
|
1298
1432
|
const srcSchemeName = this.#schemeNameOf(srcPath);
|
|
1299
1433
|
const dstSchemeName = this.#schemeNameOf(dstPath);
|
|
@@ -1388,6 +1522,13 @@ class Engine {
|
|
|
1388
1522
|
const method = handler[methodName];
|
|
1389
1523
|
if (typeof method !== "function")
|
|
1390
1524
|
return { status: 501 };
|
|
1525
|
+
// External @plurnk/plurnk-schemes-* siblings receive the DB-free SchemeCtx
|
|
1526
|
+
// (caps), never the raw PlurnkSchemeContext (schemes SPEC §5). The dynamic
|
|
1527
|
+
// dispatch is typed for in-tree schemes; the cast bridges the ctx shapes —
|
|
1528
|
+
// the sibling reads caps, the in-tree handler reads db.
|
|
1529
|
+
if (this.#schemes.isExternal(schemeName)) {
|
|
1530
|
+
return method.call(handler, statement, new SchemeCtxImpl(ctx, schemeName));
|
|
1531
|
+
}
|
|
1391
1532
|
return method.call(handler, statement, ctx);
|
|
1392
1533
|
}
|
|
1393
1534
|
// Bare paths default to the file scheme per plurnk.md (grammar sysprompt):
|
|
@@ -1396,8 +1537,9 @@ class Engine {
|
|
|
1396
1537
|
#schemeNameOf(path) {
|
|
1397
1538
|
if (path === null)
|
|
1398
1539
|
return null;
|
|
1540
|
+
// http + https are one scheme — the http sibling owns both prefixes (#195).
|
|
1399
1541
|
if (path.kind === "url")
|
|
1400
|
-
return path.scheme;
|
|
1542
|
+
return path.scheme === "https" ? "http" : path.scheme;
|
|
1401
1543
|
return "file"; // local (bare) → file
|
|
1402
1544
|
}
|
|
1403
1545
|
async #writeLog({ statement, result, runId, loopId, turnId, sequence, origin, }) {
|