@plurnk/plurnk-service 1.0.4 → 1.0.6
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.defaults +55 -31
- package/INSTALL.md +3 -3
- package/README.md +4 -4
- package/SPEC.md +205 -193
- package/dist/Paths.js +1 -1
- package/dist/Paths.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +18 -18
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +17 -17
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +11 -11
- package/dist/core/Dispatcher.d.ts +11 -16
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +122 -125
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Engine.d.ts +20 -23
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +213 -210
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +108 -130
- package/dist/core/PacketBuilder.d.ts +10 -11
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +204 -128
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +8 -8
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +10 -10
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +1 -0
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +9 -3
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +4 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +18 -9
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/TelemetryChannel.d.ts +5 -5
- package/dist/core/TelemetryChannel.d.ts.map +1 -1
- package/dist/core/TelemetryChannel.js +9 -9
- package/dist/core/TelemetryChannel.js.map +1 -1
- package/dist/core/caps/CapsResolve.js +2 -2
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +2 -2
- package/dist/core/caps/DbNotifyCaps.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +7 -7
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +2 -2
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +4 -4
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/content-hash.d.ts +2 -0
- package/dist/core/content-hash.d.ts.map +1 -0
- package/dist/core/content-hash.js +6 -0
- package/dist/core/content-hash.js.map +1 -0
- package/dist/core/fork.d.ts +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +31 -26
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +41 -32
- package/dist/core/git-env.d.ts.map +1 -1
- package/dist/core/git-env.js +23 -7
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-iso.d.ts +9 -0
- package/dist/core/git-iso.d.ts.map +1 -0
- package/dist/core/git-iso.js +227 -0
- package/dist/core/git-iso.js.map +1 -0
- package/dist/core/git-membership.d.ts +2 -2
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +70 -50
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts +1 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/git-state.js +21 -11
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +81 -22
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +2 -2
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +19 -12
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/run-ops.sql +26 -26
- package/dist/core/scheme-types.d.ts +6 -11
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/search-gate.js +0 -0
- package/dist/core/search-gate.js.map +1 -1
- package/dist/core/token-ruler.d.ts +2 -0
- package/dist/core/token-ruler.d.ts.map +1 -0
- package/dist/core/token-ruler.js +18 -0
- package/dist/core/token-ruler.js.map +1 -0
- package/dist/core/worker-cap.d.ts +8 -0
- package/dist/core/worker-cap.d.ts.map +1 -0
- package/dist/core/worker-cap.js +20 -0
- package/dist/core/worker-cap.js.map +1 -0
- package/dist/core/{session-settings.d.ts → workspace-settings.d.ts} +5 -5
- package/dist/core/workspace-settings.d.ts.map +1 -0
- package/dist/core/{session-settings.js → workspace-settings.js} +13 -13
- package/dist/core/workspace-settings.js.map +1 -0
- package/dist/digest/Digest.d.ts +3 -3
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +79 -73
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +18 -18
- package/dist/schemes/Exec.d.ts +9 -2
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +121 -68
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +3 -3
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.js +23 -23
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.js +5 -5
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +63 -14
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +49 -8
- package/dist/schemes/Plurnk.js +5 -5
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Skill.js +5 -5
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Unknown.js +5 -5
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/{Run.d.ts → Worker.d.ts} +2 -2
- package/dist/schemes/Worker.d.ts.map +1 -0
- package/dist/schemes/{Run.js → Worker.js} +67 -67
- package/dist/schemes/Worker.js.map +1 -0
- package/dist/schemes/_entry-crud.js +11 -11
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +29 -29
- package/dist/schemes/_entry-find.d.ts +2 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +31 -19
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +9 -9
- package/dist/schemes/_entry-graph.d.ts +2 -2
- package/dist/schemes/_entry-graph.d.ts.map +1 -1
- package/dist/schemes/_entry-graph.js +18 -18
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-graph.sql +8 -8
- package/dist/schemes/_entry-manifest.d.ts +1 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +74 -50
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +4 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +25 -25
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +9 -9
- package/dist/schemes/_entry-semantic.d.ts +2 -1
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +23 -4
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_entry-semantic.sql +18 -4
- package/dist/schemes/_entry-send.d.ts +1 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +6 -6
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/exec-abort.js +1 -1
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/server/Daemon.d.ts +50 -48
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +307 -297
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/client-input.js +20 -20
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +7 -7
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/drain.sql +27 -27
- package/dist/server/envelope.d.ts +20 -20
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +86 -86
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +39 -39
- package/dist/server/logEntry.d.ts +1 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +1 -1
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/loop-model.d.ts +3 -0
- package/dist/server/loop-model.d.ts.map +1 -0
- package/dist/server/loop-model.js +26 -0
- package/dist/server/loop-model.js.map +1 -0
- package/dist/server/loopDocs.d.ts +1 -1
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +6 -6
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/seam-entry-read.sql +2 -2
- package/dist/server/seam-log-read.sql +3 -3
- package/dist/server/seam-loop.sql +3 -3
- package/dist/server/seam-proposal-list.sql +4 -4
- package/dist/server/yolo.js +2 -2
- package/dist/server/yolo.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +21 -6
- package/dist/service.js.map +1 -1
- package/migrations/0000-00-00.01_schema.sql +84 -85
- package/package.json +17 -14
- package/dist/core/TokenGauge.d.ts +0 -16
- package/dist/core/TokenGauge.d.ts.map +0 -1
- package/dist/core/TokenGauge.js +0 -58
- package/dist/core/TokenGauge.js.map +0 -1
- package/dist/core/run-cap.d.ts +0 -8
- package/dist/core/run-cap.d.ts.map +0 -1
- package/dist/core/run-cap.js +0 -20
- package/dist/core/run-cap.js.map +0 -1
- package/dist/core/search-prefetch.d.ts +0 -17
- package/dist/core/search-prefetch.d.ts.map +0 -1
- package/dist/core/search-prefetch.js +0 -164
- package/dist/core/search-prefetch.js.map +0 -1
- package/dist/core/session-settings.d.ts.map +0 -1
- package/dist/core/session-settings.js.map +0 -1
- package/dist/schemes/Run.d.ts.map +0 -1
- package/dist/schemes/Run.js.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { renderAddress, promptLoopPrefix } from "./plurnk-uri.js";
|
|
3
|
-
import {
|
|
3
|
+
import { rulerCount } from "./token-ruler.js";
|
|
4
|
+
import { docsExcludeSet } from "./teaching.js";
|
|
4
5
|
import { Policy } from "@plurnk/plurnk-execs";
|
|
5
|
-
import
|
|
6
|
+
import WorkspaceSettings from "./workspace-settings.js";
|
|
6
7
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
7
8
|
import { readPacketInject, readSystemPolicy, readProjectPolicy } from "./packet-inject.js";
|
|
8
9
|
import { readFile } from "node:fs/promises";
|
|
@@ -19,6 +20,7 @@ import ProviderInstantiate from "./ProviderInstantiate.js";
|
|
|
19
20
|
const TOKENS_FREE_PLACEHOLDER = "{{tokensFree}}";
|
|
20
21
|
const TOKEN_USAGE_PLACEHOLDER = "{{tokenUsage}}";
|
|
21
22
|
const TOKEN_PERCENT_PLACEHOLDER = "{{tokenPercent}}";
|
|
23
|
+
const SYSTEM_CTX_PLACEHOLDER = "{{systemCtx}}"; // #440 — treemap's non-turn overhead = total − Σturns, known only post-assembly
|
|
22
24
|
// §tokenomics-window-partition — the four partition numbers. REQUIRED (fail-hard, the
|
|
23
25
|
// providers-env convention): the ceiling is DERIVED from these, never set directly
|
|
24
26
|
// (PLURNK_BUDGET_CEILING is retired — a settable ceiling let policy contradict physics).
|
|
@@ -58,14 +60,21 @@ class PacketBuilder {
|
|
|
58
60
|
// scopeEnvToAlias resolves PLURNK_SERVICE_*_<alias> over the bare fallback with providers' own
|
|
59
61
|
// battle-tested suffix parser. Cached per alias; the boot-global case falls back to the active
|
|
60
62
|
// alias when a provider carries no side-table entry (a test Mock).
|
|
61
|
-
static #KNOBS = ["
|
|
63
|
+
static #KNOBS = ["PLURNK_SERVICE_CONTEXT_WINDOW", "PLURNK_SERVICE_REASONING", "PLURNK_SERVICE_COMPLETION", "PLURNK_SERVICE_SAFETY"];
|
|
62
64
|
#partitions = new Map();
|
|
63
65
|
#resolvePartition(alias) {
|
|
66
|
+
// #472 hard shed (OpenAI lexicon ruling) — the retired llama.cpp-speak names fail LOUD at the
|
|
67
|
+
// read boundary; a stale operator .env must never silently lose its ceiling to a rename.
|
|
68
|
+
for (const k of Object.keys(process.env)) {
|
|
69
|
+
const m = /^PLURNK_SERVICE_(CTX|ASSISTANT)(_.*)?$/.exec(k);
|
|
70
|
+
if (m !== null)
|
|
71
|
+
throw new Error(`${k} is retired (#472): the knob is PLURNK_SERVICE_${m[1] === "CTX" ? "CONTEXT_WINDOW" : "COMPLETION"}${m[2] ?? ""}.`);
|
|
72
|
+
}
|
|
64
73
|
const view = scopeEnvToAlias(process.env, alias, _a.#KNOBS);
|
|
65
74
|
return {
|
|
66
|
-
|
|
75
|
+
contextWindow: readPartitionIntFrom(view, "PLURNK_SERVICE_CONTEXT_WINDOW", 1),
|
|
67
76
|
reasoning: readPartitionIntFrom(view, "PLURNK_SERVICE_REASONING", 0),
|
|
68
|
-
|
|
77
|
+
completion: readPartitionIntFrom(view, "PLURNK_SERVICE_COMPLETION", 0),
|
|
69
78
|
safety: readPartitionIntFrom(view, "PLURNK_SERVICE_SAFETY", 0),
|
|
70
79
|
};
|
|
71
80
|
}
|
|
@@ -88,20 +97,20 @@ class PacketBuilder {
|
|
|
88
97
|
}
|
|
89
98
|
return false;
|
|
90
99
|
}
|
|
100
|
+
// #421 — §tokenomics-window-unpollable-deliberate: an unpollable window (provider.contextWindow null
|
|
101
|
+
// after env/probe/catalog all miss) with NO per-alias knob is genuinely-unknown — nobody chose an
|
|
102
|
+
// envelope. The budget/ceiling short-circuit that window to NO-CAP (#isUnboundedWindow) rather than
|
|
103
|
+
// substitute a stand-in the operator never chose; the bare partition still feeds maxTokensFor's
|
|
104
|
+
// generation reserves, which the backend clamps. A per-alias CONTEXT_WINDOW knob makes the window DELIBERATE
|
|
105
|
+
// and it flows bounded, normally.
|
|
106
|
+
#isUnboundedWindow(provider) {
|
|
107
|
+
if (provider.contextWindow !== null)
|
|
108
|
+
return false;
|
|
109
|
+
const alias = ProviderInstantiate.aliasOf(provider) ?? resolveActiveAlias(process.env)?.alias ?? "";
|
|
110
|
+
return !_a.#hasAliasKnob(alias);
|
|
111
|
+
}
|
|
91
112
|
#partitionFor(provider) {
|
|
92
113
|
const alias = ProviderInstantiate.aliasOf(provider) ?? resolveActiveAlias(process.env)?.alias ?? "";
|
|
93
|
-
// #377 — §tokenomics-window-unpollable-deliberate: "CTX stands in for unknown physics" is
|
|
94
|
-
// only honest when the CTX is the operator's DELIBERATE policy for THIS alias. An unpollable
|
|
95
|
-
// window (provider.contextSize null — the provider's machine signal after env/probe/catalog
|
|
96
|
-
// all miss) riding BARE partition numbers is nobody's policy: numbers tuned for some other
|
|
97
|
-
// model silently applied, surfacing later as truncation/overflow telemetry (#352's failure
|
|
98
|
-
// mode recurring for every unpollable model). Fail loud at first use, naming both remedies.
|
|
99
|
-
if (provider.contextSize === null && !_a.#hasAliasKnob(alias)) {
|
|
100
|
-
// BEFORE the cache lookup — the constructor pre-primes the boot alias's partition, and a
|
|
101
|
-
// cache hit must never bypass this refusal (the check keys on the PROVIDER's null window,
|
|
102
|
-
// which the cached partition knows nothing about).
|
|
103
|
-
throw new Error(`window unpollable for alias '${alias || "<active>"}' and its partition rides bare numbers — nobody chose this envelope for this model. Either set PLURNK_PROVIDERS_CONTEXT_SIZE_${alias || "<alias>"} (so the window is known) or set the per-alias partition (PLURNK_SERVICE_{CTX,REASONING,ASSISTANT,SAFETY}_${alias || "<alias>"}) so the numbers are deliberate.`);
|
|
104
|
-
}
|
|
105
114
|
const hit = this.#partitions.get(alias);
|
|
106
115
|
if (hit !== undefined)
|
|
107
116
|
return hit;
|
|
@@ -109,15 +118,15 @@ class PacketBuilder {
|
|
|
109
118
|
this.#partitions.set(alias, part);
|
|
110
119
|
return part;
|
|
111
120
|
}
|
|
112
|
-
// The generation envelope — REASONING +
|
|
121
|
+
// The generation envelope — REASONING + COMPLETION, one undifferentiated pool, passed on
|
|
113
122
|
// every generate({maxTokens}): no decode is unbounded (§tokenomics-window-partition). Per
|
|
114
123
|
// alias (#352): gemma's measured envelope; a cloud alias's generous default the backend clamps.
|
|
115
|
-
|
|
116
|
-
const { reasoning,
|
|
117
|
-
return reasoning +
|
|
124
|
+
maxTokensFor(provider) {
|
|
125
|
+
const { reasoning, completion } = this.#partitionFor(provider);
|
|
126
|
+
return reasoning + completion;
|
|
118
127
|
}
|
|
119
|
-
// §tokenomics-window-partition
|
|
120
|
-
// is DERIVED, never set: effectiveWindow = min(
|
|
128
|
+
// §tokenomics-window-partition — the prompt ceiling
|
|
129
|
+
// is DERIVED, never set: effectiveWindow = min(CONTEXT_WINDOW, provider window; CONTEXT_WINDOW alone when the
|
|
121
130
|
// provider reports none) minus the reserves, divided by the loop's observed real/measured
|
|
122
131
|
// token ratio (usage.prompt is ground truth; a heuristic ruler shipped a 65k-real packet into
|
|
123
132
|
// a 49k window, #311). A fractional ceiling also budgeted the prompt against the window and
|
|
@@ -128,31 +137,34 @@ class PacketBuilder {
|
|
|
128
137
|
// space (usage.prompt, the numerator, is real; the calibration ratio maps measured→real and
|
|
129
138
|
// has no business here). The raw n_ctx overstates usable room by the reserve total.
|
|
130
139
|
promptBudgetFor(provider) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
if (this.#isUnboundedWindow(provider))
|
|
141
|
+
return null; // #421 — no cap: an unknown window has no denominator
|
|
142
|
+
const { contextWindow, reasoning, completion, safety } = this.#partitionFor(provider);
|
|
143
|
+
const effectiveWindow = provider.contextWindow === null ? contextWindow : Math.min(contextWindow, provider.contextWindow);
|
|
144
|
+
return Math.max(0, effectiveWindow - reasoning - completion - safety);
|
|
134
145
|
}
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const
|
|
146
|
+
// §tokenomics-agnostic-ruler — the ceiling is the real window partition (window − reserves),
|
|
147
|
+
// NO calibration ratio: the model-facing measure is the chars/2 ruler (an over-count for
|
|
148
|
+
// typical text), so comparing ruler-weight to the real-token ceiling is itself the conservative
|
|
149
|
+
// bias — the model curates against less room than it has and never overflows for typical
|
|
150
|
+
// content; the exact provider count guards the pathological tail at the materialization gate.
|
|
151
|
+
ceilingFor(provider) {
|
|
152
|
+
if (this.#isUnboundedWindow(provider))
|
|
153
|
+
return null; // #421 — no cap: the gauge headline is omitted
|
|
154
|
+
const { contextWindow, reasoning, completion, safety } = this.#partitionFor(provider);
|
|
155
|
+
const effectiveWindow = provider.contextWindow === null ? contextWindow : Math.min(contextWindow, provider.contextWindow);
|
|
156
|
+
const promptBudget = effectiveWindow - reasoning - completion - safety;
|
|
145
157
|
if (promptBudget <= 0) {
|
|
146
158
|
const alias = ProviderInstantiate.aliasOf(provider) ?? resolveActiveAlias(process.env)?.alias ?? "";
|
|
147
|
-
throw new Error(`window partition contradiction for alias '${alias}': effective window ${effectiveWindow} <= reserves ${reasoning}+${
|
|
159
|
+
throw new Error(`window partition contradiction for alias '${alias}': effective window ${effectiveWindow} <= reserves ${reasoning}+${completion}+${safety}. A local (llama-server) alias needs its OWN measured envelope — set PLURNK_SERVICE_{CONTEXT_WINDOW,REASONING,COMPLETION,SAFETY}_${alias || "<alias>"} (the bare defaults are cloud-generous; #352).`);
|
|
148
160
|
}
|
|
149
|
-
return
|
|
161
|
+
return promptBudget;
|
|
150
162
|
}
|
|
151
163
|
// Assemble the request half of the spec'd packet (Packet.json system
|
|
152
164
|
// and §user) BEFORE the provider call. The same packet object is then
|
|
153
165
|
// completed with assistant + assistantRaw after the model responds, so
|
|
154
166
|
// the stored packet and the wire payload share one source of truth.
|
|
155
|
-
async buildRequestPacket({ initialMessages, requirements,
|
|
167
|
+
async buildRequestPacket({ initialMessages, requirements, workspaceId, workerId, loopId, currentTurnSeq, provider, gitStatus, telemetryErrors: presetTelemetry, }) {
|
|
156
168
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
157
169
|
// plurnk.md (grammar/dialects) ONLY — the definition is the hot-path grammar.
|
|
158
170
|
// The scheme catalogue is its own `schemes` section below tools (§schemes-directory),
|
|
@@ -165,7 +177,7 @@ class PacketBuilder {
|
|
|
165
177
|
// the runLoop caller's messages.user for tests that bypass the
|
|
166
178
|
// foist mechanism entirely.
|
|
167
179
|
const loopSeqRow = await this.#db.engine_loop_sequence.get({ loop_id: loopId });
|
|
168
|
-
const promptRows = (await this.#db.drain_get_all_prompt_bodies_for_loop.all({ pattern: `${promptLoopPrefix(
|
|
180
|
+
const promptRows = (await this.#db.drain_get_all_prompt_bodies_for_loop.all({ pattern: `${promptLoopPrefix(workerId, loopSeqRow?.sequence ?? loopId)}%` }))
|
|
169
181
|
.filter((r) => typeof r.content === "string" && r.content.length > 0);
|
|
170
182
|
// §prompt-auto-read (owner): the section is a PATHS list (the errors shape — no bodies);
|
|
171
183
|
// each prompt's content reaches the model through its foisted auto-READ in the log, and
|
|
@@ -184,14 +196,14 @@ class PacketBuilder {
|
|
|
184
196
|
// leads requirements.md, so a third copy here was pure duplication in the model's packet. PLAN
|
|
185
197
|
// is mandated unconditionally by plurnk.md §Imperatives (grammar 0.70 requires every turn to
|
|
186
198
|
// lead with PLAN), so the service injects no separate plan directive either.
|
|
187
|
-
const log = await this.#buildLog(
|
|
199
|
+
const log = await this.#buildLog(workerId);
|
|
188
200
|
const telemetryErrors = presetTelemetry ?? await this.buildTelemetryErrors(loopId, currentTurnSeq);
|
|
189
|
-
const countTokens =
|
|
190
|
-
// #367 — the capability sheet must reflect the LOOP MODE, not just
|
|
201
|
+
const countTokens = rulerCount; // §tokenomics-agnostic-ruler — the ONE model-facing ruler (chars/2), not the provider
|
|
202
|
+
// #367 — the capability sheet must reflect the LOOP MODE, not just workspace-enablement: an
|
|
191
203
|
// ask-mode loop advertises only what its dispatch gate (resolveForLoop) will accept, so the
|
|
192
204
|
// model is never taught a tag it'll then be 403'd on (the taught→emitted→rejected→508 spiral).
|
|
193
205
|
const activeSchemes = this.#schemes.resolveForLoop(await this.#loadLoopFlags(loopId));
|
|
194
|
-
const tools = this.#collectTools(await this.#
|
|
206
|
+
const tools = this.#collectTools(await this.#workspaceEnabled(workspaceId), await WorkspaceSettings.questionsEnabled(this.#db, workspaceId), activeSchemes);
|
|
195
207
|
// Budget readout (SPEC.md §tokenomics). Two-pass: render the budget from
|
|
196
208
|
// the structured log's subtotals with a {{tokensFree}} placeholder, build
|
|
197
209
|
// the section list, measure the assembled total, resolve free, substitute.
|
|
@@ -199,8 +211,9 @@ class PacketBuilder {
|
|
|
199
211
|
// a serialized approximation. ceiling is the provider's window ×
|
|
200
212
|
// PLURNK_BUDGET_CEILING (null when no window is reported → headline
|
|
201
213
|
// omitted, section lines still shown). §tokenomics-render-weight-budget
|
|
202
|
-
const ceiling = this.ceilingFor(provider
|
|
203
|
-
const
|
|
214
|
+
const ceiling = this.ceilingFor(provider);
|
|
215
|
+
const logBudget = PacketWire.measureLogBudget(log, countTokens);
|
|
216
|
+
const budgetReadout = this.#renderBudget(logBudget, ceiling);
|
|
204
217
|
// The default packet: an ordered list of addressable sections (§packet-assembly).
|
|
205
218
|
// `slot` is a TRUST boundary (and the prompt-cache boundary): system holds only
|
|
206
219
|
// framework-authored, non-injectable sections — the static head (definition/tools/
|
|
@@ -209,24 +222,24 @@ class PacketBuilder {
|
|
|
209
222
|
// the requirements footer. The budget section carries its {{tokensFree}} placeholders
|
|
210
223
|
// here; they resolve below once the assembled total is known.
|
|
211
224
|
const inject = await readPacketInject(); // #240 — operator section, per-turn, fail-hard on a broken path
|
|
212
|
-
const
|
|
225
|
+
const workspaceRoot = (await this.#db.envelope_get_workspace.get({ id: workspaceId }))?.project_root ?? null;
|
|
213
226
|
const systemPolicy = await readSystemPolicy(); // ~/.plurnk/AGENTS.md (or PLURNK_SERVICE_POLICY)
|
|
214
|
-
const projectPolicy = await readProjectPolicy(
|
|
227
|
+
const projectPolicy = await readProjectPolicy(workspaceRoot); // <projectRoot>/AGENTS.md (or PLURNK_SERVICE_PROJECT)
|
|
215
228
|
// Child-orientation (§child-orientation): the live things THIS run holds — open streams +
|
|
216
|
-
// unconcluded child
|
|
229
|
+
// unconcluded child workers — surfaced every turn as terse `* <status> <path>` pointers (same shape
|
|
217
230
|
// as errors) just above the errors section. Orienting STATE so the model never loses track of
|
|
218
231
|
// what it's holding (the premature-terminate trap), never advice on what to do. Empty → omitted.
|
|
219
|
-
const childStreams = (await this.#db.engine_child_streams_open.all({
|
|
232
|
+
const childStreams = (await this.#db.engine_child_streams_open.all({ worker_id: workerId }))
|
|
220
233
|
.map((s) => ({ status: "active", path: renderAddress(s.scheme, s.pathname) }));
|
|
221
|
-
const
|
|
222
|
-
.map((r) => ({ status: r.status, path: `
|
|
234
|
+
const childWorkers = (await this.#db.engine_child_workers_live.all({ worker_id: workerId }))
|
|
235
|
+
.map((r) => ({ status: r.status, path: `worker://${r.name}` }));
|
|
223
236
|
const defaults = [
|
|
224
237
|
{ name: "definition", slot: "system", header: null, content: system_definition, tokens: 0 },
|
|
225
|
-
{ name: "tools", slot: "system", header: null, content: tools
|
|
226
|
-
{ name: "schemes", slot: "system", header: "
|
|
227
|
-
...(inject !== null ? [{ name: "inject", slot: "system", header: "
|
|
238
|
+
{ name: "tools", slot: "system", header: null, content: tools, tokens: 0 }, // titleless — the fenced op catalog flows on from plurnk.md (definition) directly above
|
|
239
|
+
{ name: "schemes", slot: "system", header: "Schemes", content: this.#schemes.teach(), tokens: 0 },
|
|
240
|
+
...(inject !== null ? [{ name: "inject", slot: "system", header: "Operator Notes", content: inject, tokens: 0 }] : []),
|
|
228
241
|
// policy: the client's privileged rules — ~/.plurnk/AGENTS.md (system) then <root>/AGENTS.md (project) — below grammar/tools/schemes, above budget-the-law. AGENTS is POLICY here, never a curatable READable entry. Empty content ⇒ section omitted.
|
|
229
|
-
{ name: "system-policy", slot: "system", header: "
|
|
242
|
+
{ name: "system-policy", slot: "system", header: "Policy", content: systemPolicy ?? "", tokens: 0 },
|
|
230
243
|
{ name: "project-policy", slot: "system", header: "Project Policy", content: projectPolicy ?? "", tokens: 0 },
|
|
231
244
|
// The packet split is a TRUST boundary: system carries only framework-authored, non-injectable
|
|
232
245
|
// sections; anything that could carry attacker-reachable text (a READ result, exec output, the
|
|
@@ -235,19 +248,19 @@ class PacketBuilder {
|
|
|
235
248
|
// neither is an injection surface; both sit at the bottom of system, just above budget-the-law.
|
|
236
249
|
// child-orientation: what THIS run holds live — streams then runs — just above errors. Terse
|
|
237
250
|
// pointers (the path is the actionable address the model READs/OPENs/KILLs), never advice. §child-orientation
|
|
238
|
-
{ name: "child-streams", slot: "system", header: "
|
|
239
|
-
{ name: "child-
|
|
240
|
-
{ name: "errors", slot: "system", header: "
|
|
241
|
-
{ name: "git", slot: "system", header: "
|
|
251
|
+
{ name: "child-streams", slot: "system", header: "Child Streams", content: PacketWire.renderChildPointers(childStreams), tokens: 0 },
|
|
252
|
+
{ name: "child-workers", slot: "system", header: "Active Child Workers", content: PacketWire.renderChildPointers(childWorkers), tokens: 0 },
|
|
253
|
+
{ name: "errors", slot: "system", header: "Errors", content: PacketWire.renderErrors(telemetryErrors), tokens: 0 },
|
|
254
|
+
{ name: "git", slot: "system", header: "Git Status", content: PacketWire.renderGit(gitStatus), tokens: 0 },
|
|
242
255
|
// budget — LAW (a hard ceiling the model must obey).
|
|
243
|
-
{ name: "budget", slot: "system", header: "
|
|
256
|
+
{ name: "budget", slot: "system", header: "Budget", content: budgetReadout, tokens: 0 },
|
|
244
257
|
// §prompt-auto-read (owner): the prompts section is the system slot's very bottom —
|
|
245
258
|
// a paths-only list (the errors shape); bodies arrive via the foisted auto-READ.
|
|
246
|
-
{ name: "prompt", slot: "system", header: "
|
|
259
|
+
{ name: "prompt", slot: "system", header: "User Prompts", content: prompt, tokens: 0 },
|
|
247
260
|
// log in the user slot: injectable content (READ results, exec output, the model's own mirror) — data, never rules — kept at the action point so the model consults its history.
|
|
248
|
-
{ name: "log", slot: "user", header: "
|
|
261
|
+
{ name: "log", slot: "user", header: "Log", content: PacketWire.renderLog(log, countTokens), tokens: 0 },
|
|
249
262
|
// requirements renders LAST — the user-slot footer, the syntax contract closest to the model's turn (a recency carve-out for weak models).
|
|
250
|
-
{ name: "requirements", slot: "user", header: "
|
|
263
|
+
{ name: "requirements", slot: "user", header: "Recap", content: baseRequirements, tokens: 0 },
|
|
251
264
|
];
|
|
252
265
|
// Plugin packet control (§packet-assembly): trusted schemes rewrite the
|
|
253
266
|
// default list — add, remove, reorder — in-process, before measurement.
|
|
@@ -257,14 +270,18 @@ class PacketBuilder {
|
|
|
257
270
|
let total = countTokens(PacketWire.renderSlot(sections, "system")) + countTokens(PacketWire.renderSlot(sections, "user"));
|
|
258
271
|
{
|
|
259
272
|
const budgetSec = sections.find((s) => s.name === "budget"); // a plugin may have removed it
|
|
260
|
-
|
|
273
|
+
// A null ceiling (#421 — unbounded window) has no headline to calibrate: no truncation, no
|
|
274
|
+
// percent/free substitution. #renderBudget already omitted the headline, so nothing to do.
|
|
275
|
+
if (budgetSec && ceiling !== null) {
|
|
261
276
|
// Curation pressure gates on OCCUPANCY (§tokenomics-pressure-gates-on-occupancy, #308):
|
|
262
277
|
// the Turns/Heaviest tables are a standing FOLD-target list, and a high-headroom model
|
|
263
278
|
// reads them as a todo — burning turns on token hygiene at 3% of a 64k window. Under
|
|
264
279
|
// half-full, the headline's numbers stand alone (truncate at the first blank line) and
|
|
265
280
|
// the total RE-measures — the substituted figures must reconcile with what ships.
|
|
266
281
|
// A null ceiling can't calibrate, so the full readout stays.
|
|
267
|
-
|
|
282
|
+
// A mermaid budget (#440) self-scales to pressure, so it is never truncated — the calm
|
|
283
|
+
// low-usage view IS the point; only the tabular readout collapses under half-full.
|
|
284
|
+
if (!budgetSec.content.includes("```mermaid") && (total / ceiling) * 100 < 50) {
|
|
268
285
|
const cut = budgetSec.content.indexOf("\n\n");
|
|
269
286
|
if (cut !== -1) {
|
|
270
287
|
budgetSec.content = budgetSec.content.slice(0, cut);
|
|
@@ -273,12 +290,17 @@ class PacketBuilder {
|
|
|
273
290
|
}
|
|
274
291
|
const tokensFree = Math.max(0, ceiling - total); // free floors at 0 on overshoot — §tokenomics-over-budget-floor
|
|
275
292
|
const percent = (total / ceiling) * 100; // usage as % of the ceiling — §tokenomics-context-percent
|
|
293
|
+
const sumTurns = logBudget.byTurn.reduce((s, t) => s + t.tokens, 0); // #440 — treemap non-turn box = total − Σturns
|
|
294
|
+
const systemCtx = Math.max(0, total - sumTurns);
|
|
295
|
+
// replaceAll: a mermaid budget recurs free/used across the headline + treemap + pie, so a
|
|
296
|
+
// single .replace would leave the diagrams carrying literal {{…}} placeholders.
|
|
276
297
|
budgetSec.content = budgetSec.content
|
|
277
|
-
.
|
|
298
|
+
.replaceAll(TOKEN_USAGE_PLACEHOLDER, String(total))
|
|
278
299
|
// Any nonzero usage under 1% is "<1" — Math.round alone claimed "1%" from 0.51%,
|
|
279
300
|
// overstating a near-empty window.
|
|
280
|
-
.
|
|
281
|
-
.
|
|
301
|
+
.replaceAll(TOKEN_PERCENT_PLACEHOLDER, total > 0 && percent < 1 ? "<1" : String(Math.round(percent)))
|
|
302
|
+
.replaceAll(TOKENS_FREE_PLACEHOLDER, String(tokensFree))
|
|
303
|
+
.replaceAll(SYSTEM_CTX_PLACEHOLDER, String(systemCtx));
|
|
282
304
|
}
|
|
283
305
|
}
|
|
284
306
|
// Pass 2: per-section render-weight + the assembled packet total (post
|
|
@@ -294,7 +316,23 @@ class PacketBuilder {
|
|
|
294
316
|
// placeholder here — buildRequestPacket substitutes it after measuring the packet.
|
|
295
317
|
#renderBudget(log, ceiling) {
|
|
296
318
|
const lines = [];
|
|
297
|
-
|
|
319
|
+
// #421 — no ceiling (unbounded window): omit the headline entirely; the section lines below
|
|
320
|
+
// stay so the model keeps its FOLD-target surface, just with no percent it can't compute.
|
|
321
|
+
if (ceiling !== null)
|
|
322
|
+
lines.push(`Token Ceiling ${ceiling} · Token Usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · Tokens Free ${TOKENS_FREE_PLACEHOLDER}`);
|
|
323
|
+
// #440 {§budget-mermaid} — the enriched visual Budget (default on). With a ceiling to scale
|
|
324
|
+
// against, the treemap REPLACES the Turns table (per-turn composition) + a pie gauge; the
|
|
325
|
+
// heaviest-items list stays a table (#450). Self-scaled to pressure (calm→urgent), never <50%-truncated.
|
|
326
|
+
// Set PLURNK_SERVICE_BUDGET_MERMAID=off to A/B against the tabular baseline (#440's before/after).
|
|
327
|
+
if (process.env.PLURNK_SERVICE_BUDGET_MERMAID !== "off" && ceiling !== null && log.entries > 0) {
|
|
328
|
+
if (lines.length > 0)
|
|
329
|
+
lines.push("");
|
|
330
|
+
lines.push(_a.#renderBudgetMermaid(log, ceiling));
|
|
331
|
+
// #450 — the heaviest items stay a plain ranked list (a ranking isn't a composition, no
|
|
332
|
+
// treemap; two mermaid diagrams are enough visual examples) — the same table as the tabular budget.
|
|
333
|
+
lines.push(..._a.#heaviestItemsLines(log.largest));
|
|
334
|
+
return lines.join("\n");
|
|
335
|
+
}
|
|
298
336
|
if (log.entries > 0) {
|
|
299
337
|
if (lines.length > 0)
|
|
300
338
|
lines.push("");
|
|
@@ -306,23 +344,54 @@ class PacketBuilder {
|
|
|
306
344
|
for (const t of log.byTurn)
|
|
307
345
|
lines.push(`| ${t.turn} | ${t.tokens} |`);
|
|
308
346
|
}
|
|
309
|
-
|
|
310
|
-
// (§tokenomics {§tokenomics-largest-entries}). "items", not "entries": the readout
|
|
311
|
-
// lists log:/// rows (log items), distinct from catalog entries (plurnk.md: "EDIT
|
|
312
|
-
// is only for entries. Do not attempt to edit log items.").
|
|
313
|
-
if (log.largest.length > 0) {
|
|
314
|
-
lines.push("", "Heaviest items (FOLD targets — folding reclaims their tokens):", "| item | tokens |", "|---|--:|");
|
|
315
|
-
for (const e of log.largest)
|
|
316
|
-
lines.push(`| ${e.path} | ${e.tokens} |`);
|
|
317
|
-
}
|
|
347
|
+
lines.push(..._a.#heaviestItemsLines(log.largest));
|
|
318
348
|
}
|
|
319
349
|
return lines.join("\n");
|
|
320
350
|
}
|
|
321
|
-
//
|
|
322
|
-
//
|
|
351
|
+
// The heaviest individual log items — the FOLD targets behind the weight, a ranked LIST in both the
|
|
352
|
+
// mermaid and tabular budgets (#450: a ranking isn't a composition, so it's never a chart). "items",
|
|
353
|
+
// not "entries": log:/// rows, distinct from catalog entries (plurnk.md: "EDIT is only for entries").
|
|
354
|
+
// {§tokenomics-largest-entries}
|
|
355
|
+
static #heaviestItemsLines(largest) {
|
|
356
|
+
if (largest.length === 0)
|
|
357
|
+
return [];
|
|
358
|
+
return ["", "Heaviest items (FOLD targets — folding reclaims their tokens):", "| item | tokens |", "|---|--:|",
|
|
359
|
+
...largest.map((e) => `| ${e.path} | ${e.tokens} |`)];
|
|
360
|
+
}
|
|
361
|
+
// #440 {§budget-mermaid} — the Budget as two budget-scaled mermaid diagrams (validated to render on
|
|
362
|
+
// GitHub; syntax: plurnk-plurnkdown/demo/budget-mermaid.md). Both scaled to the CEILING, so salience
|
|
363
|
+
// tracks pressure: `free` dominates at low usage (calm), turn boxes fill as it climbs (urgent).
|
|
364
|
+
// free/used/system+context are placeholders — the post-assembly total resolves them. (#450 cut the xychart.)
|
|
365
|
+
static #renderBudgetMermaid(log, ceiling) {
|
|
366
|
+
// Turn composition → treemap: turn boxes + system+context + free compose the whole ceiling —
|
|
367
|
+
// the per-turn FOLD surface (which turns are heavy, labeled `turn L/T`) the headline can't give.
|
|
368
|
+
const treemap = [
|
|
369
|
+
"```mermaid",
|
|
370
|
+
"treemap-beta",
|
|
371
|
+
`"Budget — ceiling ${ceiling}"`,
|
|
372
|
+
` "free": ${TOKENS_FREE_PLACEHOLDER}`,
|
|
373
|
+
` "system + context": ${SYSTEM_CTX_PLACEHOLDER}`,
|
|
374
|
+
...log.byTurn.map((t) => ` "turn ${t.turn}": ${t.tokens}`),
|
|
375
|
+
"```",
|
|
376
|
+
].join("\n");
|
|
377
|
+
// Gauge → pie: used vs free, budget-scaled (used + free = ceiling); also a visual exemplar for
|
|
378
|
+
// the model's own user-facing SENDs. (#450 cut the heaviest-items xychart — its bare-coordinate
|
|
379
|
+
// labels a floor model can't decode, and the treemap already surfaces per-turn heaviness.)
|
|
380
|
+
const pie = [
|
|
381
|
+
"```mermaid",
|
|
382
|
+
"pie showData",
|
|
383
|
+
` title Budget — used vs free (ceiling ${ceiling})`,
|
|
384
|
+
` "used" : ${TOKEN_USAGE_PLACEHOLDER}`,
|
|
385
|
+
` "free" : ${TOKENS_FREE_PLACEHOLDER}`,
|
|
386
|
+
"```",
|
|
387
|
+
].join("\n");
|
|
388
|
+
return [treemap, pie].join("\n\n");
|
|
389
|
+
}
|
|
390
|
+
// #328 — the per-workspace client execs policy narrows what the packet ADVERTISES, matching what
|
|
391
|
+
// dispatch refuses: a workspace-disabled tag is absent from the capability sheet and the doc set,
|
|
323
392
|
// never taught-then-refused. No policy (execs unset) → everything boot-registered shows.
|
|
324
|
-
async #
|
|
325
|
-
const { execs } = await
|
|
393
|
+
async #workspaceEnabled(workspaceId) {
|
|
394
|
+
const { execs } = await WorkspaceSettings.read(this.#db, workspaceId);
|
|
326
395
|
if (execs === null)
|
|
327
396
|
return () => true;
|
|
328
397
|
return (tag) => Policy.isEnabled(tag, execs);
|
|
@@ -344,58 +413,55 @@ class PacketBuilder {
|
|
|
344
413
|
return DEFAULT_LOOP_FLAGS;
|
|
345
414
|
}
|
|
346
415
|
}
|
|
347
|
-
#collectTools(
|
|
348
|
-
|
|
416
|
+
#collectTools(workspaceEnabled, questionsOn = false, activeSchemes) {
|
|
417
|
+
// §PACKET Tools (#441) — the capability sheet's OP examples ride a `plurnk` fence, matching the
|
|
418
|
+
// Schemes catalog (one packet, one shape for op-example sheets). Prose notices (EXEC-disabled)
|
|
419
|
+
// stay prose beside the fence — a prose line isn't an op for the op-fence gate to validate.
|
|
420
|
+
const ops = [];
|
|
421
|
+
const notices = [];
|
|
349
422
|
// §send-300-choices — the one-liner rides ONLY where questions are enabled (allowed +
|
|
350
423
|
// client-requested); the fuller questions.md doc injects through docEntries the same way.
|
|
351
424
|
if (questionsOn)
|
|
352
|
-
|
|
353
|
-
// Each available runtime tag contributes its self-documenting example —
|
|
354
|
-
// the example carries syntax + purpose, so there's no prose line. Tags
|
|
355
|
-
// with no example (sh/node, covered by the core prompt) contribute
|
|
356
|
-
// nothing; available-only, so the model never sees an unusable tag. `* `
|
|
357
|
-
// bullets + bare op forms match the packet's list/op rendering (no `- `,
|
|
358
|
-
// no backticks — see packet-wire.ts).
|
|
425
|
+
ops.push("<<SEND[300]:Deploy where?;staging;production:SEND");
|
|
359
426
|
const executors = this.#executors();
|
|
360
427
|
if (executors !== undefined) {
|
|
361
428
|
const excluded = docsExcludeSet();
|
|
362
429
|
const runtimes = executors.availableRuntimes();
|
|
363
|
-
// execs#24
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
// be8a77c. The sheet's lines are EXEC-usage examples, so the key is the 'exec' scheme
|
|
367
|
-
// (the op face, excludedInAsk). When it's inactive, say so POSITIVELY: plurnk.md still
|
|
368
|
-
// teaches EXEC as language, and silent absence measurably invites confabulated runtimes
|
|
369
|
-
// (the client's 5-probe: 500×3). Core speaks the line — only core knows the gate closed.
|
|
430
|
+
// execs#24 / #367: the sheet's lines are EXEC-usage examples, keyed on the 'exec' scheme
|
|
431
|
+
// (the op face, excludedInAsk). When inactive, say so POSITIVELY (a prose notice): plurnk.md
|
|
432
|
+
// still teaches EXEC as language, and silent absence measurably invites confabulated runtimes.
|
|
370
433
|
const execActive = activeSchemes === undefined || activeSchemes.has("exec");
|
|
371
434
|
if (runtimes.length > 0 && !execActive) {
|
|
372
|
-
|
|
373
|
-
return tools;
|
|
435
|
+
notices.push("EXEC operations are disabled for this loop — do not run commands; answer or advise directly");
|
|
374
436
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
437
|
+
else {
|
|
438
|
+
for (const tag of runtimes) {
|
|
439
|
+
if (excluded.has(tag))
|
|
440
|
+
continue; // #240 — PLURNK_SERVICE_DOCS_EXCLUDE drops the oneliner + the doc
|
|
441
|
+
if (!workspaceEnabled(tag))
|
|
442
|
+
continue; // #328 — workspace-disabled tags aren't advertised
|
|
443
|
+
const entry = executors.entry(tag);
|
|
444
|
+
// #240 — the example IS the oneliner (a bare op, fenced below); the fuller doc
|
|
445
|
+
// materializes at plurnk://docs/<tag>.md. No example → no line.
|
|
446
|
+
if (entry?.example)
|
|
447
|
+
ops.push(entry.example);
|
|
448
|
+
}
|
|
386
449
|
}
|
|
387
450
|
}
|
|
388
|
-
|
|
451
|
+
const parts = [...notices];
|
|
452
|
+
if (ops.length > 0)
|
|
453
|
+
parts.push(`\`\`\`plurnk\n${ops.join("\n")}\n\`\`\``);
|
|
454
|
+
return parts.join("\n\n");
|
|
389
455
|
}
|
|
390
456
|
// #note12 — the daughter-provided reference docs (schemes' + execs' `documentation`),
|
|
391
457
|
// materialized at plurnk:///docs/<name>.md by loop_run (like operator docs) so the
|
|
392
458
|
// catalogue's doc-links READ and the manifest carries each doc's token cost.
|
|
393
|
-
async docEntries(
|
|
459
|
+
async docEntries(workspaceId) {
|
|
394
460
|
const out = this.#schemes.docs(); // scheme docs already drop PLURNK_SERVICE_DOCS_EXCLUDE names
|
|
395
461
|
// §send-300-choices — the conditional teaching: questions.md (from the docs corpus)
|
|
396
|
-
// materializes ONLY for enabled
|
|
397
|
-
// plugin docs below. An un-enabled
|
|
398
|
-
if (await
|
|
462
|
+
// materializes ONLY for enabled workspaces — the same conditional-doc mechanism as the EXEC
|
|
463
|
+
// plugin docs below. An un-enabled workspace is never taught the op it can't use.
|
|
464
|
+
if (await WorkspaceSettings.questionsEnabled(this.#db, workspaceId)) {
|
|
399
465
|
try {
|
|
400
466
|
const q = await readFile(resolvePath(Paths.schemeDocs, "questions.md"), "utf8");
|
|
401
467
|
if (q.length > 0)
|
|
@@ -406,11 +472,11 @@ class PacketBuilder {
|
|
|
406
472
|
const executors = this.#executors();
|
|
407
473
|
if (executors !== undefined) {
|
|
408
474
|
const excluded = docsExcludeSet();
|
|
409
|
-
const
|
|
475
|
+
const workspaceEnabled = await this.#workspaceEnabled(workspaceId); // #328 — no doc for a disabled tag
|
|
410
476
|
for (const tag of executors.availableRuntimes()) {
|
|
411
477
|
if (excluded.has(tag))
|
|
412
478
|
continue; // #240 — exec docs honor the same exclude
|
|
413
|
-
if (!
|
|
479
|
+
if (!workspaceEnabled(tag))
|
|
414
480
|
continue;
|
|
415
481
|
const doc = executors.entry(tag)?.documentation;
|
|
416
482
|
if (doc !== undefined && doc.length > 0)
|
|
@@ -425,10 +491,12 @@ class PacketBuilder {
|
|
|
425
491
|
// strike, rebuild, re-measure. Folds (never deletes). The strike it raises and
|
|
426
492
|
// the hard-stop it can signal are returned to runLoop, which owns abandonment.
|
|
427
493
|
// §grinder-overflow-only — fires only on actual overflow, never speculatively
|
|
428
|
-
async enforceBudget({ packet, provider,
|
|
429
|
-
const ceiling = this.ceilingFor(provider
|
|
494
|
+
async enforceBudget({ packet, provider, workerId, loopId, turnId, mintSequence, rebuild }) {
|
|
495
|
+
const ceiling = this.ceilingFor(provider);
|
|
430
496
|
const measure = (p) => p.tokens;
|
|
431
|
-
|
|
497
|
+
// #421 — a null ceiling is an unbounded window: always fit, never fold or strike (the backend
|
|
498
|
+
// clamps; this mirrors Engine's physicallySendable, which treats a null contextWindow as sendable).
|
|
499
|
+
if (ceiling === null || measure(packet) <= ceiling)
|
|
432
500
|
return { packet, fit: true, struck: false };
|
|
433
501
|
// ONE rule, every turn — turn 1 and turn 101 alike (§grinder-layer1-rollback): fold the
|
|
434
502
|
// NEWEST turn boundary's still-open rows (the prior turn's emissions + this turn's
|
|
@@ -442,15 +510,23 @@ class PacketBuilder {
|
|
|
442
510
|
// turn late. The row is grinder-exempt, so it stacks into a visible recurrence trail. It
|
|
443
511
|
// sits at the turn's reserved running sequence (mintSequence) so it never collides with the
|
|
444
512
|
// post-generate dispatch rows. §telemetry-uniform-error-channel, §grinder-overflow-error-row
|
|
445
|
-
await this.#telemetry.mintEngineError("budget_overflow", {
|
|
513
|
+
await this.#telemetry.mintEngineError("budget_overflow", { workerId, loopId, turnId, sequence: mintSequence });
|
|
446
514
|
await this.#db.engine_grinder_fold_newest_turn.run({ loop_id: loopId, turn_id: turnId });
|
|
447
515
|
const current = await rebuild();
|
|
448
516
|
return { packet: current, fit: measure(current) <= ceiling, struck: true };
|
|
449
517
|
}
|
|
518
|
+
// §tokenomics-agnostic-ruler — the EXACT materialization measure: the provider's own token
|
|
519
|
+
// count of the assembled packet, the ONE place per-model exactness is used (once per turn, at
|
|
520
|
+
// the fit-gate). The model-facing render-weight (packet.tokens) stays the ruler; this is the
|
|
521
|
+
// physics check that the real bytes fit the real window.
|
|
522
|
+
exactPacketTokens(packet, provider) {
|
|
523
|
+
return provider.countTokens(PacketWire.renderSlot(packet.sections, "system"))
|
|
524
|
+
+ provider.countTokens(PacketWire.renderSlot(packet.sections, "user"));
|
|
525
|
+
}
|
|
450
526
|
// Complete the packet by adding the model's response. After this the
|
|
451
527
|
// packet matches Packet.json fully and is ready for storage.
|
|
452
528
|
completePacket(requestPacket, assistant, assistantRaw, provider) {
|
|
453
|
-
const assistantTokens =
|
|
529
|
+
const assistantTokens = rulerCount(assistant.content); // §tokenomics-agnostic-ruler — render-weight in ruler units (usage_* keep the provider's real count)
|
|
454
530
|
return {
|
|
455
531
|
tokens: requestPacket.tokens + assistantTokens,
|
|
456
532
|
sections: requestPacket.sections,
|
|
@@ -484,16 +560,16 @@ class PacketBuilder {
|
|
|
484
560
|
// Snapshot is taken at packet build (pre-dispatch this turn), so it
|
|
485
561
|
// reflects "what has happened before this turn." Each row carries a
|
|
486
562
|
// log:///<loop_seq>/<turn_seq>/<sequence> coordinate the model can READ.
|
|
487
|
-
async #buildLog(
|
|
488
|
-
// SPEC §packet-terms: runs own log entries — log is the
|
|
489
|
-
// not the loop's. Span all loops in the
|
|
563
|
+
async #buildLog(workerId) {
|
|
564
|
+
// SPEC §packet-terms: runs own log entries — log is the worker's history,
|
|
565
|
+
// not the loop's. Span all loops in the worker so the model sees
|
|
490
566
|
// earlier loops' work as conversational memory.
|
|
491
567
|
//
|
|
492
568
|
// User prompts are first-class log entries: runTurn writes a
|
|
493
569
|
// client-origin SEND[200] row at sequence=0 of each new
|
|
494
570
|
// turn-1. Prompts thus surface naturally in this query — no
|
|
495
571
|
// synthetic / shim layer.
|
|
496
|
-
const rows = await this.#db.engine_render_log.all({
|
|
572
|
+
const rows = await this.#db.engine_render_log.all({ worker_id: workerId });
|
|
497
573
|
return rows.map((r) => ({
|
|
498
574
|
coordinate: `${r.loop_seq}/${r.turn_seq}/${r.sequence}`,
|
|
499
575
|
origin: r.origin,
|