@plurnk/plurnk-service 0.42.0 → 0.44.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 +21 -7
- package/SPEC.md +34 -28
- package/bin/plurnk-service.ts +50 -7
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +2 -1
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +89 -64
- package/dist/core/Engine.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 -22
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/packet-wire.d.ts +30 -31
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +81 -80
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +3 -0
- package/dist/core/plurnk-uri.d.ts.map +1 -0
- package/dist/core/plurnk-uri.js +23 -0
- package/dist/core/plurnk-uri.js.map +1 -0
- package/dist/core/scheme-types.d.ts +4 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/schemes/Exec.d.ts +0 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +2 -1
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts +0 -1
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +2 -1
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.d.ts +0 -1
- package/dist/schemes/Known.d.ts.map +1 -1
- package/dist/schemes/Known.js +2 -1
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Log.d.ts +0 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +2 -1
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Plurnk.d.ts +0 -1
- package/dist/schemes/Plurnk.d.ts.map +1 -1
- package/dist/schemes/Plurnk.js +9 -4
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Run.d.ts +0 -1
- package/dist/schemes/Run.d.ts.map +1 -1
- package/dist/schemes/Run.js +2 -1
- package/dist/schemes/Run.js.map +1 -1
- package/dist/schemes/Unknown.d.ts +0 -1
- package/dist/schemes/Unknown.d.ts.map +1 -1
- package/dist/schemes/Unknown.js +2 -1
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +2 -1
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +6 -1
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +3 -1
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/clientTurn.d.ts.map +1 -1
- package/dist/server/clientTurn.js +1 -2
- package/dist/server/clientTurn.js.map +1 -1
- package/dist/server/envelope.d.ts +1 -0
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +9 -0
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +6 -2
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/session_rename.d.ts +5 -0
- package/dist/server/methods/session_rename.d.ts.map +1 -0
- package/dist/server/methods/session_rename.js +32 -0
- package/dist/server/methods/session_rename.js.map +1 -0
- package/migrations/0000-00-00.01_schema.sql +3 -5
- package/package.json +10 -35
- package/requirements.md +3 -8
package/dist/core/Engine.js
CHANGED
|
@@ -4,6 +4,7 @@ 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 { foldAuthorityIntoPath, renderAddress } from "./plurnk-uri.js";
|
|
7
8
|
import GitState from "./git-state.js";
|
|
8
9
|
import Fork from "./fork.js";
|
|
9
10
|
import RunCap from "./run-cap.js";
|
|
@@ -538,10 +539,10 @@ class Engine {
|
|
|
538
539
|
const promptRow = await this.#db.engine_get_loop_prompt.get({ loop_id: loopId });
|
|
539
540
|
if (promptRow !== undefined && typeof promptRow.prompt === "string" && promptRow.prompt.length > 0) {
|
|
540
541
|
const promptPath = {
|
|
541
|
-
kind: "url", raw: `plurnk
|
|
542
|
+
kind: "url", raw: `plurnk://prompt/${loopId}/${seq}`,
|
|
542
543
|
scheme: "plurnk", username: null, password: null,
|
|
543
|
-
hostname:
|
|
544
|
-
pathname:
|
|
544
|
+
hostname: "prompt", port: null,
|
|
545
|
+
pathname: `/${loopId}/${seq}`, params: {}, fragment: null,
|
|
545
546
|
};
|
|
546
547
|
const promptStmt = {
|
|
547
548
|
op: "EDIT", suffix: "", signal: null,
|
|
@@ -554,9 +555,9 @@ class Engine {
|
|
|
554
555
|
sequence: nextActionIndex, origin: "plurnk",
|
|
555
556
|
onDispatch: (id) => { promptLogId = id; onDispatch?.(id); },
|
|
556
557
|
});
|
|
557
|
-
// §prompt-fold (User Note 6): the prompt EDIT duplicates
|
|
558
|
-
//
|
|
559
|
-
//
|
|
558
|
+
// §prompt-fold (User Note 6): the prompt EDIT duplicates the
|
|
559
|
+
// prompt section, so fold it — logged for forensics, collapsed
|
|
560
|
+
// in the model's log, re-OPENable.
|
|
560
561
|
if (promptLogId !== undefined)
|
|
561
562
|
await this.#db.engine_fold_log_entry.run({ id: promptLogId });
|
|
562
563
|
nextActionIndex++;
|
|
@@ -659,7 +660,7 @@ class Engine {
|
|
|
659
660
|
// grammar can't bound degeneration *inside* a statement body — this caps the
|
|
660
661
|
// decode at the free window so a runaway can't reach the context wall.
|
|
661
662
|
const genCeiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling); // provider.contextSize, the immutable identity, read by the budget — §provider-surface-identity
|
|
662
|
-
const maxTokens = genCeiling === null ? undefined : Math.max(1, genCeiling - requestPacket.
|
|
663
|
+
const maxTokens = genCeiling === null ? undefined : Math.max(1, genCeiling - requestPacket.tokens);
|
|
663
664
|
const response = await provider.generate({ messages: modelMessages, runId: String(runId), signal, grammar: await this.#grammarConstraint(), maxTokens }); // §provider-surface-generate §provider-guarantees-single-call §provider-guarantees-signal-wired
|
|
664
665
|
// Engine splits wire-level response: emission (content, reasoning,
|
|
665
666
|
// parsed ops) → packet.assistant per Packet.json §assistant;
|
|
@@ -849,18 +850,19 @@ class Engine {
|
|
|
849
850
|
// the stored packet and the wire payload share one source of truth.
|
|
850
851
|
async #buildRequestPacket({ initialMessages, requirements, runId, loopId, currentTurnSeq, provider, gitStatus, telemetryErrors: presetTelemetry, }) {
|
|
851
852
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
852
|
-
// plurnk.md (grammar/dialects
|
|
853
|
-
// scheme
|
|
854
|
-
//
|
|
855
|
-
const system_definition =
|
|
856
|
-
//
|
|
853
|
+
// plurnk.md (grammar/dialects only — grammar 0.49+ is scheme-agnostic). The
|
|
854
|
+
// scheme catalogue is now its OWN section (`schemes`, below tools), so the
|
|
855
|
+
// definition is just the operator's system prompt — tools sits right below it.
|
|
856
|
+
const system_definition = byRole("system");
|
|
857
|
+
// the prompt section sources from the loop's most recent prompt entry first
|
|
857
858
|
// (plurnk:///prompt/<loop_id>/<N> for the highest N written to date).
|
|
858
859
|
// This is what inject + the turn-1 foist write into. Falls back to
|
|
859
860
|
// the runLoop caller's messages.user for tests that bypass the
|
|
860
861
|
// foist mechanism entirely.
|
|
861
|
-
const latestPromptRow = await this.#db.drain_get_latest_prompt_body_for_loop.get({ pattern:
|
|
862
|
+
const latestPromptRow = await this.#db.drain_get_latest_prompt_body_for_loop.get({ pattern: `/prompt/${loopId}/%` });
|
|
863
|
+
const promptCap = Number.parseInt(process.env.PLURNK_PROMPT_PREVIEW_CHARS ?? "", 10);
|
|
862
864
|
const prompt = (latestPromptRow !== undefined && typeof latestPromptRow.content === "string" && latestPromptRow.content.length > 0)
|
|
863
|
-
? latestPromptRow.content
|
|
865
|
+
? PacketWire.previewPrompt(latestPromptRow.content, renderAddress("plurnk", latestPromptRow.pathname), Number.isInteger(promptCap) ? promptCap : -1)
|
|
864
866
|
: byRole("user");
|
|
865
867
|
// Requirements is engine-sourced, NOT threaded from callers — that threading is
|
|
866
868
|
// exactly how it went missing (callers read the sysprompt but never the
|
|
@@ -874,65 +876,83 @@ class Engine {
|
|
|
874
876
|
const requirementsText = `Syntax: <<OPsuffix[signal]?(target)?<Line/Result>?:body?:OPsuffix\n\n${planDirective}${baseRequirements}`;
|
|
875
877
|
const log = await this.#buildLog(runId);
|
|
876
878
|
const telemetryErrors = presetTelemetry ?? await this.#buildTelemetryErrors(loopId, currentTurnSeq);
|
|
877
|
-
// Per-section render-cost subtotals via provider's tokenizer.
|
|
878
|
-
// Engine approximates each section by tokenizing its serialized
|
|
879
|
-
// form — wire-payload tokens may differ slightly because chat-
|
|
880
|
-
// template scaffolding adds bytes, but the subtotal tracks "what
|
|
881
|
-
// the model has to process" closely enough for budget diagnostics.
|
|
882
879
|
const countTokens = (t) => provider.countTokens(t); // §provider-surface-counttokens
|
|
883
|
-
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
//
|
|
887
|
-
//
|
|
888
|
-
//
|
|
889
|
-
//
|
|
880
|
+
const tools = this.#collectTools();
|
|
881
|
+
// Budget readout (SPEC.md §tokenomics). Two-pass: render the budget from
|
|
882
|
+
// the structured log's subtotals with a {{tokensFree}} placeholder, build
|
|
883
|
+
// the section list, measure the assembled total, resolve free, substitute.
|
|
884
|
+
// Subtotals come from the real log render — meta and fences included — not
|
|
885
|
+
// a serialized approximation. ceiling is the provider's window ×
|
|
886
|
+
// PLURNK_BUDGET_CEILING (null when no window is reported → headline
|
|
887
|
+
// omitted, section lines still shown). §tokenomics-render-weight-budget
|
|
890
888
|
const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
889
|
+
const budgetReadout = this.#renderBudget(PacketWire.measureLogBudget(log, countTokens), ceiling);
|
|
890
|
+
// The default packet: an ordered list of sections, each addressable state
|
|
891
|
+
// (§packet-construction). `slot` is the prompt-cache boundary; the STATIC
|
|
892
|
+
// sections (definition, tools, schemes) lead the system slot so they form the
|
|
893
|
+
// cached prefix, with the dynamic log after. In the user slot, requirements renders
|
|
894
|
+
// last (the contract closest to the assistant turn); budget/errors/git are
|
|
895
|
+
// peer sections (unbundled). The budget section carries its {{tokensFree}}
|
|
896
|
+
// placeholders here; they resolve below once the assembled total is known.
|
|
897
|
+
const defaults = [
|
|
898
|
+
{ name: "definition", slot: "system", header: null, content: system_definition, tokens: 0 },
|
|
899
|
+
{ name: "tools", slot: "system", header: "Plurnk System Tools", content: tools.join("\n"), tokens: 0 },
|
|
900
|
+
{ name: "schemes", slot: "system", header: "Plurnk System Schemes", content: this.#schemes.teach(), tokens: 0 },
|
|
901
|
+
{ name: "log", slot: "system", header: "Plurnk System Log", content: PacketWire.renderLog(log), tokens: 0 },
|
|
902
|
+
{ name: "prompt", slot: "user", header: "Plurnk System User Prompt", content: prompt, tokens: 0 },
|
|
903
|
+
{ name: "budget", slot: "user", header: "Plurnk System Budget", content: budgetReadout, tokens: 0 },
|
|
904
|
+
{ name: "errors", slot: "user", header: "Plurnk System Errors", content: PacketWire.renderErrors(telemetryErrors), tokens: 0 },
|
|
905
|
+
{ name: "git", slot: "user", header: "Plurnk System Git Status", content: PacketWire.renderGit(gitStatus), tokens: 0 },
|
|
906
|
+
{ name: "requirements", slot: "user", header: "Plurnk System Requirements", content: requirementsText, tokens: 0 },
|
|
907
|
+
];
|
|
908
|
+
// Plugin packet control (§packet-construction): trusted schemes rewrite the
|
|
909
|
+
// default list — add, remove, reorder — in-process, before measurement.
|
|
910
|
+
const sections = await this.#schemes.transformSections(defaults);
|
|
911
|
+
// Pass 1: measure the assembled total with the placeholder budget in
|
|
912
|
+
// place, resolve free/percent, substitute into the budget section.
|
|
913
|
+
const total = countTokens(PacketWire.renderSlot(sections, "system")) + countTokens(PacketWire.renderSlot(sections, "user"));
|
|
898
914
|
const tokensFree = ceiling === null ? null : Math.max(0, ceiling - total); // free floors at 0 on overshoot — §tokenomics-over-budget-floor
|
|
899
915
|
const percent = ceiling === null ? null : Math.round((total / ceiling) * 100); // usage as % of the ceiling — §tokenomics-context-percent
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
916
|
+
if (tokensFree !== null) {
|
|
917
|
+
const budgetSec = sections.find((s) => s.name === "budget"); // a plugin may have removed it
|
|
918
|
+
if (budgetSec) {
|
|
919
|
+
budgetSec.content = budgetSec.content
|
|
920
|
+
.replace(TOKEN_USAGE_PLACEHOLDER, String(total))
|
|
921
|
+
.replace(TOKEN_PERCENT_PLACEHOLDER, percent === 0 && total > 0 ? "<1" : String(percent))
|
|
922
|
+
.replace(TOKENS_FREE_PLACEHOLDER, String(tokensFree));
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Pass 2: per-section render-weight + the assembled packet total (post
|
|
926
|
+
// substitution — the placeholder/number length delta is negligible).
|
|
927
|
+
for (const s of sections)
|
|
928
|
+
s.tokens = countTokens(PacketWire.renderSection(s));
|
|
929
|
+
const packetTokens = countTokens(PacketWire.renderSlot(sections, "system")) + countTokens(PacketWire.renderSlot(sections, "user"));
|
|
930
|
+
return { tokens: packetTokens, sections, telemetryErrors };
|
|
911
931
|
}
|
|
912
932
|
// Budget readout body, rendered into the `# Plurnk System Budget` section.
|
|
913
933
|
// Headline `ceiling/free` only when a ceiling exists; section lines for the
|
|
914
934
|
// curatable index/log weight the model can FOLD back. tokensFree is a
|
|
915
935
|
// placeholder here — buildSystem substitutes it after measuring the packet.
|
|
916
|
-
#renderBudget(
|
|
936
|
+
#renderBudget(log, ceiling) {
|
|
917
937
|
const lines = [];
|
|
918
938
|
if (ceiling !== null)
|
|
919
939
|
lines.push(`ceiling ${ceiling} · usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · free ${TOKENS_FREE_PLACEHOLDER}`);
|
|
920
|
-
if (
|
|
921
|
-
lines.push(`Log entries: ${
|
|
940
|
+
if (log.entries > 0) {
|
|
941
|
+
lines.push(`Log entries: ${log.entries} entries, ${log.tokens} tokens`);
|
|
922
942
|
// Per-turn weight — the grinder's rollback unit, oldest first: the
|
|
923
943
|
// model sees what's first to go (§tokenomics {§tokenomics-turn-totals}).
|
|
924
|
-
if (
|
|
944
|
+
if (log.byTurn.length > 0) {
|
|
925
945
|
lines.push("Turns:", "| turn | tokens |", "|---|--:|");
|
|
926
|
-
for (const t of
|
|
946
|
+
for (const t of log.byTurn)
|
|
927
947
|
lines.push(`| ${t.turn} | ${t.tokens} |`);
|
|
928
948
|
}
|
|
929
949
|
// The heaviest individual log items — the FOLD targets behind the weight
|
|
930
950
|
// (§tokenomics {§tokenomics-largest-entries}). "items", not "entries": the readout
|
|
931
951
|
// lists log:/// rows (log items), distinct from catalog entries (plurnk.md: "EDIT
|
|
932
952
|
// is only for entries. Do not attempt to edit log items.").
|
|
933
|
-
if (
|
|
953
|
+
if (log.largest.length > 0) {
|
|
934
954
|
lines.push("Heaviest items:", "| item | tokens |", "|---|--:|");
|
|
935
|
-
for (const e of
|
|
955
|
+
for (const e of log.largest)
|
|
936
956
|
lines.push(`| ${e.path} | ${e.tokens} |`);
|
|
937
957
|
}
|
|
938
958
|
}
|
|
@@ -958,16 +978,16 @@ class Engine {
|
|
|
958
978
|
const entry = this.#executors.entry(tag);
|
|
959
979
|
if (entry?.example)
|
|
960
980
|
tools.push(`* ${entry.example}`);
|
|
961
|
-
// #note12 — link the executor's fuller doc (materialized at plurnk:///docs/<tag
|
|
981
|
+
// #note12 — link the executor's fuller doc (materialized at plurnk:///docs/<tag>.md);
|
|
962
982
|
// its token cost rides that manifest entry, so no inline recount here.
|
|
963
983
|
if (entry?.documentation)
|
|
964
|
-
tools.push(`* docs for ${tag}: plurnk
|
|
984
|
+
tools.push(`* docs for ${tag}: plurnk://docs/${tag}.md`);
|
|
965
985
|
}
|
|
966
986
|
}
|
|
967
987
|
return tools;
|
|
968
988
|
}
|
|
969
989
|
// #note12 — the daughter-provided reference docs (schemes' + execs' `documentation`),
|
|
970
|
-
// materialized at plurnk:///docs/<name
|
|
990
|
+
// materialized at plurnk:///docs/<name>.md by loop_run (like operator docs) so the
|
|
971
991
|
// catalogue's doc-links READ and the manifest carries each doc's token cost.
|
|
972
992
|
docEntries() {
|
|
973
993
|
const out = this.#schemes.docs();
|
|
@@ -988,7 +1008,7 @@ class Engine {
|
|
|
988
1008
|
// §grinder-overflow-only — fires only on actual overflow, never speculatively
|
|
989
1009
|
async #enforceBudget({ packet, provider, runId, loopId, turnId, sessionId, turnNumber, rebuild }) {
|
|
990
1010
|
const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
991
|
-
const measure = (p) => p.
|
|
1011
|
+
const measure = (p) => p.tokens;
|
|
992
1012
|
if (ceiling === null || measure(packet) <= ceiling)
|
|
993
1013
|
return { packet, fit: true, struck: false };
|
|
994
1014
|
const folded = new Map();
|
|
@@ -1000,7 +1020,7 @@ class Engine {
|
|
|
1000
1020
|
note(le.scheme ?? "log");
|
|
1001
1021
|
if (priorLogs.length > 0)
|
|
1002
1022
|
await this.#db.engine_grinder_fold_prior_turn_logs.run({ loop_id: loopId, turn_id: turnId });
|
|
1003
|
-
const errors = packet.
|
|
1023
|
+
const errors = packet.telemetryErrors;
|
|
1004
1024
|
let current = priorLogs.length > 0 ? await rebuild(errors) : packet;
|
|
1005
1025
|
if (measure(current) <= ceiling) {
|
|
1006
1026
|
this.#emitBudgetOverflow(sessionId, loopId, folded);
|
|
@@ -1037,9 +1057,9 @@ class Engine {
|
|
|
1037
1057
|
#completePacket(requestPacket, assistant, assistantRaw, provider) {
|
|
1038
1058
|
const assistantTokens = provider.countTokens(assistant.content);
|
|
1039
1059
|
return {
|
|
1040
|
-
tokens: requestPacket.
|
|
1041
|
-
|
|
1042
|
-
|
|
1060
|
+
tokens: requestPacket.tokens + assistantTokens,
|
|
1061
|
+
sections: requestPacket.sections,
|
|
1062
|
+
telemetryErrors: requestPacket.telemetryErrors,
|
|
1043
1063
|
assistant,
|
|
1044
1064
|
assistantRaw,
|
|
1045
1065
|
};
|
|
@@ -1074,7 +1094,7 @@ class Engine {
|
|
|
1074
1094
|
});
|
|
1075
1095
|
return [...this.#drainTelemetry(loopId), ...actionFailures];
|
|
1076
1096
|
}
|
|
1077
|
-
// SPEC §packet
|
|
1097
|
+
// SPEC §packet the log section — chronological action-entries for the loop.
|
|
1078
1098
|
// Snapshot is taken at packet build (pre-dispatch this turn), so it
|
|
1079
1099
|
// reflects "what has happened before this turn." Each row carries a
|
|
1080
1100
|
// log:///<loop_seq>/<turn_seq>/<sequence> coordinate the model can READ.
|
|
@@ -1404,8 +1424,8 @@ class Engine {
|
|
|
1404
1424
|
return (row?.n ?? 0) > 0;
|
|
1405
1425
|
}
|
|
1406
1426
|
// Inject a prompt into the run's currently-executing loop. Writes a
|
|
1407
|
-
// plurnk:///prompt/<loop_id>/<next-turn> entry whose body becomes
|
|
1408
|
-
//
|
|
1427
|
+
// plurnk:///prompt/<loop_id>/<next-turn> entry whose body becomes the
|
|
1428
|
+
// prompt section at the next turn boundary. Last-wins: if two
|
|
1409
1429
|
// injects target the same next-turn slot, the second overwrites the
|
|
1410
1430
|
// first.
|
|
1411
1431
|
//
|
|
@@ -1425,7 +1445,7 @@ class Engine {
|
|
|
1425
1445
|
const sessionRow = await this.#db.drain_get_run_session.get({ run_id: runId });
|
|
1426
1446
|
if (sessionRow === undefined)
|
|
1427
1447
|
throw new Error(`Engine.inject: run ${runId} not found`);
|
|
1428
|
-
const pathname =
|
|
1448
|
+
const pathname = `/prompt/${loopId}/${turnSeq}`; // canonical storage form (leading slash), matching the foist via #pathnameOf
|
|
1429
1449
|
const ctx = {
|
|
1430
1450
|
db: this.#db, sessionId: sessionRow.session_id, runId, loopId,
|
|
1431
1451
|
turnId: 0, // no turn open at inject time; entries don't pin turnId
|
|
@@ -1955,9 +1975,14 @@ class Engine {
|
|
|
1955
1975
|
if (path.kind === "local")
|
|
1956
1976
|
return { scheme: null, username: null, password: null, hostname: null, port: null, pathname: decodePathParens(path.raw), params: null, fragment: null }; // #239 item 4
|
|
1957
1977
|
const scheme = path.scheme === "file" ? null : path.scheme;
|
|
1978
|
+
// plurnk uses its authority as a namespace — fold it into the canonical pathname so the
|
|
1979
|
+
// log keys identically to the entry (/prompt/<loop>, /docs/x.md). A web host (http://) is
|
|
1980
|
+
// NOT a namespace: keep it in hostname.
|
|
1981
|
+
const foldNs = scheme === "plurnk";
|
|
1958
1982
|
return {
|
|
1959
1983
|
scheme, username: path.username, password: path.password,
|
|
1960
|
-
hostname: path.hostname, port: path.port,
|
|
1984
|
+
hostname: foldNs ? null : path.hostname, port: path.port,
|
|
1985
|
+
pathname: decodePathParens(foldNs ? foldAuthorityIntoPath(path.hostname, path.pathname) : path.pathname), // #239 item 4
|
|
1961
1986
|
params: JSON.stringify(path.params), fragment: path.fragment,
|
|
1962
1987
|
};
|
|
1963
1988
|
}
|