@plurnk/plurnk-service 0.49.0 → 0.51.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.
Files changed (35) hide show
  1. package/.env.example +2 -2
  2. package/SPEC.md +15 -3
  3. package/dist/core/Engine.d.ts +1 -0
  4. package/dist/core/Engine.d.ts.map +1 -1
  5. package/dist/core/Engine.js +60 -19
  6. package/dist/core/Engine.js.map +1 -1
  7. package/dist/core/Engine.sql +19 -2
  8. package/dist/core/session-settings.d.ts +5 -0
  9. package/dist/core/session-settings.d.ts.map +1 -1
  10. package/dist/core/session-settings.js +10 -1
  11. package/dist/core/session-settings.js.map +1 -1
  12. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  13. package/dist/schemes/_entry-semantic.js +6 -5
  14. package/dist/schemes/_entry-semantic.js.map +1 -1
  15. package/dist/server/Daemon.d.ts +1 -0
  16. package/dist/server/Daemon.d.ts.map +1 -1
  17. package/dist/server/Daemon.js +23 -5
  18. package/dist/server/Daemon.js.map +1 -1
  19. package/dist/server/MethodRegistry.d.ts +2 -0
  20. package/dist/server/MethodRegistry.d.ts.map +1 -1
  21. package/dist/server/envelope.d.ts.map +1 -1
  22. package/dist/server/envelope.js +9 -0
  23. package/dist/server/envelope.js.map +1 -1
  24. package/dist/server/methods/loop_run.d.ts.map +1 -1
  25. package/dist/server/methods/loop_run.js +7 -1
  26. package/dist/server/methods/loop_run.js.map +1 -1
  27. package/dist/server/methods/providers_list.d.ts.map +1 -1
  28. package/dist/server/methods/providers_list.js +15 -8
  29. package/dist/server/methods/providers_list.js.map +1 -1
  30. package/dist/server/methods/session_create.d.ts.map +1 -1
  31. package/dist/server/methods/session_create.js +12 -4
  32. package/dist/server/methods/session_create.js.map +1 -1
  33. package/migrations/0000-00-00.01_schema.sql +6 -0
  34. package/package.json +7 -5
  35. package/requirements.md +3 -3
package/.env.example CHANGED
@@ -124,8 +124,8 @@ PLURNK_MANIFEST_ITEMS=-1
124
124
  # Prompt-preview cap: the loop's prompt renders in user.prompt every turn, and a fat prompt
125
125
  # replays each turn (bloat). Show the first N CHARS of the body + a pointer to the full
126
126
  # prompt (always READable at its plurnk://prompt/<loop>/<seq> entry — nothing is lost).
127
- # -1 = no cap (render the full prompt every turn). Default 512.
128
- PLURNK_PROMPT_PREVIEW_CHARS=512
127
+ # -1 = no cap (render the full prompt every turn).
128
+ PLURNK_PROMPT_PREVIEW_CHARS=1024
129
129
 
130
130
  # Session-tier ceiling on CONCURRENT active runs (a run with a non-terminal loop)
131
131
  # — the fork-bomb / destabilization brake. -1 = no cap (default); only concurrency
package/SPEC.md CHANGED
@@ -152,7 +152,7 @@ Server posture: this package is the runtime. User-facing CLI lives in `plurnk` a
152
152
 
153
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.
154
154
 
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 (`expanded`). 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*.
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 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 (`expanded`). A run owns **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*.
156
156
 
157
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}
158
158
 
