@plurnk/plurnk-service 0.45.0 → 0.46.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/.env.example +14 -1
- package/README.md +1 -1
- package/SPEC.md +5 -5
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +46 -40
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +16 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +7 -2
- 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 +31 -0
- package/dist/core/SchemeRegistry.js.map +1 -1
- 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 +11 -0
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/scheme-types.d.ts +1 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/schemes/Exec.d.ts +2 -2
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +30 -25
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +7 -2
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +14 -7
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_entry-semantic.sql +16 -0
- package/dist/schemes/exec-abort.d.ts +11 -0
- package/dist/schemes/exec-abort.d.ts.map +1 -0
- package/dist/schemes/exec-abort.js +23 -0
- package/dist/schemes/exec-abort.js.map +1 -0
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +62 -42
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/drain.sql +10 -0
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +31 -11
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/service.js +1 -1
- package/dist/service.js.map +1 -1
- package/migrations/0000-00-00.01_schema.sql +2 -2
- package/package.json +9 -9
package/.env.example
CHANGED
|
@@ -32,6 +32,10 @@ PLURNK_DB_PATH=~/.plurnk/plurnk.db
|
|
|
32
32
|
PLURNK_HOST=127.0.0.1
|
|
33
33
|
PLURNK_PORT=3044
|
|
34
34
|
|
|
35
|
+
# --- Client (read by the `plurnk` CLI from this shared home; the daemon ignores it) ---
|
|
36
|
+
# The daemon WebSocket URL the client connects to (mirrors PLURNK_HOST/PORT above).
|
|
37
|
+
# PLURNK_WS=ws://127.0.0.1:3044
|
|
38
|
+
|
|
35
39
|
# --- Model aliases ---
|
|
36
40
|
# PLURNK_MODEL is the active provider for every loop (a client may override per loop
|
|
37
41
|
# via loop.run({alias})). Out of the box it is `plurnk`. Change this one line to use
|
|
@@ -64,7 +68,12 @@ PLURNK_RPC_TIMEOUT=30000
|
|
|
64
68
|
# so a fast exec's output can land in it instead of a turn later. A fixed grace
|
|
65
69
|
# beat, NOT a wait-for-completion (slow execs proceed + surface via the wake path).
|
|
66
70
|
# 0 = off (the model sees fast-exec output a turn late, as today).
|
|
67
|
-
PLURNK_EXEC_WAIT_MS=
|
|
71
|
+
PLURNK_EXEC_WAIT_MS=1000
|
|
72
|
+
# Teardown reap grace: when a loop/run tears down a background exec, the spawn gets a polite
|
|
73
|
+
# signal first (SIGHUP, or the model's KILL[code]); a stream that IGNORES it is hard-killed
|
|
74
|
+
# (SIGKILL, to the whole process group) this many ms later — so the reap can't wedge on a
|
|
75
|
+
# signal-ignoring child. plurnk-execs refuses to bake this number; the consumer owns it.
|
|
76
|
+
PLURNK_EXEC_KILL_GRACE_MS=2000
|
|
68
77
|
PLURNK_LOOP_TIMEOUT=86400000
|
|
69
78
|
|
|
70
79
|
# --- Engine rails ---
|
|
@@ -141,6 +150,10 @@ PLURNK_VERSION_POLL_TTL=3600000
|
|
|
141
150
|
# @plurnk/plurnk-grammar; an absolute/relative path is your own. =0 (or empty) disables.
|
|
142
151
|
# PLURNK_PROVIDERS_GBNF=plurnk.gbnf
|
|
143
152
|
|
|
153
|
+
# GBNF debug, honored natively by @plurnk/plurnk-providers (>=0.13.0): validate the grammar locally
|
|
154
|
+
# (fail-hard if malformed) and run UNCONSTRAINED, never sending it to the model. Dev aid; off by default.
|
|
155
|
+
PLURNK_GBNF_DEBUG=0
|
|
156
|
+
|
|
144
157
|
# Provider retry attempts (providers 0.7+): how many times generate() retries a
|
|
145
158
|
# TRANSIENT failure (429 rate-limit, 5xx/network) with exponential backoff (base 2s
|
|
146
159
|
# is a provider constant — the COUNT is the operator knob). REQUIRED, fail-hard if
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Config + state live in `~/.plurnk/` (created on first run): set `PLURNK_MODEL` a
|
|
|
41
41
|
|
|
42
42
|
## Semantic search
|
|
43
43
|
|
|
44
|
-
`FIND` ranks via an optional embedder peer, `@plurnk/plurnk-mimetypes-embeddings` (heavy native deps; not installed by default). Absent → `
|
|
44
|
+
`FIND`'s `~query` ranks semantically via an optional embedder peer, `@plurnk/plurnk-mimetypes-embeddings` (heavy native deps; not installed by default). Absent → `~query` falls back to FTS keyword ranking and `start` prints an `embedder inactive` notice. Enable vector search: `npm i @plurnk/plurnk-mimetypes-embeddings`.
|
|
45
45
|
|
|
46
46
|
## Tests
|
|
47
47
|
|
package/SPEC.md
CHANGED
|
@@ -193,10 +193,10 @@ Beyond the three creation ops:
|
|
|
193
193
|
A run is a **log plus a cancellation scope** — one `AbortController` per run, reused while live and replaced only once aborted, so a cancel ends the run as a unit and a later `loop.run` is never born cancelled. A run's queued loops are advanced by a **drain**: a single per-run worker that claims loops atomically (status 100→102) and runs each under the run's scope. A loop may spawn **streams** (execs) that outlive it; each is a row in the subscription registry (§subscriptions) — the durable record of what the run holds open. Cancellation and conclusion are defined against these structures, never wall-clock timing.
|
|
194
194
|
|
|
195
195
|
- **One drain advances a run.** At most one drain is registered for a run at any instant: a `loop.run` or wake on a run with a live drain folds in (active→next-turn) or enqueues a loop that drain claims, never a second parallel drain. A drain's start and its empty-queue teardown relinquish run under one per-run lock, so the teardown's re-claim cannot race a concurrent start into a double-drain. {§run-lifecycle-single-drain}
|
|
196
|
-
- **A cancel reaps every stream the run holds — by the registry.** `loop.cancel` / `KILL` / shutdown abort the run scope AND iterate the run's open subscriptions, aborting each via its owning scheme; the registry is the source of truth, the in-process abort signal a fast-path optimization. A stream that is running, mid-spawn (its row written before it is killable), or spawned after the cancel is reaped alike. {§run-lifecycle-total-reap}
|
|
196
|
+
- **A cancel reaps every stream the run holds — by the registry.** `loop.cancel` / `KILL` / shutdown abort the run scope AND iterate the run's open subscriptions, aborting each via its owning scheme; the registry is the source of truth, the in-process abort signal a fast-path optimization. A stream that is running, mid-spawn (its row written before it is killable), or spawned after the cancel is reaped alike. The teardown abort is a BOUNDED reap — the executor sends a polite signal then SIGKILL after a consumer-set grace (`PLURNK_EXEC_KILL_GRACE_MS`), so a signal-ignoring stream can't wedge it; a model `KILL[code]` on a live stream instead delivers exactly that signal once (bare `KILL` → the executor's SIGHUP default, `KILL[9]` → SIGKILL), the model owning any escalation. {§run-lifecycle-total-reap}
|
|
197
197
|
- **A stream's kill binds to the scope it captured at spawn.** A stream captures the run's cancellation scope as it registers and wires its kill to it, re-checking `aborted` AFTER wiring — no check-then-listen gap can drop an abort that lands mid-registration. Because the scope is replaced only once aborted, a captured-then-replaced scope is necessarily already aborted, so replacement never strands a live stream. {§run-lifecycle-exec-epoch-bound}
|
|
198
198
|
- **A cancelled run is not resurrected by its own torn-down work.** A stream conclusion delivered to a cancelled, idle run starts no fresh drain: an aborted (499) conclusion is skipped, and a straggler that concluded cleanly surfaces its deliverable as an environment delta (§env-delta), never a revived loop. The cancel was deliberate; only an explicit `loop.run` resumes the run. {§run-lifecycle-no-resurrection}
|
|
199
|
-
- **A stream conclusion always reaches its run.** When a backgrounded stream concludes, the daemon routes it through the same inject seam as any loop source (§actor-boundary-passive-wake): an active run folds the
|
|
199
|
+
- **A stream conclusion always reaches its run.** When a backgrounded stream concludes, the daemon routes it through the same inject seam as any loop source (§actor-boundary-passive-wake): an active run folds the conclusion into its next turn; a run parked at a **slept loop** (`SEND[202]`) **awakens that loop in place** — the slept loop *is* the continuation, so there is no fresh loop and no summary-as-prompt fiction. The result is never lost: a parked loop sleeps rather than ending, and the stream's status-transition is the OPEN event (§actor-boundary-passive-wake) that wakes it; on resume it reads the concluded stream's own state, not a synthetic prompt. {§run-lifecycle-wake-liveness}
|
|
200
200
|
- **A loop is never stranded by a drain's exit.** A drain relinquishes its registry slot only after a lock-held re-claim confirms the queue is empty; a loop enqueued during that teardown is either re-claimed by the exiting drain or claimed by a fresh drain that a later inject starts. The relinquish and the start are serialized, so neither the lost-loop hang nor a transient double-drain can occur. {§run-lifecycle-no-lost-loop}
|
|
201
201
|
|
|
202
202
|
---
|
|
@@ -896,7 +896,7 @@ registry.register("loop.run", {
|
|
|
896
896
|
|
|
897
897
|
| Method | Params | Result | Notes |
|
|
898
898
|
|-------------------|-------------------------------------|------------------------|-------|
|
|
899
|
-
| `loop.run` | `prompt: string`, `maxTurns?: number`, `alias?: string`, `flags?: LoopFlags` | `{ loopId,
|
|
899
|
+
| `loop.run` | `prompt: string`, `maxTurns?: number`, `alias?: string`, `flags?: LoopFlags` | `{ loopId, action, finalStatus: 100 }` | Model-driven loop. **Accepts and returns immediately** (`finalStatus: 100`; `action` = `enqueued_new_loop` \| `injected_next_turn`) — it never blocks on the loop, which can park indefinitely (`SEND[202]`, §run-lifecycle-wake-liveness). The loop's outcome — `finalStatus`, `turnIds`, `hitMaxTurns`, `usage` — arrives on the **`loop/terminated`** event. Optional `alias` overrides the boot-time `PLURNK_MODEL`. Optional `flags` carries per-loop flags (`{yolo?: boolean}`; more as wired — see §engine-rails). Streams `log/entry` and `loop/proposal` during. `longRunning: false`. {§methods-loop-run} |
|
|
900
900
|
| `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. |
|
|
901
901
|
| `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`. {§methods-loop-cancel} |
|
|
902
902
|
| `providers.list` | none | `{ aliases: ProviderAlias[] }` | Lists configured `PLURNK_MODEL_<alias>` entries with `{alias, provider, model, active}`. Clients use to populate model-selection UI. |
|
|
@@ -947,7 +947,7 @@ Server-initiated events on the same WebSocket.
|
|
|
947
947
|
| `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). |
|
|
948
948
|
| `session/created` | `{ id, name, projectRoot }` | Any client creates a session. |
|
|
949
949
|
| `stream/event` | `{ entryId, channel, state, contentLength }` | Channel content grows or state transitions. {§notifications-stream-event-on-channel-change} |
|
|
950
|
-
| `stream/concluded` | `{ entryId, target, subscriptionId, scheme, closeStatus, summary, wakeAction, wakeLoopId? }` | A streaming subscription closed (subprocess finished / errored / cancelled). `wakeAction` says
|
|
950
|
+
| `stream/concluded` | `{ entryId, target, subscriptionId, scheme, closeStatus, summary, wakeAction, wakeLoopId? }` | A streaming subscription closed (subprocess finished / errored / cancelled). `wakeAction` says how the conclusion reached the run: `resumed-loop` (a slept `202` loop resumed in place, §run-lifecycle-wake-liveness), `no-op-active-loop` (folded into a live loop's next turn), `skipped-aborted`/`skipped-cancelled`/`skipped-no-provider`, or `no-loop` (nothing to resume). `summary` rides the notification for client display; it is no longer fed to the model as a prompt. {§notifications-stream-concluded} |
|
|
951
951
|
| `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. {§notifications-telemetry-event} |
|
|
952
952
|
|
|
953
953
|
`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 ({§notifications-envelope-carries-sessionid}); the broadcast stays session-scoped too.
|
|
@@ -1190,7 +1190,7 @@ The wire projection (`PacketWire.renderSlot`) groups sections by slot into the s
|
|
|
1190
1190
|
|
|
1191
1191
|
**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 the `prompt` section; the entry itself stays READ/FOLD-able like any other. The foisted `EDIT`'s **log row is folded by default** (`expanded=0`): the prompt body already lives in the `prompt` section, so the log keeps the action for forensics while collapsing the duplicate body, re-OPENable like any fold (§open-fold). {§prompt-fold}
|
|
1192
1192
|
|
|
1193
|
-
**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, tags?, channels: { <
|
|
1193
|
+
**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, tags?, channels: { <uri>: { mimetype, tokens, lines } } }` — every channel keyed by the URI the model READs (the default channel by the bare path, a non-default by `path#channel`), so it reaches a channel without guessing. `tags` is present only when the entry carries `entry_tags` — its own categorization, surfaced so the model sees it in the directory and can `FIND` by tag without a separate read. 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}
|
|
1194
1194
|
|
|
1195
1195
|
### §telemetry user.telemetry — model-facing runtime telemetry
|
|
1196
1196
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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
|
|
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
|
@@ -366,13 +366,12 @@ class Engine {
|
|
|
366
366
|
}
|
|
367
367
|
this.#loopAborts.set(loopId, loopAbort);
|
|
368
368
|
// Cleanup splits by termination kind:
|
|
369
|
-
// - "graceful" (
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
// - "forceful" (max_turns,
|
|
374
|
-
//
|
|
375
|
-
// tear down immediately.
|
|
369
|
+
// - "graceful" (SEND[202] Accepted): in-flight streaming-scheme spawns
|
|
370
|
+
// are ALLOWED to outlive the loop — they complete naturally, write final
|
|
371
|
+
// channel state, and wake-on-completion (E.4) opens a fresh loop. 202 is
|
|
372
|
+
// the only terminal that means "keep my async work."
|
|
373
|
+
// - "forceful" (SEND[200] done, max_turns, strike, cancel, budget, 4xx/5xx):
|
|
374
|
+
// fire the loop-level abort so leftover spawns tear down. "Done" reaps.
|
|
376
375
|
const cleanup = (kind, reason) => {
|
|
377
376
|
if (kind === "forceful" && !loopAbort.signal.aborted) {
|
|
378
377
|
loopAbort.abort(reason ?? "loop_forceful_termination");
|
|
@@ -387,10 +386,10 @@ class Engine {
|
|
|
387
386
|
if (row === undefined)
|
|
388
387
|
throw new Error(`Engine.runLoop: loop ${loopId} not found`);
|
|
389
388
|
if (row.status !== 102) {
|
|
390
|
-
//
|
|
391
|
-
// (
|
|
392
|
-
//
|
|
393
|
-
cleanup(row.status
|
|
389
|
+
// Only 202 (Accepted) lets spawns outlive — it IS the async wake
|
|
390
|
+
// contract (E.4). Every other terminal, 200 included, reaps: "done"
|
|
391
|
+
// must not leak running execs. Trust the code's declared intent.
|
|
392
|
+
cleanup(row.status === 202 ? "graceful" : "forceful", `loop_terminal_${row.status}`);
|
|
394
393
|
return { turnIds, finalStatus: row.status, hitMaxTurns: false, reason: "external" };
|
|
395
394
|
}
|
|
396
395
|
if (maxTurns >= 0 && turnIds.length >= maxTurns) {
|
|
@@ -578,6 +577,7 @@ class Engine {
|
|
|
578
577
|
wakeRunNotify: this.#wakeRunNotify,
|
|
579
578
|
tokenize: this.#tokenize,
|
|
580
579
|
mimetypes: this.#mimetypes,
|
|
580
|
+
defaultChannelFor: (s) => this.#schemes.defaultChannelFor(s),
|
|
581
581
|
pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
|
|
582
582
|
};
|
|
583
583
|
// SPEC §membership D4/D5 — git-ls-files workspace membership, resolved at
|
|
@@ -656,14 +656,14 @@ class Engine {
|
|
|
656
656
|
// queries log_entries scoped to the run — the prompt entry just
|
|
657
657
|
// written (if turn 1) is part of that query result.
|
|
658
658
|
let requestPacket = await this.#buildRequestPacket({
|
|
659
|
-
initialMessages: messages, requirements, runId, loopId,
|
|
659
|
+
initialMessages: messages, requirements, sessionId, runId, loopId,
|
|
660
660
|
currentTurnSeq: seq, provider, gitStatus,
|
|
661
661
|
});
|
|
662
662
|
// SPEC §grinder — budget grinder, pre-LLM: reclaim window on actual overflow.
|
|
663
663
|
const enforced = await this.#enforceBudget({
|
|
664
664
|
packet: requestPacket, provider, runId, loopId, turnId, sessionId, turnNumber,
|
|
665
665
|
rebuild: (telemetryErrors) => this.#buildRequestPacket({
|
|
666
|
-
initialMessages: messages, requirements, runId, loopId,
|
|
666
|
+
initialMessages: messages, requirements, sessionId, runId, loopId,
|
|
667
667
|
currentTurnSeq: seq, provider, telemetryErrors, gitStatus,
|
|
668
668
|
}),
|
|
669
669
|
});
|
|
@@ -694,14 +694,16 @@ class Engine {
|
|
|
694
694
|
response = await provider.generate({ messages: modelMessages, runId: String(runId), signal, grammar: await this.#grammarConstraint(), maxTokens, attributions: attributions.length > 0 ? attributions : undefined }); // §provider-surface-generate §provider-guarantees-single-call §provider-guarantees-signal-wired §attribution-plurnk-namespace-reserved
|
|
695
695
|
}
|
|
696
696
|
catch (err) {
|
|
697
|
-
//
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
if (err instanceof ProviderError
|
|
704
|
-
this.#pushTelemetry(sessionId, loopId, { source: "provider", kind:
|
|
697
|
+
// Every provider error surfaces as telemetry (the client/model sees the cause). #256:
|
|
698
|
+
// grammar_unenforced is the one the MODEL can recover from — the backend didn't
|
|
699
|
+
// constrain the GBNF, so this turn was rejected but a conforming emission next turn is
|
|
700
|
+
// accepted: fall through as an empty no-op turn so the strike rail retries. Every other
|
|
701
|
+
// kind (rate_limit, network_failure, unauthorized, …) is terminal — telemetry'd, then
|
|
702
|
+
// propagated to end the loop (rather than only the opaque loop.run rejection).
|
|
703
|
+
if (err instanceof ProviderError) {
|
|
704
|
+
this.#pushTelemetry(sessionId, loopId, { source: "provider", kind: err.kind, message: err.message });
|
|
705
|
+
if (err.kind !== "grammar_unenforced")
|
|
706
|
+
throw err;
|
|
705
707
|
response = {
|
|
706
708
|
assistant: { content: "", reasoning: null, usage: { prompt: requestPacket.tokens, completion: 0, reasoning: 0, cached: 0, total: requestPacket.tokens }, finishReason: null, model: provider.model },
|
|
707
709
|
assistantRaw: null,
|
|
@@ -881,7 +883,7 @@ class Engine {
|
|
|
881
883
|
// and §user) BEFORE the provider call. The same packet object is then
|
|
882
884
|
// completed with assistant + assistantRaw after the model responds, so
|
|
883
885
|
// the stored packet and the wire payload share one source of truth.
|
|
884
|
-
async #buildRequestPacket({ initialMessages, requirements, runId, loopId, currentTurnSeq, provider, gitStatus, telemetryErrors: presetTelemetry, }) {
|
|
886
|
+
async #buildRequestPacket({ initialMessages, requirements, sessionId, runId, loopId, currentTurnSeq, provider, gitStatus, telemetryErrors: presetTelemetry, }) {
|
|
885
887
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
886
888
|
// plurnk.md (grammar/dialects) ONLY — the definition is the hot-path grammar.
|
|
887
889
|
// The scheme catalogue is its own `schemes` section below tools (§schemes-directory),
|
|
@@ -921,6 +923,9 @@ class Engine {
|
|
|
921
923
|
// omitted, section lines still shown). §tokenomics-render-weight-budget
|
|
922
924
|
const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
923
925
|
const budgetReadout = this.#renderBudget(PacketWire.measureLogBudget(log, countTokens), ceiling);
|
|
926
|
+
// Per-scheme tally (§packet) so the model sees which schemes hold content without
|
|
927
|
+
// probing e.g. FIND(known://**) every turn. "" when empty → the section is omitted.
|
|
928
|
+
const catalogSummary = await this.#db.engine_scheme_catalog_summary.all({ session_id: sessionId });
|
|
924
929
|
// The default packet: an ordered list of sections, each addressable state
|
|
925
930
|
// (§packet-construction). `slot` is the prompt-cache boundary; the STATIC
|
|
926
931
|
// sections (definition, tools) lead the system slot so they form the cached
|
|
@@ -937,6 +942,7 @@ class Engine {
|
|
|
937
942
|
{ name: "budget", slot: "user", header: "Plurnk System Budget", content: budgetReadout, tokens: 0 },
|
|
938
943
|
{ name: "errors", slot: "user", header: "Plurnk System Errors", content: PacketWire.renderErrors(telemetryErrors), tokens: 0 },
|
|
939
944
|
{ name: "git", slot: "user", header: "Plurnk System Git Status", content: PacketWire.renderGit(gitStatus), tokens: 0 },
|
|
945
|
+
{ name: "catalog", slot: "user", header: "Plurnk System Catalog", content: PacketWire.renderCatalog(catalogSummary), tokens: 0 },
|
|
940
946
|
{ name: "requirements", slot: "user", header: "Plurnk System Requirements", content: requirementsText, tokens: 0 },
|
|
941
947
|
];
|
|
942
948
|
// Plugin packet control (§packet-construction): trusted schemes rewrite the
|
|
@@ -1752,11 +1758,12 @@ class Engine {
|
|
|
1752
1758
|
return { status: 400, error: "KILL target must be a URL path with a scheme" };
|
|
1753
1759
|
if (schemeName === "log")
|
|
1754
1760
|
return { status: 405, error: "log:/// is append-only; KILL must bounce" };
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1761
|
+
// Process-KILL: any scheme whose handler exposes kill() aborts a live stream — the
|
|
1762
|
+
// exec handler, registered as "exec" + under every runtime tag (sh/node), so a tag-
|
|
1763
|
+
// addressed stream (sh:///l/t/s) routes here, not to deleteEntry. §exec
|
|
1764
|
+
const killable = this.#schemes.get(schemeName);
|
|
1765
|
+
if (killable !== undefined && typeof killable.kill === "function") {
|
|
1766
|
+
return await killable.kill(pathnameFromPath(path), statement.signal, ctx);
|
|
1760
1767
|
}
|
|
1761
1768
|
if (schemeName === "run") {
|
|
1762
1769
|
// terminate — abort any run by address; whoever holds it may end it.
|
|
@@ -1873,8 +1880,9 @@ class Engine {
|
|
|
1873
1880
|
const status = statement.signal;
|
|
1874
1881
|
if (status === null)
|
|
1875
1882
|
return { status: 400 };
|
|
1876
|
-
if (status === 200 || status === 499) {
|
|
1877
|
-
//
|
|
1883
|
+
if (status === 200 || status === 202 || status === 499) {
|
|
1884
|
+
// The broadcast terminals (200 done, 202 parked-async, 499 cancelled) advance
|
|
1885
|
+
// the loop; each carries its body as the loop's terminal message — the deliverable.
|
|
1878
1886
|
const body = statement.body;
|
|
1879
1887
|
const message = body === null ? null : typeof body === "string" ? body : body.raw;
|
|
1880
1888
|
await this.#db.engine_loop_set_status.run({ status, loop_id: loopId, message });
|
|
@@ -1934,15 +1942,13 @@ class Engine {
|
|
|
1934
1942
|
let attrsObj = (result.attrs !== undefined && result.attrs !== null)
|
|
1935
1943
|
? { ...result.attrs }
|
|
1936
1944
|
: {};
|
|
1937
|
-
// EXEC
|
|
1938
|
-
//
|
|
1939
|
-
//
|
|
1940
|
-
//
|
|
1941
|
-
//
|
|
1942
|
-
//
|
|
1943
|
-
//
|
|
1944
|
-
// Runtime comes from statement.signal (EXEC's runtime slot) so it's
|
|
1945
|
-
// resolvable for failed execs too; empty/absent = the default shell.
|
|
1945
|
+
// EXEC stream entry addresses by RUNTIME TAG as authority (§exec): it lives at
|
|
1946
|
+
// <runtime>:///<loop_seq>/<turn_seq>/<sequence> (e.g. sh:///1/1/2) — the runtime tag
|
|
1947
|
+
// is the scheme, the coordinate already unique per statement. The log row's target
|
|
1948
|
+
// points at this same address; its log:/// coordinate shares the trailing
|
|
1949
|
+
// <loop>/<turn>/<seq>, so the model correlates op to stream output. Runtime comes
|
|
1950
|
+
// from statement.signal (EXEC's runtime slot) so it's resolvable for failed execs
|
|
1951
|
+
// too; empty/absent = the default shell.
|
|
1946
1952
|
if (statement.op === "EXEC") {
|
|
1947
1953
|
const seqs = await this.#db.engine_loop_turn_seqs.get({
|
|
1948
1954
|
loop_id: loopId, turn_id: turnId,
|
|
@@ -1950,8 +1956,8 @@ class Engine {
|
|
|
1950
1956
|
if (seqs === undefined)
|
|
1951
1957
|
throw new Error(`Engine.#writeLog: loop_turn_seqs returned no row for loop=${loopId} turn=${turnId}`);
|
|
1952
1958
|
const runtime = (typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
|
|
1953
|
-
const coordPathname = `/${
|
|
1954
|
-
target.scheme =
|
|
1959
|
+
const coordPathname = `/${seqs.loop_seq}/${seqs.turn_seq}/${sequence}`;
|
|
1960
|
+
target.scheme = runtime;
|
|
1955
1961
|
target.pathname = coordPathname;
|
|
1956
1962
|
attrsObj.pathname = coordPathname;
|
|
1957
1963
|
// Mutate the in-memory result.attrs too: the dispatch path
|