@plurnk/plurnk-service 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SPEC.md +116 -83
- package/bin/plurnk-service.ts +132 -0
- package/dist/Paths.d.ts +8 -0
- package/dist/Paths.d.ts.map +1 -0
- package/dist/Paths.js +47 -0
- package/dist/Paths.js.map +1 -0
- package/dist/content/index.d.ts +9 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +10 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/line-marker.d.ts +26 -0
- package/dist/content/line-marker.d.ts.map +1 -0
- package/dist/content/line-marker.js +323 -0
- package/dist/content/line-marker.js.map +1 -0
- package/dist/content/matcher.d.ts +15 -0
- package/dist/content/matcher.d.ts.map +1 -0
- package/dist/content/matcher.js +112 -0
- package/dist/content/matcher.js.map +1 -0
- package/dist/content/mimetype-binary.d.ts +9 -0
- package/dist/content/mimetype-binary.d.ts.map +1 -0
- package/dist/content/mimetype-binary.js +86 -0
- package/dist/content/mimetype-binary.js.map +1 -0
- package/dist/content/path-mimetype.d.ts +6 -0
- package/dist/content/path-mimetype.d.ts.map +1 -0
- package/dist/content/path-mimetype.js +49 -0
- package/dist/content/path-mimetype.js.map +1 -0
- package/dist/content/read-resolve.d.ts +20 -0
- package/dist/content/read-resolve.d.ts.map +1 -0
- package/dist/content/read-resolve.js +60 -0
- package/dist/content/read-resolve.js.map +1 -0
- package/dist/core/ChannelWrite.d.ts +35 -30
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +49 -41
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Engine.d.ts +16 -10
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +309 -115
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/EnvFlags.d.ts +6 -3
- package/dist/core/EnvFlags.d.ts.map +1 -1
- package/dist/core/EnvFlags.js +62 -60
- package/dist/core/EnvFlags.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +26 -0
- package/dist/core/ExecutorRegistry.d.ts.map +1 -0
- package/dist/core/ExecutorRegistry.js +99 -0
- package/dist/core/ExecutorRegistry.js.map +1 -0
- package/dist/core/PluginLoader.d.ts +6 -3
- package/dist/core/PluginLoader.d.ts.map +1 -1
- package/dist/core/PluginLoader.js +77 -73
- package/dist/core/PluginLoader.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +4 -2
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +23 -22
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +1 -1
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +3 -3
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/git-membership.d.ts +8 -0
- package/dist/core/git-membership.d.ts.map +1 -0
- package/dist/core/git-membership.js +125 -0
- package/dist/core/git-membership.js.map +1 -0
- package/dist/core/packet-wire.d.ts +47 -6
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +376 -312
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/resolveForLoop.d.ts +16 -0
- package/dist/core/resolveForLoop.d.ts.map +1 -0
- package/dist/core/resolveForLoop.js +34 -0
- package/dist/core/resolveForLoop.js.map +1 -0
- package/dist/core/results.d.ts +40 -0
- package/dist/core/results.d.ts.map +1 -0
- package/dist/core/results.js +52 -0
- package/dist/core/results.js.map +1 -0
- package/dist/core/scheme-types.d.ts +7 -4
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js +4 -4
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/core/types.d.ts +26 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +17 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -43
- package/dist/index.js.map +1 -1
- package/dist/schemes/EffectPolicy.d.ts +6 -0
- package/dist/schemes/EffectPolicy.d.ts.map +1 -0
- package/dist/schemes/EffectPolicy.js +18 -0
- package/dist/schemes/EffectPolicy.js.map +1 -0
- package/dist/schemes/Exec.d.ts +1 -0
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +86 -45
- 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 +32 -69
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Known.d.ts.map +1 -1
- package/dist/schemes/Known.js +13 -13
- package/dist/schemes/Known.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +8 -52
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Plurnk.d.ts.map +1 -1
- package/dist/schemes/Plurnk.js +13 -13
- package/dist/schemes/Plurnk.js.map +1 -1
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +13 -13
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Unknown.d.ts.map +1 -1
- package/dist/schemes/Unknown.js +13 -13
- package/dist/schemes/Unknown.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +5 -3
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +55 -50
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts +10 -3
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +99 -77
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.d.ts +6 -0
- package/dist/schemes/_entry-manifest.d.ts.map +1 -0
- package/dist/schemes/_entry-manifest.js +45 -0
- package/dist/schemes/_entry-manifest.js.map +1 -0
- package/dist/schemes/_entry-ops.d.ts +7 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +198 -316
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-send.d.ts +4 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +57 -55
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/server/ClientConnection.js +3 -3
- package/dist/server/ClientConnection.js.map +1 -1
- package/dist/server/Daemon.d.ts +5 -5
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +234 -176
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/clientTurn.d.ts +4 -1
- package/dist/server/clientTurn.d.ts.map +1 -1
- package/dist/server/clientTurn.js +19 -17
- package/dist/server/clientTurn.js.map +1 -1
- package/dist/server/dsl.d.ts +19 -16
- package/dist/server/dsl.d.ts.map +1 -1
- package/dist/server/dsl.js +127 -105
- package/dist/server/dsl.js.map +1 -1
- package/dist/server/envelope.d.ts +22 -19
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +116 -102
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/logEntry.d.ts +4 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +41 -39
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/methods/_dispatchAsClient.d.ts +3 -1
- package/dist/server/methods/_dispatchAsClient.d.ts.map +1 -1
- package/dist/server/methods/_dispatchAsClient.js +31 -29
- package/dist/server/methods/_dispatchAsClient.js.map +1 -1
- package/dist/server/methods/discover.d.ts +3 -1
- package/dist/server/methods/discover.d.ts.map +1 -1
- package/dist/server/methods/discover.js +8 -6
- package/dist/server/methods/discover.js.map +1 -1
- package/dist/server/methods/entry_read.d.ts +4 -1
- package/dist/server/methods/entry_read.d.ts.map +1 -1
- package/dist/server/methods/entry_read.js +54 -52
- package/dist/server/methods/entry_read.js.map +1 -1
- package/dist/server/methods/log_read.d.ts +4 -1
- package/dist/server/methods/log_read.d.ts.map +1 -1
- package/dist/server/methods/log_read.js +38 -36
- package/dist/server/methods/log_read.js.map +1 -1
- package/dist/server/methods/loop_cancel.d.ts +3 -1
- package/dist/server/methods/loop_cancel.d.ts.map +1 -1
- package/dist/server/methods/loop_cancel.js +19 -17
- package/dist/server/methods/loop_cancel.js.map +1 -1
- package/dist/server/methods/loop_resolve.d.ts +3 -1
- package/dist/server/methods/loop_resolve.d.ts.map +1 -1
- package/dist/server/methods/loop_resolve.js +42 -40
- package/dist/server/methods/loop_resolve.js.map +1 -1
- package/dist/server/methods/loop_run.d.ts +3 -1
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +104 -102
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/op_copy.d.ts +3 -1
- package/dist/server/methods/op_copy.d.ts.map +1 -1
- package/dist/server/methods/op_copy.js +25 -23
- package/dist/server/methods/op_copy.js.map +1 -1
- package/dist/server/methods/op_dispatch.d.ts +3 -1
- package/dist/server/methods/op_dispatch.d.ts.map +1 -1
- package/dist/server/methods/op_dispatch.js +18 -16
- package/dist/server/methods/op_dispatch.js.map +1 -1
- package/dist/server/methods/op_edit.d.ts +3 -1
- package/dist/server/methods/op_edit.d.ts.map +1 -1
- package/dist/server/methods/op_edit.js +23 -21
- package/dist/server/methods/op_edit.js.map +1 -1
- package/dist/server/methods/op_exec.d.ts +3 -1
- package/dist/server/methods/op_exec.d.ts.map +1 -1
- package/dist/server/methods/op_exec.js +20 -18
- package/dist/server/methods/op_exec.js.map +1 -1
- package/dist/server/methods/op_find.d.ts +3 -1
- package/dist/server/methods/op_find.d.ts.map +1 -1
- package/dist/server/methods/op_find.js +23 -21
- package/dist/server/methods/op_find.js.map +1 -1
- package/dist/server/methods/op_hide.d.ts +3 -1
- package/dist/server/methods/op_hide.d.ts.map +1 -1
- package/dist/server/methods/op_hide.js +23 -21
- package/dist/server/methods/op_hide.js.map +1 -1
- package/dist/server/methods/op_move.d.ts +3 -1
- package/dist/server/methods/op_move.d.ts.map +1 -1
- package/dist/server/methods/op_move.js +23 -21
- package/dist/server/methods/op_move.js.map +1 -1
- package/dist/server/methods/op_parse.d.ts +3 -1
- package/dist/server/methods/op_parse.d.ts.map +1 -1
- package/dist/server/methods/op_parse.js +24 -22
- package/dist/server/methods/op_parse.js.map +1 -1
- package/dist/server/methods/op_read.d.ts +3 -1
- package/dist/server/methods/op_read.d.ts.map +1 -1
- package/dist/server/methods/op_read.js +23 -21
- package/dist/server/methods/op_read.js.map +1 -1
- package/dist/server/methods/op_send.d.ts +3 -1
- package/dist/server/methods/op_send.d.ts.map +1 -1
- package/dist/server/methods/op_send.js +22 -20
- package/dist/server/methods/op_send.js.map +1 -1
- package/dist/server/methods/op_show.d.ts +3 -1
- package/dist/server/methods/op_show.d.ts.map +1 -1
- package/dist/server/methods/op_show.js +23 -21
- package/dist/server/methods/op_show.js.map +1 -1
- package/dist/server/methods/ping.d.ts +3 -1
- package/dist/server/methods/ping.d.ts.map +1 -1
- package/dist/server/methods/ping.js +8 -6
- package/dist/server/methods/ping.js.map +1 -1
- package/dist/server/methods/providers_list.d.ts +3 -1
- package/dist/server/methods/providers_list.d.ts.map +1 -1
- package/dist/server/methods/providers_list.js +19 -17
- package/dist/server/methods/providers_list.js.map +1 -1
- package/dist/server/methods/session_attach.d.ts +3 -1
- package/dist/server/methods/session_attach.d.ts.map +1 -1
- package/dist/server/methods/session_attach.js +43 -41
- package/dist/server/methods/session_attach.js.map +1 -1
- package/dist/server/methods/session_create.d.ts +3 -1
- package/dist/server/methods/session_create.d.ts.map +1 -1
- package/dist/server/methods/session_create.js +51 -49
- package/dist/server/methods/session_create.js.map +1 -1
- package/dist/server/methods/session_list.d.ts +3 -1
- package/dist/server/methods/session_list.d.ts.map +1 -1
- package/dist/server/methods/session_list.js +9 -7
- package/dist/server/methods/session_list.js.map +1 -1
- package/dist/server/methods/session_runs.d.ts +3 -1
- package/dist/server/methods/session_runs.d.ts.map +1 -1
- package/dist/server/methods/session_runs.js +19 -17
- package/dist/server/methods/session_runs.js.map +1 -1
- package/dist/server/methods/session_set_persona.d.ts +3 -1
- package/dist/server/methods/session_set_persona.d.ts.map +1 -1
- package/dist/server/methods/session_set_persona.js +28 -26
- package/dist/server/methods/session_set_persona.js.map +1 -1
- package/dist/server/methods/session_set_root.d.ts +3 -1
- package/dist/server/methods/session_set_root.d.ts.map +1 -1
- package/dist/server/methods/session_set_root.js +31 -29
- package/dist/server/methods/session_set_root.js.map +1 -1
- package/dist/server/noProposals.d.ts +6 -0
- package/dist/server/noProposals.d.ts.map +1 -0
- package/dist/server/noProposals.js +37 -0
- package/dist/server/noProposals.js.map +1 -0
- package/dist/server/yolo.d.ts +3 -1
- package/dist/server/yolo.d.ts.map +1 -1
- package/dist/server/yolo.js +15 -13
- package/dist/server/yolo.js.map +1 -1
- package/package.json +71 -32
- package/bin/plurnk-service.js +0 -112
- /package/migrations/{001_schema.sql → 0000-00-00.01_schema.sql} +0 -0
package/dist/core/Engine.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
import { PlurnkParser, PlurnkParseError } from "@plurnk/plurnk-grammar";
|
|
2
3
|
import { Mimetypes, emptyRegistry } from "@plurnk/plurnk-mimetypes";
|
|
3
|
-
import
|
|
4
|
+
import EntryCrud from "../schemes/_entry-crud.js";
|
|
5
|
+
import EntryManifest from "../schemes/_entry-manifest.js";
|
|
6
|
+
import GitMembership from "./git-membership.js";
|
|
4
7
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
5
|
-
import {
|
|
6
|
-
//
|
|
7
|
-
// digest projection are structurally one function
|
|
8
|
-
//
|
|
9
|
-
import
|
|
8
|
+
import { LineMarkerOps, MimetypeBinary } from "../content/index.js";
|
|
9
|
+
// Shared module imported by both Engine and bin/digest.ts, so wire
|
|
10
|
+
// projection and digest projection are structurally one function — no
|
|
11
|
+
// drift between wire and digest possible.
|
|
12
|
+
import PacketWire from "./packet-wire.js";
|
|
10
13
|
// SPEC §3.6: writer must be in target scheme's manifest.writableBy.
|
|
11
14
|
// SHOW/HIDE/READ/FIND are not gated — they touch visibility metadata or read.
|
|
12
15
|
const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC"]);
|
|
13
16
|
const DEFAULT_PREVIEW_BUDGET = 256;
|
|
14
17
|
const DEFAULT_MAX_STRIKES = 3;
|
|
15
18
|
const DEFAULT_MAX_COMMANDS = 99;
|
|
19
|
+
const DEFAULT_BUDGET_CEILING = 0.9;
|
|
20
|
+
// Substituted into the budget readout after the assembled packet is measured
|
|
21
|
+
// (the figure depends on the packet's own rendered size — chicken/egg).
|
|
22
|
+
const TOKENS_FREE_PLACEHOLDER = "{{tokensFree}}";
|
|
23
|
+
const TOKEN_USAGE_PLACEHOLDER = "{{tokenUsage}}";
|
|
24
|
+
const TOKEN_PERCENT_PLACEHOLDER = "{{tokenPercent}}";
|
|
16
25
|
const readBudget = () => {
|
|
17
26
|
const raw = process.env.PLURNK_ENTRY_SIZE_DEFAULT_TOKENS;
|
|
18
27
|
if (raw === undefined || raw.length === 0)
|
|
@@ -22,6 +31,18 @@ const readBudget = () => {
|
|
|
22
31
|
return DEFAULT_PREVIEW_BUDGET;
|
|
23
32
|
return n;
|
|
24
33
|
};
|
|
34
|
+
// PLURNK_BUDGET_CEILING is dual-mode: <=1 is a fraction of the provider's
|
|
35
|
+
// context window, >1 is an absolute token wall — lets a demo pin a tiny
|
|
36
|
+
// ceiling regardless of the model's real window to force the grinder.
|
|
37
|
+
const readCeiling = () => {
|
|
38
|
+
const raw = process.env.PLURNK_BUDGET_CEILING;
|
|
39
|
+
if (raw === undefined || raw.length === 0)
|
|
40
|
+
return DEFAULT_BUDGET_CEILING;
|
|
41
|
+
const n = Number.parseFloat(raw);
|
|
42
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
43
|
+
return DEFAULT_BUDGET_CEILING;
|
|
44
|
+
return n;
|
|
45
|
+
};
|
|
25
46
|
const readMaxStrikes = () => {
|
|
26
47
|
const raw = process.env.PLURNK_MAX_STRIKES;
|
|
27
48
|
if (raw === undefined || raw.length === 0)
|
|
@@ -131,41 +152,59 @@ const fingerprintOp = (stmt) => {
|
|
|
131
152
|
}
|
|
132
153
|
return base;
|
|
133
154
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
class Engine {
|
|
156
|
+
static computeCeiling(contextSize, config) {
|
|
157
|
+
// Absolute wall (config > 1) is window-independent — the point of the >1
|
|
158
|
+
// mode is to pin a ceiling even when the provider reports no window; cap at
|
|
159
|
+
// the real window when one is known. Ratio mode needs a window to scale.
|
|
160
|
+
if (config > 1)
|
|
161
|
+
return contextSize === null ? Math.floor(config) : Math.min(Math.floor(config), contextSize);
|
|
162
|
+
return contextSize === null ? null : Math.floor(contextSize * config);
|
|
163
|
+
}
|
|
164
|
+
// Per-turn fingerprint: sorted set of per-op fingerprints, joined. Order
|
|
165
|
+
// within a turn doesn't matter — we want the SET of activities.
|
|
166
|
+
static fingerprintTurn(ops) {
|
|
167
|
+
return ops.map(fingerprintOp).toSorted().join(",");
|
|
168
|
+
}
|
|
169
|
+
// Rail #39 cycle detector. For each candidate period k in [1, maxCyclePeriod],
|
|
170
|
+
// check whether the last k*minCycles entries form minCycles repetitions of the
|
|
171
|
+
// same length-k pattern. O(maxCyclePeriod × minCycles × max k) ≈ tiny. Rummy
|
|
172
|
+
// parallel: src/plugins/error/error.js detectCycle.
|
|
173
|
+
static detectCycle(history, minCycles, maxCyclePeriod) {
|
|
174
|
+
for (let k = 1; k <= maxCyclePeriod; k++) {
|
|
175
|
+
const needed = k * minCycles;
|
|
176
|
+
if (history.length < needed)
|
|
177
|
+
continue;
|
|
178
|
+
const tail = history.slice(-needed);
|
|
179
|
+
const cycle = tail.slice(0, k);
|
|
180
|
+
let match = true;
|
|
181
|
+
outer: for (let rep = 0; rep < minCycles; rep++) {
|
|
182
|
+
for (let j = 0; j < k; j++) {
|
|
183
|
+
if (tail[rep * k + j] !== cycle[j]) {
|
|
184
|
+
match = false;
|
|
185
|
+
break outer;
|
|
186
|
+
}
|
|
156
187
|
}
|
|
157
188
|
}
|
|
189
|
+
if (match)
|
|
190
|
+
return { detected: true, period: k, cycles: minCycles };
|
|
158
191
|
}
|
|
159
|
-
|
|
160
|
-
return { detected: true, period: k, cycles: minCycles };
|
|
192
|
+
return { detected: false };
|
|
161
193
|
}
|
|
162
|
-
return { detected: false };
|
|
163
|
-
};
|
|
164
|
-
export default class Engine {
|
|
165
194
|
#db;
|
|
166
195
|
#schemes;
|
|
167
196
|
#mimetypes;
|
|
168
197
|
#previewBudget;
|
|
198
|
+
#budgetCeiling;
|
|
199
|
+
// Write-time tokenizer (SPEC §14.2). Synchronous per the provider
|
|
200
|
+
// contract (§2.1). Populated from the active provider's countTokens via
|
|
201
|
+
// the Daemon; a divisor tripwire stands in only for bare/standalone
|
|
202
|
+
// construction before a provider is wired (same boot affordance as
|
|
203
|
+
// Mimetypes, §4.5). Real counts come from provider.countTokens.
|
|
204
|
+
#tokenize;
|
|
205
|
+
// Boot-discovered runtime executors. Daemon builds + sets via
|
|
206
|
+
// setExecutors at start(); undefined until then (and in bare tests).
|
|
207
|
+
#executors;
|
|
169
208
|
// Per-loop transient buffer of actionless failures pending surface in the
|
|
170
209
|
// NEXT packet's user.telemetry.errors[]. Drained by #buildTelemetryErrors.
|
|
171
210
|
// Map<loopId, TelemetryError[]>. SPEC §15.1.
|
|
@@ -200,7 +239,7 @@ export default class Engine {
|
|
|
200
239
|
// status code but has no way to surface why the loop degraded.
|
|
201
240
|
// Per-grammar 0.17.0 protocol — see SPEC §15.1.
|
|
202
241
|
#telemetryEventNotify;
|
|
203
|
-
constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify }) {
|
|
242
|
+
constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify, tokenize }) {
|
|
204
243
|
this.#db = db;
|
|
205
244
|
this.#schemes = schemes;
|
|
206
245
|
this.#streamEventNotify = streamEventNotify;
|
|
@@ -214,6 +253,16 @@ export default class Engine {
|
|
|
214
253
|
discovery: { registry: emptyRegistry(), handlers: new Map() },
|
|
215
254
|
});
|
|
216
255
|
this.#previewBudget = readBudget();
|
|
256
|
+
this.#budgetCeiling = readCeiling();
|
|
257
|
+
// Tripwire default matches the Mimetypes boot affordance (SPEC §4.5):
|
|
258
|
+
// the divisor stands in only until the provider-backed tokenizer is
|
|
259
|
+
// wired by the Daemon. Real counts come from provider.countTokens.
|
|
260
|
+
this.#tokenize = tokenize ?? ((text) => Math.ceil(text.length / 4));
|
|
261
|
+
}
|
|
262
|
+
// Late injection: the executor registry is async-built at daemon start()
|
|
263
|
+
// (discover + probe), after Engine construction.
|
|
264
|
+
setExecutors(executors) {
|
|
265
|
+
this.#executors = executors;
|
|
217
266
|
}
|
|
218
267
|
#pushTelemetry(sessionId, loopId, event) {
|
|
219
268
|
const existing = this.#telemetryBuffer.get(loopId);
|
|
@@ -305,6 +354,12 @@ export default class Engine {
|
|
|
305
354
|
turnNumber: turnIds.length + 1, maxTurns,
|
|
306
355
|
});
|
|
307
356
|
turnIds.push(turn.turnId);
|
|
357
|
+
// SPEC §14.4: budget hard-stop — packet won't fit even collapsed → abandon.
|
|
358
|
+
if (turn.budgetHardStop) {
|
|
359
|
+
await this.#db.engine_loop_cancel.run({ loop_id: loopId });
|
|
360
|
+
cleanup("forceful", "budget_overflow");
|
|
361
|
+
return { turnIds, finalStatus: 499, hitMaxTurns: false, reason: "budget_overflow" };
|
|
362
|
+
}
|
|
308
363
|
// Rail #39: cycle detection. Push this turn's fingerprint to
|
|
309
364
|
// history, scan for repetition patterns. Detection bumps
|
|
310
365
|
// turnErrors so the strike system handles abandonment
|
|
@@ -315,9 +370,12 @@ export default class Engine {
|
|
|
315
370
|
// reason for treating the turn as a failure, not its own alert.
|
|
316
371
|
const state = this.#strikeState.get(loopId) ?? { streak: 0, turnErrors: 0, history: [] };
|
|
317
372
|
state.history.push(turn.fingerprint);
|
|
318
|
-
const cycle = detectCycle(state.history, minCycles, maxCyclePeriod);
|
|
373
|
+
const cycle = _a.detectCycle(state.history, minCycles, maxCyclePeriod);
|
|
319
374
|
if (cycle.detected)
|
|
320
375
|
state.turnErrors++;
|
|
376
|
+
// SPEC §14.4: a non-soft grinder fire counts toward the strike streak.
|
|
377
|
+
if (turn.budgetStruck)
|
|
378
|
+
state.turnErrors++;
|
|
321
379
|
this.#strikeState.set(loopId, state);
|
|
322
380
|
// Rail #38: strike accounting. Three sources strike a turn:
|
|
323
381
|
// 1. recordedFailed — any action-entry at hard failure status
|
|
@@ -409,13 +467,61 @@ export default class Engine {
|
|
|
409
467
|
nextActionIndex++;
|
|
410
468
|
}
|
|
411
469
|
}
|
|
470
|
+
// plurnk://manifest.json — rewritten EVERY turn (a live view of the
|
|
471
|
+
// entry set, which changes each turn). A derived view like the index,
|
|
472
|
+
// NOT an action — written directly (Engine.inject's path): no log entry,
|
|
473
|
+
// no sequence slot, not dispatched. The catalog body is built in the
|
|
474
|
+
// schemes layer (_entry-manifest); the engine only orchestrates the
|
|
475
|
+
// per-turn write. Does not list itself.
|
|
476
|
+
const systemCtx = {
|
|
477
|
+
db: this.#db, sessionId, runId, loopId, turnId,
|
|
478
|
+
writer: "system",
|
|
479
|
+
signal: this.#loopAborts.get(loopId)?.signal,
|
|
480
|
+
streamEventNotify: this.#streamEventNotify,
|
|
481
|
+
wakeRunNotify: this.#wakeRunNotify,
|
|
482
|
+
tokenize: this.#tokenize,
|
|
483
|
+
mimetypes: this.#mimetypes,
|
|
484
|
+
pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
|
|
485
|
+
};
|
|
486
|
+
// SPEC §14.3 D4/D5 — git-ls-files workspace membership, resolved at
|
|
487
|
+
// prompt-composition (EMI is eager + relevance-bounded). When the
|
|
488
|
+
// session's project_root is a git working tree, tracked files are
|
|
489
|
+
// members without a client `add`; active members are materialized
|
|
490
|
+
// (disk → body channel + visibility) so they surface in the index
|
|
491
|
+
// below. No-ops on headless / non-git sessions. Runs BEFORE the
|
|
492
|
+
// manifest + index build so this turn's packet reflects them.
|
|
493
|
+
await GitMembership.indexGitMembership(systemCtx);
|
|
494
|
+
await EntryCrud.writeEntry("manifest.json", {
|
|
495
|
+
channels: { body: { content: await EntryManifest.buildManifestBody(systemCtx, this.#previewBudget), mimetype: "application/json" } },
|
|
496
|
+
tags: [],
|
|
497
|
+
}, systemCtx, "plurnk");
|
|
412
498
|
// Build the spec'd packet (Packet.json) request half. #buildLog
|
|
413
499
|
// queries log_entries scoped to the run — the prompt entry just
|
|
414
500
|
// written (if turn 1) is part of that query result.
|
|
415
|
-
|
|
501
|
+
let requestPacket = await this.#buildRequestPacket({
|
|
416
502
|
initialMessages: messages, persona, requirements, runId, loopId,
|
|
417
503
|
currentTurnSeq: seq, provider,
|
|
418
504
|
});
|
|
505
|
+
// SPEC §14.4 — budget grinder, pre-LLM: reclaim window on actual overflow.
|
|
506
|
+
const enforced = await this.#enforceBudget({
|
|
507
|
+
packet: requestPacket, provider, runId, loopId, turnId, sessionId, turnNumber,
|
|
508
|
+
rebuild: (telemetryErrors) => this.#buildRequestPacket({
|
|
509
|
+
initialMessages: messages, persona, requirements, runId, loopId,
|
|
510
|
+
currentTurnSeq: seq, provider, telemetryErrors,
|
|
511
|
+
}),
|
|
512
|
+
});
|
|
513
|
+
requestPacket = enforced.packet;
|
|
514
|
+
if (!enforced.fit) {
|
|
515
|
+
// Hard 413: won't fit even with only the manifest left. Skip the LLM,
|
|
516
|
+
// close the turn, and let runLoop abandon (499).
|
|
517
|
+
const hardPacket = this.#completePacket(requestPacket, { content: "", ops: [], reasoning: null }, null, provider);
|
|
518
|
+
await this.#db.engine_close_turn.run({
|
|
519
|
+
id: turnId, status: 413, packet: JSON.stringify(hardPacket),
|
|
520
|
+
usage_prompt: 0, usage_completion: 0, usage_cached: 0, usage_cost_pico: 0,
|
|
521
|
+
finish_reason: "budget_hard_stop", model: provider.model,
|
|
522
|
+
});
|
|
523
|
+
return { turnId, status: 413, statuses: [], fingerprint: "", budgetStruck: enforced.struck, budgetHardStop: true };
|
|
524
|
+
}
|
|
419
525
|
const modelMessages = this.#packetToWireMessages(requestPacket);
|
|
420
526
|
const response = await provider.generate({ messages: modelMessages, signal });
|
|
421
527
|
// Engine splits wire-level response: emission (content, reasoning,
|
|
@@ -506,7 +612,7 @@ export default class Engine {
|
|
|
506
612
|
// nothing. Strike accounting (engine-internal) treats it as a
|
|
507
613
|
// struck turn; the model just sees an empty packet next turn.
|
|
508
614
|
// Per SPEC §15.1 gamification policy.
|
|
509
|
-
return { turnId, status: turnStatus, statuses, fingerprint: fingerprintTurn(packetAssistant.ops) };
|
|
615
|
+
return { turnId, status: turnStatus, statuses, fingerprint: _a.fingerprintTurn(packetAssistant.ops), budgetStruck: enforced.struck, budgetHardStop: false };
|
|
510
616
|
}
|
|
511
617
|
// Split the wire-level ProviderResponse into the two destinations:
|
|
512
618
|
// packet.assistant gets the model's emission (content, ops, reasoning);
|
|
@@ -568,7 +674,7 @@ export default class Engine {
|
|
|
568
674
|
// and §user) BEFORE the provider call. The same packet object is then
|
|
569
675
|
// completed with assistant + assistantRaw after the model responds, so
|
|
570
676
|
// the stored packet and the wire payload share one source of truth.
|
|
571
|
-
async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, }) {
|
|
677
|
+
async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, telemetryErrors: presetTelemetry, }) {
|
|
572
678
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
573
679
|
const system_definition = byRole("system");
|
|
574
680
|
// user.prompt sources from the loop's most recent prompt entry first
|
|
@@ -587,65 +693,117 @@ export default class Engine {
|
|
|
587
693
|
const persona = (row?.persona !== undefined && row?.persona !== null) ? row.persona : defaultPersona;
|
|
588
694
|
const index = await this.#buildIndex(runId, loopId);
|
|
589
695
|
const log = await this.#buildLog(runId);
|
|
590
|
-
const telemetryErrors = await this.#buildTelemetryErrors(loopId, currentTurnSeq);
|
|
696
|
+
const telemetryErrors = presetTelemetry ?? await this.#buildTelemetryErrors(loopId, currentTurnSeq);
|
|
591
697
|
// Per-section render-cost subtotals via provider's tokenizer.
|
|
592
698
|
// Engine approximates each section by tokenizing its serialized
|
|
593
699
|
// form — wire-payload tokens may differ slightly because chat-
|
|
594
700
|
// template scaffolding adds bytes, but the subtotal tracks "what
|
|
595
701
|
// the model has to process" closely enough for budget diagnostics.
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
return {
|
|
609
|
-
system: {
|
|
610
|
-
tokens: systemTokens,
|
|
611
|
-
system_definition,
|
|
612
|
-
persona,
|
|
613
|
-
index,
|
|
614
|
-
log,
|
|
615
|
-
},
|
|
616
|
-
user: {
|
|
617
|
-
tokens: userTokens,
|
|
618
|
-
prompt,
|
|
619
|
-
telemetry: { budget, errors: telemetryErrors },
|
|
620
|
-
system_requirements: requirements,
|
|
621
|
-
},
|
|
702
|
+
const countTokens = (t) => provider.countTokens(t);
|
|
703
|
+
// Budget readout (SPEC.md §14.2). Two-pass: measure the wire-rendered
|
|
704
|
+
// index/log sections (budget-independent), install the readout with a
|
|
705
|
+
// tokensFree placeholder, measure the assembled total, resolve free,
|
|
706
|
+
// substitute. Subtotals come from the real render — meta and fences
|
|
707
|
+
// included — not a serialized approximation. ceiling is the provider's
|
|
708
|
+
// window × PLURNK_BUDGET_CEILING (null when no window is reported →
|
|
709
|
+
// headline omitted, section lines still shown).
|
|
710
|
+
const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
711
|
+
const scratch = {
|
|
712
|
+
system: { system_definition, persona, index, log },
|
|
713
|
+
user: { prompt, telemetry: { budget: "", errors: telemetryErrors }, system_requirements: requirements },
|
|
622
714
|
};
|
|
715
|
+
const sections = PacketWire.measureBudgetSections(scratch, countTokens);
|
|
716
|
+
scratch.user.telemetry.budget = this.#renderBudget(sections, ceiling);
|
|
717
|
+
const total = countTokens(PacketWire.renderSystemContent(scratch.system)) + countTokens(PacketWire.renderUserContent(scratch.user));
|
|
718
|
+
const tokensFree = ceiling === null ? null : Math.max(0, ceiling - total);
|
|
719
|
+
const percent = ceiling === null ? null : Math.round((total / ceiling) * 100);
|
|
720
|
+
const budget = tokensFree === null
|
|
721
|
+
? scratch.user.telemetry.budget
|
|
722
|
+
: scratch.user.telemetry.budget
|
|
723
|
+
.replace(TOKEN_USAGE_PLACEHOLDER, String(total))
|
|
724
|
+
.replace(TOKEN_PERCENT_PLACEHOLDER, String(percent))
|
|
725
|
+
.replace(TOKENS_FREE_PLACEHOLDER, String(tokensFree));
|
|
726
|
+
const system = { tokens: 0, system_definition, persona, index, log };
|
|
727
|
+
const user = { tokens: 0, prompt, telemetry: { budget, errors: telemetryErrors }, system_requirements: requirements };
|
|
728
|
+
system.tokens = countTokens(PacketWire.renderSystemContent(system));
|
|
729
|
+
user.tokens = countTokens(PacketWire.renderUserContent(user));
|
|
730
|
+
return { system, user };
|
|
731
|
+
}
|
|
732
|
+
// Budget readout body, rendered into the `# Plurnk System Budget` section.
|
|
733
|
+
// Headline `ceiling/free` only when a ceiling exists; section lines for the
|
|
734
|
+
// curatable index/log weight the model can HIDE back. tokensFree is a
|
|
735
|
+
// placeholder here — buildSystem substitutes it after measuring the packet.
|
|
736
|
+
#renderBudget(sections, ceiling) {
|
|
737
|
+
const lines = [];
|
|
738
|
+
if (ceiling !== null)
|
|
739
|
+
lines.push(`ceiling ${ceiling} · usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · free ${TOKENS_FREE_PLACEHOLDER}`);
|
|
740
|
+
if (sections.index.channels > 0)
|
|
741
|
+
lines.push(`Index previews: ${sections.index.channels} channels, ${sections.index.tokens} tokens`);
|
|
742
|
+
if (sections.log.entries > 0) {
|
|
743
|
+
lines.push(`Log entries: ${sections.log.entries} entries, ${sections.log.tokens} tokens`);
|
|
744
|
+
if (sections.log.byScheme.length > 0) {
|
|
745
|
+
lines.push("| scheme | entries | tokens |", "|---|--:|--:|");
|
|
746
|
+
for (const s of sections.log.byScheme)
|
|
747
|
+
lines.push(`| ${s.scheme} | ${s.entries} | ${s.tokens} |`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return lines.join("\n");
|
|
623
751
|
}
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
#
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
752
|
+
// SPEC §14.4 — the budget grinder. Runs pre-LLM (in runTurn, after the packet
|
|
753
|
+
// is built, before provider.generate); fires only on actual overflow. Two
|
|
754
|
+
// passes, re-measuring between. Hides (never deletes) — the prior turn's logs,
|
|
755
|
+
// then the catalog except the manifest lifeline. The strike it raises and the
|
|
756
|
+
// hard-stop it can signal are returned to runLoop, which owns abandonment.
|
|
757
|
+
async #enforceBudget({ packet, provider, runId, loopId, turnId, sessionId, turnNumber, rebuild }) {
|
|
758
|
+
const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
|
|
759
|
+
const measure = (p) => p.system.tokens + p.user.tokens;
|
|
760
|
+
if (ceiling === null || measure(packet) <= ceiling)
|
|
761
|
+
return { packet, fit: true, struck: false };
|
|
762
|
+
const hidden = new Map();
|
|
763
|
+
const note = (scheme) => { hidden.set(scheme, (hidden.get(scheme) ?? 0) + 1); };
|
|
764
|
+
// Pass 1 — prior-turn rollback: hide the latest emissions (the ones that
|
|
765
|
+
// pushed it over). No prior turn (turn 1, env overflow) → no-op → pass 2.
|
|
766
|
+
const priorLogs = await this.#db.engine_grinder_prior_turn_logs.all({ loop_id: loopId, turn_id: turnId });
|
|
767
|
+
for (const le of priorLogs)
|
|
768
|
+
note(le.scheme ?? "log");
|
|
769
|
+
if (priorLogs.length > 0)
|
|
770
|
+
await this.#db.engine_grinder_hide_prior_turn_logs.run({ loop_id: loopId, turn_id: turnId });
|
|
771
|
+
const errors = packet.user.telemetry.errors;
|
|
772
|
+
let current = priorLogs.length > 0 ? await rebuild(errors) : packet;
|
|
773
|
+
if (measure(current) <= ceiling) {
|
|
774
|
+
this.#emitBudgetOverflow(sessionId, loopId, hidden);
|
|
775
|
+
return { packet: current, fit: true, struck: turnNumber > 1 };
|
|
776
|
+
}
|
|
777
|
+
// Pass 2 — index collapse: hide every catalog entry except the manifest.
|
|
778
|
+
const catalog = await this.#db.engine_grinder_catalog.all({ run_id: runId, session_id: sessionId });
|
|
779
|
+
for (const c of catalog)
|
|
780
|
+
note(c.scheme);
|
|
781
|
+
if (catalog.length > 0) {
|
|
782
|
+
const pairs = JSON.stringify(catalog.map((c) => ({ entry_id: c.entry_id, channel: c.channel })));
|
|
783
|
+
await this.#db.engine_grinder_hide_catalog.run({ run_id: runId, pairs });
|
|
784
|
+
}
|
|
785
|
+
current = catalog.length > 0 ? await rebuild(errors) : current;
|
|
786
|
+
this.#emitBudgetOverflow(sessionId, loopId, hidden);
|
|
787
|
+
return { packet: current, fit: measure(current) <= ceiling, struck: turnNumber > 1 };
|
|
642
788
|
}
|
|
643
|
-
//
|
|
644
|
-
//
|
|
789
|
+
// The model-facing budget event (SPEC §14.4, §15.1): which entries left the
|
|
790
|
+
// window, by scheme — the model's own terms, no mechanism vocabulary. The
|
|
791
|
+
// strike this overflow triggers stays engine-internal (gamification policy).
|
|
792
|
+
#emitBudgetOverflow(sessionId, loopId, hidden) {
|
|
793
|
+
if (hidden.size === 0)
|
|
794
|
+
return;
|
|
795
|
+
this.#pushTelemetry(sessionId, loopId, {
|
|
796
|
+
source: "engine:rail",
|
|
797
|
+
kind: "budget_overflow",
|
|
798
|
+
hidden: [...hidden.entries()].map(([scheme, count]) => ({ scheme, count })),
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
// Wire projection lives in ./packet-wire.ts so Engine and
|
|
802
|
+
// bin/digest.ts import the exact same function — structurally one
|
|
645
803
|
// implementation, no drift between wire and digest possible.
|
|
646
804
|
// Format: markdown (user pick over rummy's XML alternative, 2026-05-22).
|
|
647
805
|
#packetToWireMessages(packet) {
|
|
648
|
-
return packetToWireMessages(packet);
|
|
806
|
+
return PacketWire.packetToWireMessages(packet);
|
|
649
807
|
}
|
|
650
808
|
// Complete the packet by adding the model's response. After this the
|
|
651
809
|
// packet matches Packet.json fully and is ready for storage.
|
|
@@ -779,6 +937,7 @@ export default class Engine {
|
|
|
779
937
|
content: result.preview,
|
|
780
938
|
mimetype: row.mimetype,
|
|
781
939
|
tokens: row.tokens,
|
|
940
|
+
lines: result.totalLines,
|
|
782
941
|
};
|
|
783
942
|
}
|
|
784
943
|
return [...entries.values()];
|
|
@@ -793,7 +952,9 @@ export default class Engine {
|
|
|
793
952
|
streamEventNotify: this.#streamEventNotify,
|
|
794
953
|
wakeRunNotify: this.#wakeRunNotify,
|
|
795
954
|
mimetypes: this.#mimetypes,
|
|
955
|
+
tokenize: this.#tokenize,
|
|
796
956
|
pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
|
|
957
|
+
executors: this.#executors,
|
|
797
958
|
};
|
|
798
959
|
let result;
|
|
799
960
|
let denial = this.#checkWritable(statement, origin);
|
|
@@ -846,6 +1007,13 @@ export default class Engine {
|
|
|
846
1007
|
// 202 in the result the caller sees, so runTurn never branches on
|
|
847
1008
|
// a pending state.
|
|
848
1009
|
if (result.status === 202) {
|
|
1010
|
+
// Effect-gated auto-run (read/pure runtimes, plurnk-service#182):
|
|
1011
|
+
// no human gate, no loop/proposal notification. Accept + apply
|
|
1012
|
+
// in-process; the model sees the outcome directly, never a review.
|
|
1013
|
+
if (result.attrs?.inline === true) {
|
|
1014
|
+
const effective = await this.#runApplyResolution(statement, result, { decision: "accept" }, { sessionId, runId, loopId, turnId });
|
|
1015
|
+
return this.#applyResolution(logEntryId, effective);
|
|
1016
|
+
}
|
|
849
1017
|
// Register the resolution waiter SYNCHRONOUSLY before any await
|
|
850
1018
|
// yields. A same-tick resolveProposal() (e.g. from a test that
|
|
851
1019
|
// awaits the onDispatch callback and immediately resolves) must
|
|
@@ -906,7 +1074,9 @@ export default class Engine {
|
|
|
906
1074
|
writer: "model", signal: this.#loopAborts.get(loopId)?.signal,
|
|
907
1075
|
streamEventNotify: this.#streamEventNotify,
|
|
908
1076
|
wakeRunNotify: this.#wakeRunNotify,
|
|
1077
|
+
tokenize: this.#tokenize,
|
|
909
1078
|
pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
|
|
1079
|
+
executors: this.#executors,
|
|
910
1080
|
};
|
|
911
1081
|
const applyResult = await handler.applyResolution({
|
|
912
1082
|
attrs: (originalResult.attrs ?? {}),
|
|
@@ -920,14 +1090,14 @@ export default class Engine {
|
|
|
920
1090
|
};
|
|
921
1091
|
}
|
|
922
1092
|
// Propagate applyResolution.outcome onto the accepted resolution
|
|
923
|
-
//
|
|
924
|
-
// (
|
|
925
|
-
//
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
return
|
|
1093
|
+
// (operational metadata, e.g. exec's "exit_N") AND its body — an
|
|
1094
|
+
// inline (read/pure) run returns its output as the body, which has
|
|
1095
|
+
// to reach the model-facing result this turn, not just stream to
|
|
1096
|
+
// the entry. Host accepts carry no body (fire-and-forget).
|
|
1097
|
+
const withOutcome = applyResult.outcome !== undefined && resolution.outcome === undefined
|
|
1098
|
+
? { ...resolution, outcome: applyResult.outcome }
|
|
1099
|
+
: resolution;
|
|
1100
|
+
return applyResult.body === undefined ? withOutcome : { ...withOutcome, body: applyResult.body };
|
|
931
1101
|
}
|
|
932
1102
|
catch (err) {
|
|
933
1103
|
return {
|
|
@@ -995,13 +1165,14 @@ export default class Engine {
|
|
|
995
1165
|
signal: this.#loopAborts.get(loopId)?.signal,
|
|
996
1166
|
streamEventNotify: this.#streamEventNotify,
|
|
997
1167
|
wakeRunNotify: this.#wakeRunNotify,
|
|
1168
|
+
tokenize: this.#tokenize,
|
|
998
1169
|
pushTelemetry: (event) => this.#pushTelemetry(sessionRow.session_id, loopId, event),
|
|
999
1170
|
};
|
|
1000
1171
|
const entry = {
|
|
1001
1172
|
channels: { body: { content: prompt, mimetype: "text/markdown" } },
|
|
1002
1173
|
tags: [],
|
|
1003
1174
|
};
|
|
1004
|
-
await writeEntry(pathname, entry, ctx, "plurnk");
|
|
1175
|
+
await EntryCrud.writeEntry(pathname, entry, ctx, "plurnk");
|
|
1005
1176
|
return { loopId, turnSeq };
|
|
1006
1177
|
}
|
|
1007
1178
|
// Subscribe to proposal-pending events. Daemon registers a listener
|
|
@@ -1062,13 +1233,15 @@ export default class Engine {
|
|
|
1062
1233
|
: decision === "reject" ? "rejected"
|
|
1063
1234
|
: "loop_aborted";
|
|
1064
1235
|
const outcome = resolution.outcome ?? defaultOutcome;
|
|
1065
|
-
// rx is the model-facing operation result.
|
|
1066
|
-
//
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1236
|
+
// rx is the model-facing operation result. Status always; outcome is
|
|
1237
|
+
// operational (stays on log_entries for forensics, never model-facing).
|
|
1238
|
+
// Body is normally dropped — the propose preview was an input echo —
|
|
1239
|
+
// EXCEPT an inline auto-run (read/pure) carries its run output AS the
|
|
1240
|
+
// body, which is exactly the "what happened" the model needs this turn.
|
|
1070
1241
|
// Per AGENTS.md "Operational hygiene on what the model sees."
|
|
1071
|
-
const rx =
|
|
1242
|
+
const rx = (decision === "accept" && resolution.body !== undefined)
|
|
1243
|
+
? JSON.stringify({ status, body: resolution.body })
|
|
1244
|
+
: JSON.stringify({ status });
|
|
1072
1245
|
await this.#db.engine_resolve_log_entry.run({
|
|
1073
1246
|
id: logEntryId, state, outcome, status_rx: status, rx,
|
|
1074
1247
|
});
|
|
@@ -1123,7 +1296,7 @@ export default class Engine {
|
|
|
1123
1296
|
return { status: 403, error: `writer '${origin}' is not in writableBy for scheme '${schemeName}'` };
|
|
1124
1297
|
}
|
|
1125
1298
|
// Per-loop flag gating. Schemes self-declare their flag affinity in
|
|
1126
|
-
// their manifest (
|
|
1299
|
+
// their manifest (excludedInAsk / requiresWeb /
|
|
1127
1300
|
// requiresInteraction); SchemeRegistry.resolveForLoop returns the
|
|
1128
1301
|
// active set under the loop's persisted flags. Anything outside the
|
|
1129
1302
|
// set returns 403 — action-entry-as-outcome carries the rejection.
|
|
@@ -1133,7 +1306,7 @@ export default class Engine {
|
|
|
1133
1306
|
return null;
|
|
1134
1307
|
const flags = await this.#loadLoopFlags(loopId);
|
|
1135
1308
|
// Fast path: default flags gate nothing. (yolo never gates.)
|
|
1136
|
-
if (!flags.
|
|
1309
|
+
if (!flags.noWeb && !flags.noInteraction && flags.mode === "act")
|
|
1137
1310
|
return null;
|
|
1138
1311
|
const active = this.#schemes.resolveForLoop(flags);
|
|
1139
1312
|
const check = (target) => {
|
|
@@ -1173,8 +1346,9 @@ export default class Engine {
|
|
|
1173
1346
|
const srcHandler = this.#schemes.get(srcSchemeName);
|
|
1174
1347
|
if (srcHandler === undefined || typeof srcHandler.deleteEntry !== "function")
|
|
1175
1348
|
return { status: 501 };
|
|
1176
|
-
//
|
|
1177
|
-
|
|
1349
|
+
// MOVE to /dev/null (the grammar's idiomatic delete) or a null-body
|
|
1350
|
+
// MOVE deletes the source entry. SPEC §6.5.
|
|
1351
|
+
if (dstPath === null || pathnameFromPath(dstPath) === "/dev/null") {
|
|
1178
1352
|
const srcPathname = pathnameFromPath(srcPath);
|
|
1179
1353
|
const delResult = await srcHandler.deleteEntry(srcPathname, ctx);
|
|
1180
1354
|
return { status: delResult.status };
|
|
@@ -1206,12 +1380,12 @@ export default class Engine {
|
|
|
1206
1380
|
if (srcResult.status !== 200 || srcResult.entry === null)
|
|
1207
1381
|
return { status: 404, error: `COPY/MOVE source not found: ${srcSchemeName}://${srcPathname}` };
|
|
1208
1382
|
const entry = srcResult.entry;
|
|
1209
|
-
//
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1383
|
+
// Destination read — the conflict/no-op verdict is deferred until the
|
|
1384
|
+
// to-be-written content is known (after <L> slice + tag resolution below),
|
|
1385
|
+
// so an identical re-copy resolves to 304 instead of a phantom 409.
|
|
1386
|
+
const dstExisting = typeof dstHandler.readEntry === "function"
|
|
1387
|
+
? await dstHandler.readEntry(dstPathname, ctx)
|
|
1388
|
+
: null;
|
|
1215
1389
|
// Mimetype compatibility check against the destination scheme's manifest
|
|
1216
1390
|
const dstManifest = dstHandler.constructor.manifest;
|
|
1217
1391
|
const dstChannels = dstManifest?.channels ?? {};
|
|
@@ -1230,10 +1404,10 @@ export default class Engine {
|
|
|
1230
1404
|
if (lineMarker !== null) {
|
|
1231
1405
|
const sliced = {};
|
|
1232
1406
|
for (const [channelName, channelData] of Object.entries(entry.channels)) {
|
|
1233
|
-
if (isBinaryMimetype(channelData.mimetype)) {
|
|
1407
|
+
if (MimetypeBinary.isBinaryMimetype(channelData.mimetype)) {
|
|
1234
1408
|
return { status: 415, error: `cannot slice <L> on binary channel '${channelName}' (${channelData.mimetype})` };
|
|
1235
1409
|
}
|
|
1236
|
-
const r = sliceLinesRaw(channelData.content ?? "", lineMarker);
|
|
1410
|
+
const r = LineMarkerOps.sliceLinesRaw(channelData.content ?? "", lineMarker);
|
|
1237
1411
|
if (r.status !== 200)
|
|
1238
1412
|
return { status: r.status, error: r.error };
|
|
1239
1413
|
sliced[channelName] = { ...channelData, content: r.text ?? "" };
|
|
@@ -1244,6 +1418,21 @@ export default class Engine {
|
|
|
1244
1418
|
const tags = (Array.isArray(statement.signal) && statement.signal.length > 0)
|
|
1245
1419
|
? statement.signal
|
|
1246
1420
|
: entry.tags;
|
|
1421
|
+
// 304/409 on an existing destination (SPEC §6.4): a re-copy that would write
|
|
1422
|
+
// exactly what's already there — same channel contents, same tags — is a no-op
|
|
1423
|
+
// (304), mirroring EDIT's 304-on-noop (§6.1). A divergent destination is a real
|
|
1424
|
+
// collision (409); COPY/MOVE never clobbers.
|
|
1425
|
+
if (dstExisting !== null && dstExisting.status === 200 && dstExisting.entry !== null) {
|
|
1426
|
+
const dstChannels = dstExisting.entry.channels;
|
|
1427
|
+
const writeNames = Object.keys(channels).sort();
|
|
1428
|
+
const dstNames = Object.keys(dstChannels).sort();
|
|
1429
|
+
const sameContent = writeNames.length === dstNames.length
|
|
1430
|
+
&& writeNames.every((n, i) => n === dstNames[i] && (channels[n]?.content ?? "") === (dstChannels[n]?.content ?? ""));
|
|
1431
|
+
const sameTags = [...tags].sort().join("") === [...dstExisting.entry.tags].sort().join("");
|
|
1432
|
+
if (sameContent && sameTags)
|
|
1433
|
+
return { status: 304 };
|
|
1434
|
+
return { status: 409, error: `COPY/MOVE destination exists: ${dstSchemeName}://${dstPathname}` };
|
|
1435
|
+
}
|
|
1247
1436
|
const writeResult = await dstHandler.writeEntry(dstPathname, { channels, tags }, ctx);
|
|
1248
1437
|
return { status: writeResult.status, entryId: writeResult.entryId, created: writeResult.created };
|
|
1249
1438
|
}
|
|
@@ -1322,6 +1511,8 @@ export default class Engine {
|
|
|
1322
1511
|
}
|
|
1323
1512
|
}
|
|
1324
1513
|
const attrs = JSON.stringify(attrsObj);
|
|
1514
|
+
const txJson = JSON.stringify(statement);
|
|
1515
|
+
const rxJson = JSON.stringify(result);
|
|
1325
1516
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
1326
1517
|
run_id: runId,
|
|
1327
1518
|
loop_id: loopId,
|
|
@@ -1340,11 +1531,12 @@ export default class Engine {
|
|
|
1340
1531
|
params: target.params,
|
|
1341
1532
|
fragment: target.fragment,
|
|
1342
1533
|
lineMarker: lineMarkerJson,
|
|
1343
|
-
tx:
|
|
1534
|
+
tx: txJson,
|
|
1344
1535
|
mimetype_tx: "application/json",
|
|
1345
|
-
rx:
|
|
1536
|
+
rx: rxJson,
|
|
1346
1537
|
mimetype_rx: "application/json",
|
|
1347
1538
|
status_rx: result.status,
|
|
1539
|
+
tokens: this.#tokenize(txJson) + this.#tokenize(rxJson),
|
|
1348
1540
|
state: isProposed ? "proposed" : "resolved",
|
|
1349
1541
|
outcome: null,
|
|
1350
1542
|
attrs,
|
|
@@ -1375,4 +1567,6 @@ export default class Engine {
|
|
|
1375
1567
|
return JSON.stringify(signal);
|
|
1376
1568
|
}
|
|
1377
1569
|
}
|
|
1570
|
+
_a = Engine;
|
|
1571
|
+
export default Engine;
|
|
1378
1572
|
//# sourceMappingURL=Engine.js.map
|