@@ -235,7 +235,9 @@ The engine unions the declared tags of the active plugin families (schemes, exec
235
235
 
236
236
  **The `@plurnk/` namespace is reserved.** A package may declare an `@plurnk/` tag only if it is itself `@plurnk/`-scoped (npm enforces scope ownership at publish); otherwise it fails hard. {§attribution-plurnk-namespace-reserved}
237
237
 
238
- Deferred (#249): grounding the value in real per-turn value flow rather than the active-plugin placeholder, token-weighting, entry-level attribution, and the `client` id. Native surfacing of the field in each framework's `discover()` supersedes the service-side manifest read and extends collection to mimetype + provider plugins.
238
+ **The session's `client` id rides the same wire.** A frontend self-identifies (e.g. `plurnk.nvim/1.4.0`) at `session.create({ settings: { client } })`; the engine forwards it per turn on `generate({ client })`, which only the `plurnk` provider emits (as `Plurnk-Client`). Session-stable and self-reported distinct from attribution's install-grounded tags and omitted when unset. {§client-telemetry}
239
+
240
+ Deferred (#249): grounding the attribution value in real per-turn value flow rather than the active-plugin placeholder, token-weighting, and entry-level attribution. Native surfacing of the field in each framework's `discover()` supersedes the service-side manifest read and extends collection to mimetype + provider plugins.
239
241
 
240
242
  ### §provider-instantiation Provider instantiation
241
243
 
@@ -259,6 +261,16 @@ First path segment = provider plugin; rest = provider's own model id.
259
261
 
260
262
  Author-facing contract: [plurnk-schemes#1](https://github.com/plurnk/plurnk-schemes/issues/1). Below: what plurnk-service exposes to schemes and orchestrates over them.
261
263
 
264
+ ### §scheme-address Address resolution (RFC 3986)
265
+
266
+ Every op targets a URI; the entry key is `(scope, scheme, pathname)`. The URI parses per RFC 3986 (`scheme://[authority]/path`) and maps to that key by one rule — no per-scheme carve-out:
267
+
268
+ - A **registered** scheme is a plurnk namespace: its authority is a leading path segment, folded into the pathname (`Engine.#extractTarget` → `foldAuthorityIntoPath`). So `known://x`, `known:///x`, and pathname `/x` are the same entry — the authority is never a host, and the two-slash and three-slash forms are not distinct resources. {§scheme-address-namespace-fold}
269
+ - A **foreign** scheme (unregistered — `http`/`https`) is a real web host: its authority stays in `hostname`, never folded.
270
+ - `file` persists `scheme = NULL`; a relative path resolves against the workspace root to the namespace-absolute `/rel` key (RFC §5 reference resolution), and a path escaping the root is 403 (§membership).
271
+
272
+ Storage keys on the resolved `(scheme, pathname)` **verbatim** — the leading slash is the namespace origin, not a filesystem absolute, and is never re-normalized at the storage boundary.
273
+
262
274
  ### §scheme-manifest Manifest
263
275
 
264
276
  Per author contract. Each scheme declares a `static manifest: SchemeManifest` with `name`, `channels`, `defaultChannel`, `category`, `scope`, `writableBy`, `volatile`, `modelVisible`, optional `flags`. {§scheme-manifest-manifest} Identity match enforced at plugin load: `manifest.name` must equal `package.json#plurnk.name`.
@@ -1228,7 +1240,7 @@ Strike accounting, cycle detection, sudden-death thresholds, and no-ops bookkeep
1228
1240
 
1229
1241
  ### §tools user.tools — the capability sheet
1230
1242
 
1231
- A `# Plurnk System Tools` section renders **above** `# Plurnk System Requirements` a hook-populated list of the capabilities enabled this session, so the model sees what it can *do* before the rules it must follow. Each enabled capability contributes one line via `Engine.#collectTools`; the whole section is omitted when nothing is enabled. {§tools-capability-sheet}
1243
+ The tools capability lines render **titleless**, directly under the `definition` (plurnk.md) section the examples flow on from plurnk.md with no separate header and **above** `# Plurnk System Requirements`, so the model sees what it can *do* before the rules it must follow. Each enabled capability contributes one line via `Engine.#collectTools`; the section is omitted when nothing is enabled. {§tools-capability-sheet}
1232
1244
 
1233
1245
  **Contributors: the wired executor tags.** Each available executor tag *with an example* contributes ONE line — its canonical usage — plus a `(docs: plurnk://docs/<tag>.md)` pointer when its package ships documentation, via the shared `teachingLine` (identical shape to the scheme directory, §schemes). A tag with no example contributes nothing; `PLURNK_DOCS_EXCLUDE` drops a named tag's line + doc. The boot `ExecutorRegistry` probes availability per tag, retiring the model's blind `<<EXEC[sh]…`.
1234
1246
 
@@ -74,6 +74,7 @@ export default class Engine {
74
74
  promptTokens: number;
75
75
  completionTokens: number;
76
76
  costPico: number;
77
+ contextTokens: number;
77
78
  }>;
78
79
  runLoop({ provider, messages, requirements, sessionId, runId, loopId, maxTurns, maxStrikes, minCycles, maxCyclePeriod, origin, signal, onDispatch, }: {
79
80
  provider: Provider;
@@ -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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"}
@@ -312,6 +312,9 @@ class Engine {
312
312
  promptTokens: row?.prompt ?? 0,
313
313
  completionTokens: row?.completion ?? 0,
314
314
  costPico: row?.cost_pico ?? 0,
315
+ // #263 — the last turn's prompt tokens = current window occupancy (gauge numerator), NOT the
316
+ // summed promptTokens above, which overcounts a context that grows across turns.
317
+ contextTokens: row?.context ?? 0,
315
318
  };
316
319
  }
317
320
  #pushTelemetry(sessionId, loopId, event) {
@@ -495,6 +498,13 @@ class Engine {
495
498
  // writes consume low indices; model ops continue from there.
496
499
  const seqRow = await this.#db.engine_next_turn_sequence.get({ loop_id: loopId });
497
500
  const seq = seqRow.next;
501
+ // #269 — loops.sequence is the loop's ordinal WITHIN the run. Turn-0 foists that belong to the
502
+ // RUN (manifest preview, AGENTS, operator docs) gate on the run's FIRST loop, not every loop's
503
+ // first turn; per-loop foists (the prompt, @file) still fire each loop. Read once, turn-1 only.
504
+ const loopRow = seq === 1
505
+ ? await this.#db.engine_get_loop_prompt.get({ loop_id: loopId })
506
+ : undefined;
507
+ const runFirstLoop = (loopRow?.sequence ?? 0) === 1;
498
508
  const openRow = await this.#db.engine_open_turn.get({
499
509
  loop_id: loopId, sequence: seq,
500
510
  });
@@ -522,7 +532,8 @@ class Engine {
522
532
  // #231 — env docs (PLURNK_MD_*) UNION the session's client docs; foist a READ of
523
533
  // each materialized plurnk:///<alias>.md (loop_run materialized the same set).
524
534
  const { mdDocs } = await SessionSettings.read(this.#db, sessionId);
525
- for (const doc of await SessionSettings.resolveDocs(mdDocs)) {
535
+ // #269 operator docs are run-once; foist them only on the run's first loop.
536
+ for (const doc of runFirstLoop ? await SessionSettings.resolveDocs(mdDocs) : []) {
526
537
  const docTarget = {
527
538
  kind: "url", raw: `plurnk:///${doc.entryName}`, scheme: "plurnk",
528
539
  username: null, password: null, hostname: null, port: null,
@@ -538,7 +549,7 @@ class Engine {
538
549
  });
539
550
  nextActionIndex++;
540
551
  }
541
- const promptRow = await this.#db.engine_get_loop_prompt.get({ loop_id: loopId });
552
+ const promptRow = loopRow; // #269 — already read above (per-loop; fires every loop's turn 1)
542
553
  if (promptRow !== undefined && typeof promptRow.prompt === "string" && promptRow.prompt.length > 0) {
543
554
  const promptPath = {
544
555
  kind: "url", raw: `plurnk://prompt/${loopId}/${seq}`,
@@ -604,7 +615,7 @@ class Engine {
604
615
  // #231 — a session's client-chosen manifestItems REPLACES the env default outright.
605
616
  const { manifestItems: sessionMI, autoReadAgents } = await SessionSettings.read(this.#db, sessionId);
606
617
  const manifestItems = sessionMI !== null ? normalizeManifestItems(sessionMI) : readManifestItems();
607
- if (manifestItems !== null) {
618
+ if (manifestItems !== null && runFirstLoop) { // #269 — manifest preview is run-once
608
619
  const manifestRead = {
609
620
  op: "READ", suffix: "", signal: null, lineMarker: null,
610
621
  target: {
@@ -621,20 +632,23 @@ class Engine {
621
632
  });
622
633
  nextActionIndex++;
623
634
  }
624
- // #250 — auto-READ the project's AGENTS.md scratchpad into THIS first model turn
625
- // when the session opted in AND it's a member. The client picks it (gitignored by
626
- // convention not a git member); the engine READs it here so its body is part of
627
- // turn-1's log — a normal file:/// member READ (the model sees only the READ; it
628
- // stays read-write, so the model edits the scratchpad back as it evolves).
629
- if (autoReadAgents === true) {
630
- const agentsMember = await this.#db.crud_get_member_sig.get({ session_id: sessionId, scheme: null, pathname: "/AGENTS.md" });
631
- if (agentsMember !== undefined) {
635
+ // #268 — auto-READ the configured AGENTS file(s) into THIS first model turn. Env-driven
636
+ // (PLURNK_AGENTS_AUTO / PLURNK_AGENTS_FILES), overridable per-session by autoReadAgents; the
637
+ // matching files are auto-PICKed into membership at session setup (envelope). The model sees
638
+ // only the READ — a normal file:/// member READ, read-write so it edits the scratchpad back.
639
+ const { auto: agentsAuto, files: agentsFiles } = SessionSettings.resolveAgentsAutoload(autoReadAgents);
640
+ if (agentsAuto && runFirstLoop) { // #269 — run-once, the run's first loop
641
+ for (const file of agentsFiles) {
642
+ const pathname = file.startsWith("/") ? file : `/${file}`;
643
+ const member = await this.#db.crud_get_member_sig.get({ session_id: sessionId, scheme: null, pathname });
644
+ if (member === undefined)
645
+ continue; // absent / non-git session → not a member → skip
632
646
  const agentsRead = {
633
647
  op: "READ", suffix: "", signal: null, lineMarker: null,
634
648
  target: {
635
- kind: "url", raw: "file:///AGENTS.md", scheme: "file",
649
+ kind: "url", raw: `file://${pathname}`, scheme: "file",
636
650
  username: null, password: null, hostname: null, port: null,
637
- pathname: "/AGENTS.md", params: {}, fragment: null,
651
+ pathname, params: {}, fragment: null,
638
652
  },
639
653
  body: null, position: { line: 1, column: 1 },
640
654
  };
@@ -645,6 +659,27 @@ class Engine {
645
659
  nextActionIndex++;
646
660
  }
647
661
  }
662
+ // #260 — foist a turn-0 READ of each client-passed @file path so its content sits in front
663
+ // of the model. Daemon owns the workspace → a normal file:/// member READ; a missing or
664
+ // non-member path surfaces its READ outcome (4xx) in the log, visible to the model.
665
+ const openPathsRow = await this.#db.engine_get_loop_open_paths.get({ loop_id: loopId });
666
+ for (const raw of JSON.parse(openPathsRow?.open_paths ?? "[]")) {
667
+ const pathname = raw.startsWith("/") ? raw : `/${raw}`;
668
+ const fileRead = {
669
+ op: "READ", suffix: "", signal: null, lineMarker: null,
670
+ target: {
671
+ kind: "url", raw: `file://${pathname}`, scheme: "file",
672
+ username: null, password: null, hostname: null, port: null,
673
+ pathname, params: {}, fragment: null,
674
+ },
675
+ body: null, position: { line: 1, column: 1 },
676
+ };
677
+ await this.dispatch({
678
+ statement: fileRead, sessionId, runId, loopId, turnId,
679
+ sequence: nextActionIndex, origin: "plurnk", onDispatch,
680
+ });
681
+ nextActionIndex++;
682
+ }
648
683
  }
649
684
  // §env-delta — pre-seed environment deltas (changes since this run last
650
685
  // reconciled) as system EDIT rows, before the packet composes; advance
@@ -692,8 +727,13 @@ class Engine {
692
727
  // #249 — plugin attribution tags onto the per-turn generate() wire. Value is the
693
728
  // active-plugin set (placeholder); real per-turn grounding is deferred.
694
729
  const attributions = [...new Set([...this.#schemes.attributions(), ...(this.#executors?.attributions() ?? [])])].toSorted();
730
+ // #249 — tag the loop (the activity) with its active plugins' attribution tags, write-once.
731
+ if (attributions.length > 0)
732
+ await this.#db.engine_tag_loop_attributions.run({ loop_id: loopId, attributions: JSON.stringify(attributions) });
733
+ // #249 — session-stable frontend id, forwarded as Plurnk-Client by the plurnk provider only.
734
+ const { client } = await SessionSettings.read(this.#db, sessionId);
695
735
  try {
696
- 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
736
+ response = await provider.generate({ messages: modelMessages, runId: String(runId), signal, grammar: await this.#grammarConstraint(), maxTokens, attributions: attributions.length > 0 ? attributions : undefined, client: client ?? undefined }); // §provider-surface-generate §provider-guarantees-single-call §provider-guarantees-signal-wired §attribution-plurnk-namespace-reserved §client-telemetry
697
737
  }
698
738
  catch (err) {
699
739
  // Every provider error surfaces as telemetry (the client/model sees the cause). #256:
@@ -938,7 +978,7 @@ class Engine {
938
978
  const inject = await readPacketInject(); // #240 — operator section, per-turn, fail-hard on a broken path
939
979
  const defaults = [
940
980
  { name: "definition", slot: "system", header: null, content: system_definition, tokens: 0 },
941
- { name: "tools", slot: "system", header: "Plurnk System Tools", content: tools.join("\n"), tokens: 0 },
981
+ { name: "tools", slot: "system", header: null, content: tools.join("\n"), tokens: 0 }, // titleless — the examples flow on from plurnk.md (definition) directly above
942
982
  { name: "schemes", slot: "system", header: "Plurnk System Schemes", content: this.#schemes.teach(), tokens: 0 },
943
983
  ...(inject !== null ? [{ name: "inject", slot: "system", header: "Plurnk Operator Notes", content: inject, tokens: 0 }] : []),
944
984
  { name: "log", slot: "system", header: "Plurnk System Log", content: PacketWire.renderLog(log), tokens: 0 },
@@ -2027,10 +2067,11 @@ class Engine {
2027
2067
  if (path.kind === "local")
2028
2068
  return { scheme: null, username: null, password: null, hostname: null, port: null, pathname: decodePathParens(path.raw), params: null, fragment: null }; // #239 item 4
2029
2069
  const scheme = path.scheme === "file" ? null : path.scheme;
2030
- // plurnk uses its authority as a namespace — fold it into the canonical pathname so the
2031
- // log keys identically to the entry (/prompt/<loop>, /docs/x.md). A web host (http://) is
2032
- // NOT a namespace: keep it in hostname.
2033
- const foldNs = scheme === "plurnk";
2070
+ // Every registered (plurnk-namespace) scheme uses its authority as a namespace segment — fold
2071
+ // it into the canonical pathname so known://x ≡ known:///x ≡ /x and the log keys identically to
2072
+ // the entry (/prompt/<loop>, /docs/x.md). A foreign web host (http://, unregistered) is NOT a
2073
+ // namespace: keep it in hostname.
2074
+ const foldNs = scheme !== null && this.#schemes.has(scheme);
2034
2075
  return {
2035
2076
  scheme, username: path.username, password: path.password,
2036
2077
  hostname: foldNs ? null : path.hostname, port: path.port,