@plurnk/plurnk-service 0.4.0 → 0.6.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/README.md +13 -44
- package/SPEC.md +546 -445
- package/bin/plurnk-service.js +2 -1
- package/dist/core/ChannelWrite.d.ts +5 -0
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/Engine.d.ts +8 -5
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +190 -99
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/PluginLoader.js +6 -6
- package/dist/core/PluginLoader.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts +4 -0
- package/dist/core/ProviderInstantiate.d.ts.map +1 -0
- package/dist/core/ProviderInstantiate.js +41 -0
- package/dist/core/ProviderInstantiate.js.map +1 -0
- package/dist/core/ProviderRegistry.d.ts.map +1 -1
- package/dist/core/ProviderRegistry.js +1 -1
- package/dist/core/ProviderRegistry.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 +4 -20
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/line-marker.js +3 -3
- package/dist/core/line-marker.js.map +1 -1
- package/dist/core/matcher.d.ts +4 -6
- package/dist/core/matcher.d.ts.map +1 -1
- package/dist/core/matcher.js +61 -184
- package/dist/core/matcher.js.map +1 -1
- package/dist/core/mimetype-binary.js +1 -1
- package/dist/core/mimetype-binary.js.map +1 -1
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +130 -30
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/scheme-types.d.ts +3 -26
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js +5 -8
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +12 -24
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/File.d.ts +1 -0
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +11 -5
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +1 -0
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +10 -4
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +1 -1
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +15 -20
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/server/Daemon.d.ts +11 -0
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +18 -0
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/MethodRegistry.d.ts +2 -2
- package/dist/server/MethodRegistry.d.ts.map +1 -1
- package/dist/server/methods/loop_resolve.js +2 -2
- package/dist/server/methods/loop_resolve.js.map +1 -1
- package/dist/server/methods/loop_run.d.ts.map +1 -1
- package/dist/server/methods/loop_run.js +2 -1
- package/dist/server/methods/loop_run.js.map +1 -1
- package/dist/server/methods/providers_list.js +1 -1
- package/dist/server/methods/providers_list.js.map +1 -1
- package/package.json +17 -12
package/bin/plurnk-service.js
CHANGED
|
@@ -7,7 +7,8 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { dirname, resolve } from "node:path";
|
|
8
8
|
import Daemon from "../src/server/Daemon.ts";
|
|
9
9
|
import { parseEnvExample, formatFlagsHelp } from "../src/core/EnvFlags.ts";
|
|
10
|
-
import { loadActiveProvider
|
|
10
|
+
import { loadActiveProvider } from "../src/core/ProviderInstantiate.ts";
|
|
11
|
+
import { resolveActiveAlias } from "@plurnk/plurnk-providers";
|
|
11
12
|
|
|
12
13
|
const die = (code, message) => {
|
|
13
14
|
process.stderr.write(`${message}\n`);
|
|
@@ -17,6 +17,11 @@ export interface WakeRunPayload {
|
|
|
17
17
|
summary: string;
|
|
18
18
|
}
|
|
19
19
|
export type WakeRunNotify = (payload: WakeRunPayload) => void;
|
|
20
|
+
export interface TelemetryEventPayload {
|
|
21
|
+
loopId: number;
|
|
22
|
+
event: object;
|
|
23
|
+
}
|
|
24
|
+
export type TelemetryEventNotify = (sessionId: number, payload: TelemetryEventPayload) => void;
|
|
20
25
|
export declare const appendToChannel: (db: Db, { entryId, channel, chunk, notify }: {
|
|
21
26
|
entryId: number;
|
|
22
27
|
channel: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAQvF,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ChannelWrite.d.ts","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAQvF,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAQ9D,MAAM,WAAW,qBAAqB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAe/F,eAAO,MAAM,eAAe,GACxB,IAAI,EAAE,EACN,qCAAqC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,KACrH,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF,eAAO,MAAM,eAAe,GACxB,IAAI,EAAE,EACN,qCAAqC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAC;IAAC,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,KAC3H,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF,eAAO,MAAM,gBAAgB,GACzB,IAAI,EAAE,EACN,oCAAoC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACvG,OAAO,CAAC,MAAM,CAIhB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC1B,IAAI,EAAE,EACN,4BAA4B;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACvE,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAC/B,IAAI,EAAE,EACN,oBAAoB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KACvD,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAG/D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,mEAAmE;AACnE,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,2EAA2E;AAC3E,gCAAgC;
|
|
1
|
+
{"version":3,"file":"ChannelWrite.js","sourceRoot":"","sources":["../../src/core/ChannelWrite.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Engine.d.ts
CHANGED
|
@@ -3,13 +3,13 @@ import type SchemeRegistry from "./SchemeRegistry.ts";
|
|
|
3
3
|
import { Mimetypes } from "@plurnk/plurnk-mimetypes";
|
|
4
4
|
import type { Db } from "./Db.ts";
|
|
5
5
|
import type { LoopFlags } from "./scheme-types.ts";
|
|
6
|
-
import type { StreamEventNotify, WakeRunNotify } from "./ChannelWrite.ts";
|
|
6
|
+
import type { StreamEventNotify, TelemetryEventNotify, WakeRunNotify } from "./ChannelWrite.ts";
|
|
7
7
|
type Origin = "model" | "client" | "system" | "plugin";
|
|
8
8
|
type ChatMessage = {
|
|
9
9
|
role: "system" | "user" | "assistant";
|
|
10
10
|
content: string;
|
|
11
11
|
};
|
|
12
|
-
import type { Provider } from "
|
|
12
|
+
import type { Provider } from "@plurnk/plurnk-providers";
|
|
13
13
|
type DispatchContext = {
|
|
14
14
|
statement: PlurnkStatement;
|
|
15
15
|
sessionId: number;
|
|
@@ -56,17 +56,19 @@ export declare const detectCycle: (history: ReadonlyArray<string>, minCycles: nu
|
|
|
56
56
|
};
|
|
57
57
|
export default class Engine {
|
|
58
58
|
#private;
|
|
59
|
-
constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify }: {
|
|
59
|
+
constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify }: {
|
|
60
60
|
db: Db;
|
|
61
61
|
schemes: SchemeRegistry;
|
|
62
62
|
mimetypes?: Mimetypes;
|
|
63
63
|
streamEventNotify?: StreamEventNotify;
|
|
64
64
|
wakeRunNotify?: WakeRunNotify;
|
|
65
|
+
telemetryEventNotify?: TelemetryEventNotify;
|
|
65
66
|
});
|
|
66
|
-
runLoop({ provider, messages, persona, sessionId, runId, loopId, maxTurns, maxStrikes, minCycles, maxCyclePeriod, origin, signal, onDispatch, }: {
|
|
67
|
+
runLoop({ provider, messages, persona, requirements, sessionId, runId, loopId, maxTurns, maxStrikes, minCycles, maxCyclePeriod, origin, signal, onDispatch, }: {
|
|
67
68
|
provider: Provider;
|
|
68
69
|
messages: ChatMessage[];
|
|
69
70
|
persona?: string;
|
|
71
|
+
requirements?: string;
|
|
70
72
|
sessionId: number;
|
|
71
73
|
runId: number;
|
|
72
74
|
loopId: number;
|
|
@@ -83,10 +85,11 @@ export default class Engine {
|
|
|
83
85
|
hitMaxTurns: boolean;
|
|
84
86
|
reason: "max_turns" | "strike_threshold" | "external" | null;
|
|
85
87
|
}>;
|
|
86
|
-
runTurn({ provider, messages, persona, sessionId, runId, loopId, origin, signal, onDispatch, turnNumber, maxTurns, }: {
|
|
88
|
+
runTurn({ provider, messages, persona, requirements, sessionId, runId, loopId, origin, signal, onDispatch, turnNumber, maxTurns, }: {
|
|
87
89
|
provider: Provider;
|
|
88
90
|
messages: ChatMessage[];
|
|
89
91
|
persona?: string;
|
|
92
|
+
requirements?: string;
|
|
90
93
|
sessionId: number;
|
|
91
94
|
runId: number;
|
|
92
95
|
loopId: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAA4D,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../../src/core/Engine.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Engine.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { PlurnkParser } from "@plurnk/plurnk-grammar";
|
|
1
|
+
import { PlurnkParser, PlurnkParseError } from "@plurnk/plurnk-grammar";
|
|
2
2
|
import { Mimetypes, emptyRegistry } from "@plurnk/plurnk-mimetypes";
|
|
3
3
|
import { writeEntry } from "../schemes/_entry-crud.js";
|
|
4
4
|
import { DEFAULT_LOOP_FLAGS } from "./scheme-types.js";
|
|
5
|
-
import { sliceLinesRaw } from "
|
|
6
|
-
import { isBinaryMimetype } from "./mimetype-binary.js";
|
|
5
|
+
import { sliceLinesRaw, isBinaryMimetype } from "@plurnk/plurnk-schemes";
|
|
7
6
|
// Plain JS module shared with bin/digest.js so wire projection and
|
|
8
7
|
// digest projection are structurally one function. tsconfig.build.json
|
|
9
8
|
// has allowJs:true so this gets copied through to dist/.
|
|
10
9
|
import { packetToWireMessages } from "./packet-wire.js";
|
|
11
|
-
//
|
|
10
|
+
// SPEC §3.6: writer must be in target scheme's manifest.writableBy.
|
|
12
11
|
// SHOW/HIDE/READ/FIND are not gated — they touch visibility metadata or read.
|
|
13
12
|
const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC"]);
|
|
14
13
|
const DEFAULT_PREVIEW_BUDGET = 256;
|
|
15
14
|
const DEFAULT_MAX_STRIKES = 3;
|
|
15
|
+
const DEFAULT_MAX_COMMANDS = 99;
|
|
16
16
|
const readBudget = () => {
|
|
17
17
|
const raw = process.env.PLURNK_ENTRY_SIZE_DEFAULT_TOKENS;
|
|
18
18
|
if (raw === undefined || raw.length === 0)
|
|
@@ -31,8 +31,17 @@ const readMaxStrikes = () => {
|
|
|
31
31
|
return DEFAULT_MAX_STRIKES;
|
|
32
32
|
return n;
|
|
33
33
|
};
|
|
34
|
+
const readMaxCommands = () => {
|
|
35
|
+
const raw = process.env.PLURNK_MAX_COMMANDS;
|
|
36
|
+
if (raw === undefined || raw.length === 0)
|
|
37
|
+
return DEFAULT_MAX_COMMANDS;
|
|
38
|
+
const n = Number.parseInt(raw, 10);
|
|
39
|
+
if (!Number.isFinite(n) || n < 1)
|
|
40
|
+
return DEFAULT_MAX_COMMANDS;
|
|
41
|
+
return n;
|
|
42
|
+
};
|
|
34
43
|
// Resolution timeout — proposed entries auto-cancel if nothing arrives
|
|
35
|
-
// within this window.
|
|
44
|
+
// within this window. SPEC.md §0.5 (proposal lifecycle) + §13.5 (loop.resolve).
|
|
36
45
|
const PROPOSAL_TIMEOUT_DEFAULT_MS = 300000;
|
|
37
46
|
const readProposalTimeoutMs = () => {
|
|
38
47
|
const raw = process.env.PLURNK_PROPOSAL_TIMEOUT_MS;
|
|
@@ -166,11 +175,10 @@ export default class Engine {
|
|
|
166
175
|
// rails (cycle detection #39, etc.) — read and reset at end of each turn.
|
|
167
176
|
// `history` holds per-turn fingerprints for rail #39 cycle detection.
|
|
168
177
|
#strikeState = new Map();
|
|
169
|
-
// Proposal lifecycle
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// §Phase E for the broader lifecycle plan.
|
|
178
|
+
// Proposal lifecycle: pending dispatch pauses waiting for resolution.
|
|
179
|
+
// Engine.runTurn awaits the promise when a scheme returns status 202;
|
|
180
|
+
// Engine.resolveProposal feeds the resolution back in. Map is per-log-
|
|
181
|
+
// entry-id; entries clear on resolution. SPEC.md §0.5 + §13.5 (loop.resolve).
|
|
174
182
|
#pendingProposals = new Map();
|
|
175
183
|
// External observers of proposal lifecycle events. Daemon subscribes
|
|
176
184
|
// here to push `loop/proposal` notifications when an entry enters
|
|
@@ -186,11 +194,18 @@ export default class Engine {
|
|
|
186
194
|
#loopAborts = new Map();
|
|
187
195
|
#streamEventNotify;
|
|
188
196
|
#wakeRunNotify;
|
|
189
|
-
|
|
197
|
+
// Telemetry event fan-out: every TelemetryEvent pushed to the loop's
|
|
198
|
+
// buffer is also broadcast live to the connected client(s) on the
|
|
199
|
+
// session. Without this, the client sees `loop/terminated` with a
|
|
200
|
+
// status code but has no way to surface why the loop degraded.
|
|
201
|
+
// Per-grammar 0.17.0 protocol — see SPEC §15.1.
|
|
202
|
+
#telemetryEventNotify;
|
|
203
|
+
constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify }) {
|
|
190
204
|
this.#db = db;
|
|
191
205
|
this.#schemes = schemes;
|
|
192
206
|
this.#streamEventNotify = streamEventNotify;
|
|
193
207
|
this.#wakeRunNotify = wakeRunNotify;
|
|
208
|
+
this.#telemetryEventNotify = telemetryEventNotify;
|
|
194
209
|
// Default to empty discovery — standalone Engine construction (in
|
|
195
210
|
// tests) gets no handlers, and content flows through the framework's
|
|
196
211
|
// raw-content fitContent fallback. Daemon-managed Engine receives a
|
|
@@ -200,12 +215,16 @@ export default class Engine {
|
|
|
200
215
|
});
|
|
201
216
|
this.#previewBudget = readBudget();
|
|
202
217
|
}
|
|
203
|
-
#pushTelemetry(loopId,
|
|
218
|
+
#pushTelemetry(sessionId, loopId, event) {
|
|
204
219
|
const existing = this.#telemetryBuffer.get(loopId);
|
|
205
220
|
if (existing === undefined)
|
|
206
|
-
this.#telemetryBuffer.set(loopId, [
|
|
221
|
+
this.#telemetryBuffer.set(loopId, [event]);
|
|
207
222
|
else
|
|
208
|
-
existing.push(
|
|
223
|
+
existing.push(event);
|
|
224
|
+
// Live fan-out: client sees the event the moment it lands in the
|
|
225
|
+
// model's buffer (not at the next packet build). Same envelope on
|
|
226
|
+
// both sides per the grammar 0.17.0 TelemetryEvent protocol.
|
|
227
|
+
this.#telemetryEventNotify?.(sessionId, { loopId, event });
|
|
209
228
|
}
|
|
210
229
|
#drainTelemetry(loopId) {
|
|
211
230
|
const buf = this.#telemetryBuffer.get(loopId);
|
|
@@ -214,7 +233,28 @@ export default class Engine {
|
|
|
214
233
|
this.#telemetryBuffer.delete(loopId);
|
|
215
234
|
return buf;
|
|
216
235
|
}
|
|
217
|
-
|
|
236
|
+
// Pull a ±windowLines context block around `targetLine` (1-based) from
|
|
237
|
+
// `content`, formatted with N:\t prefixes — same shape the model
|
|
238
|
+
// already knows from READ output and numbered Index entries. Used to
|
|
239
|
+
// give parse_error telemetry concrete locality: the model sees what
|
|
240
|
+
// it wrote on the offending line, not just an abstract error message.
|
|
241
|
+
//
|
|
242
|
+
// Lenient: targetLine ≤ 0 clamps to 1; targetLine beyond content
|
|
243
|
+
// returns whatever overlap exists; empty content returns "".
|
|
244
|
+
#extractSnippet(content, targetLine, windowLines) {
|
|
245
|
+
if (content.length === 0)
|
|
246
|
+
return "";
|
|
247
|
+
const lines = content.split("\n");
|
|
248
|
+
const target = Math.max(1, targetLine);
|
|
249
|
+
const start = Math.max(1, target - windowLines);
|
|
250
|
+
const end = Math.min(lines.length, target + windowLines);
|
|
251
|
+
const slice = [];
|
|
252
|
+
for (let i = start; i <= end; i++) {
|
|
253
|
+
slice.push(`${i}:\t${lines[i - 1] ?? ""}`);
|
|
254
|
+
}
|
|
255
|
+
return slice.join("\n");
|
|
256
|
+
}
|
|
257
|
+
async runLoop({ provider, messages, persona = "", requirements = "", sessionId, runId, loopId, maxTurns = 50, maxStrikes = readMaxStrikes(), minCycles = readPositiveInt("PLURNK_MIN_CYCLES", DEFAULT_MIN_CYCLES), maxCyclePeriod = readPositiveInt("PLURNK_MAX_CYCLE_PERIOD", DEFAULT_MAX_CYCLE_PERIOD), origin = "model", signal, onDispatch, }) {
|
|
218
258
|
const turnIds = [];
|
|
219
259
|
const suddenDeathThreshold = maxTurns - maxStrikes;
|
|
220
260
|
// Per-loop AbortController for scheme-side cancellation propagation.
|
|
@@ -261,25 +301,23 @@ export default class Engine {
|
|
|
261
301
|
return { turnIds, finalStatus: 499, hitMaxTurns: true, reason: "max_turns" };
|
|
262
302
|
}
|
|
263
303
|
const turn = await this.runTurn({
|
|
264
|
-
provider, messages, persona, sessionId, runId, loopId, origin, signal, onDispatch,
|
|
304
|
+
provider, messages, persona, requirements, sessionId, runId, loopId, origin, signal, onDispatch,
|
|
265
305
|
turnNumber: turnIds.length + 1, maxTurns,
|
|
266
306
|
});
|
|
267
307
|
turnIds.push(turn.turnId);
|
|
268
308
|
// Rail #39: cycle detection. Push this turn's fingerprint to
|
|
269
309
|
// history, scan for repetition patterns. Detection bumps
|
|
270
|
-
// turnErrors so the strike system handles abandonment
|
|
310
|
+
// turnErrors so the strike system handles abandonment
|
|
311
|
+
// naturally — same internal-only role rummy gave it
|
|
312
|
+
// (plugins/error/error.js#verdict). Intentionally NOT a
|
|
313
|
+
// model-facing telemetry kind: model sees the strike pile-up
|
|
314
|
+
// (which IS the actionable signal); cycle is the engine's
|
|
315
|
+
// reason for treating the turn as a failure, not its own alert.
|
|
271
316
|
const state = this.#strikeState.get(loopId) ?? { streak: 0, turnErrors: 0, history: [] };
|
|
272
317
|
state.history.push(turn.fingerprint);
|
|
273
318
|
const cycle = detectCycle(state.history, minCycles, maxCyclePeriod);
|
|
274
|
-
if (cycle.detected)
|
|
319
|
+
if (cycle.detected)
|
|
275
320
|
state.turnErrors++;
|
|
276
|
-
this.#pushTelemetry(loopId, {
|
|
277
|
-
kind: "cycle",
|
|
278
|
-
period: cycle.period,
|
|
279
|
-
cycles: cycle.cycles,
|
|
280
|
-
message: "Loop detected",
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
321
|
this.#strikeState.set(loopId, state);
|
|
284
322
|
// Rail #38: strike accounting. Three sources strike a turn:
|
|
285
323
|
// 1. recordedFailed — any action-entry at hard failure status
|
|
@@ -287,19 +325,19 @@ export default class Engine {
|
|
|
287
325
|
// 2. noOps — turn.status === TURN_STATUS_NO_OPS (per #41).
|
|
288
326
|
// 3. turnErrors — externally bumped by per-turn rails (#39 cycle).
|
|
289
327
|
// Struck → streak++; clean → streak = 0. Threshold → abandon.
|
|
328
|
+
// Strike accounting is engine-internal bookkeeping. Per rummy
|
|
329
|
+
// precedent (plugins/error/error.js#verdict) and SPEC §15.1
|
|
330
|
+
// policy: model sees errors that happened (parse_error,
|
|
331
|
+
// action_failure), never the engine's accounting about them
|
|
332
|
+
// (strike counts, cycle detection, sudden-death threshold).
|
|
333
|
+
// Surfacing internal state to the model creates a gamification
|
|
334
|
+
// surface — model optimizes for engine metrics rather than
|
|
335
|
+
// task progress.
|
|
290
336
|
const recordedFailed = turn.statuses.some((s) => s >= 400 && !SOFT_FAILURE_STATUSES.has(s));
|
|
291
337
|
const noOps = turn.status === TURN_STATUS_NO_OPS;
|
|
292
338
|
const struck = noOps || recordedFailed || state.turnErrors > 0;
|
|
293
339
|
if (struck) {
|
|
294
340
|
state.streak++;
|
|
295
|
-
const reason = noOps ? "no_ops" : recordedFailed ? "recorded_failure" : "rail";
|
|
296
|
-
this.#pushTelemetry(loopId, {
|
|
297
|
-
kind: "strike",
|
|
298
|
-
streak: state.streak,
|
|
299
|
-
maxStrikes,
|
|
300
|
-
reason,
|
|
301
|
-
message: `strike ${state.streak}/${maxStrikes} (${reason}); ${maxStrikes - state.streak} before abandonment`,
|
|
302
|
-
});
|
|
303
341
|
if (state.streak >= maxStrikes) {
|
|
304
342
|
await this.#db.engine_loop_cancel.run({ loop_id: loopId });
|
|
305
343
|
cleanup("forceful", "strike_threshold");
|
|
@@ -311,20 +349,15 @@ export default class Engine {
|
|
|
311
349
|
}
|
|
312
350
|
state.turnErrors = 0;
|
|
313
351
|
this.#strikeState.set(loopId, state);
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
// Soft: no strike, no loop-status change. SPEC §15.1.
|
|
352
|
+
// Sudden-death threshold is engine-internal — abandonment
|
|
353
|
+
// happens at maxTurns regardless. Per gamification policy:
|
|
354
|
+
// we don't warn the model that it's nearing our limit.
|
|
318
355
|
if (turnIds.length >= suddenDeathThreshold && turnIds.length < maxTurns) {
|
|
319
|
-
|
|
320
|
-
kind: "sudden_death",
|
|
321
|
-
message: `${turnIds.length} of ${maxTurns}`,
|
|
322
|
-
remaining: maxTurns - turnIds.length,
|
|
323
|
-
});
|
|
356
|
+
// Threshold tripped; engine bookkeeping only.
|
|
324
357
|
}
|
|
325
358
|
}
|
|
326
359
|
}
|
|
327
|
-
async runTurn({ provider, messages, persona = "", sessionId, runId, loopId, origin = "model", signal, onDispatch, turnNumber = 1, maxTurns = 50, }) {
|
|
360
|
+
async runTurn({ provider, messages, persona = "", requirements = "", sessionId, runId, loopId, origin = "model", signal, onDispatch, turnNumber = 1, maxTurns = 50, }) {
|
|
328
361
|
// === Turn-as-container model ===
|
|
329
362
|
//
|
|
330
363
|
// Turn rows are created at runTurn OPEN (status=102, placeholder
|
|
@@ -380,8 +413,8 @@ export default class Engine {
|
|
|
380
413
|
// queries log_entries scoped to the run — the prompt entry just
|
|
381
414
|
// written (if turn 1) is part of that query result.
|
|
382
415
|
const requestPacket = await this.#buildRequestPacket({
|
|
383
|
-
initialMessages: messages, persona, runId, loopId,
|
|
384
|
-
|
|
416
|
+
initialMessages: messages, persona, requirements, runId, loopId,
|
|
417
|
+
currentTurnSeq: seq, provider,
|
|
385
418
|
});
|
|
386
419
|
const modelMessages = this.#packetToWireMessages(requestPacket);
|
|
387
420
|
const response = await provider.generate({ messages: modelMessages, signal });
|
|
@@ -395,8 +428,23 @@ export default class Engine {
|
|
|
395
428
|
// body starting with `//` being interpreted as xpath) silently
|
|
396
429
|
// drop, the model sees zero ops dispatched, strike-rail fires,
|
|
397
430
|
// model has no feedback on WHY its emission didn't take effect.
|
|
398
|
-
|
|
399
|
-
|
|
431
|
+
//
|
|
432
|
+
// Envelope per @plurnk/plurnk-grammar 0.17.0 TelemetryEvent:
|
|
433
|
+
// { source, kind, message, position: { type: "content-offset", line, column } }
|
|
434
|
+
// Plus a `snippet` field (additionalProperties) carrying ±N lines
|
|
435
|
+
// of the assistant's own content around the error line. Without
|
|
436
|
+
// the snippet, the model sees "invalid xpath at 1:0" but can't
|
|
437
|
+
// connect that to what IT wrote — and tends to regenerate the
|
|
438
|
+
// same broken emission. See edit-todo demo for the canonical case.
|
|
439
|
+
for (const { message, line, column, source } of parseErrors ?? []) {
|
|
440
|
+
this.#pushTelemetry(sessionId, loopId, {
|
|
441
|
+
source: "grammar",
|
|
442
|
+
kind: "parse_error",
|
|
443
|
+
message,
|
|
444
|
+
position: { type: "content-offset", line, column },
|
|
445
|
+
snippet: this.#extractSnippet(packetAssistant.content, line, 2),
|
|
446
|
+
parserSource: source,
|
|
447
|
+
});
|
|
400
448
|
}
|
|
401
449
|
const opsCount = packetAssistant.ops.length;
|
|
402
450
|
const sendOp = packetAssistant.ops.findLast((op) => op.op === "SEND" && typeof op.signal === "number");
|
|
@@ -422,8 +470,19 @@ export default class Engine {
|
|
|
422
470
|
});
|
|
423
471
|
// Dispatch model ops starting at nextActionIndex (continues the
|
|
424
472
|
// turn's running counter after any pre-model writes).
|
|
473
|
+
//
|
|
474
|
+
// Max-commands cap: a single emission with more than `maxCommands`
|
|
475
|
+
// ops is the runaway-loop fingerprint observed in pathological cases
|
|
476
|
+
// (html-attrs demo: 635 ops in one turn). Cap dispatches at the
|
|
477
|
+
// configured limit; overflow ops are dropped without per-op log
|
|
478
|
+
// entries (avoids bloating forensics with hundreds of identical refusals)
|
|
479
|
+
// and the model gets a single telemetry signal next packet so it knows
|
|
480
|
+
// its emission was truncated.
|
|
481
|
+
const maxCommands = readMaxCommands();
|
|
482
|
+
const opsToDispatch = packetAssistant.ops.slice(0, maxCommands);
|
|
483
|
+
const droppedCount = opsCount - opsToDispatch.length;
|
|
425
484
|
const statuses = [];
|
|
426
|
-
for (const [i, statement] of
|
|
485
|
+
for (const [i, statement] of opsToDispatch.entries()) {
|
|
427
486
|
const result = await this.dispatch({
|
|
428
487
|
statement, sessionId, runId, loopId, turnId,
|
|
429
488
|
sequence: nextActionIndex + i,
|
|
@@ -431,23 +490,30 @@ export default class Engine {
|
|
|
431
490
|
});
|
|
432
491
|
statuses.push(result.status);
|
|
433
492
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
this.#pushTelemetry(loopId, {
|
|
440
|
-
|
|
441
|
-
|
|
493
|
+
// max_commands_exceeded IS model-facing: dropped ops are things
|
|
494
|
+
// the model emitted that didn't run — it needs to know. Engine
|
|
495
|
+
// bookkeeping (the cap value, our threshold reasoning) stays
|
|
496
|
+
// internal; only the facts of what happened are reported.
|
|
497
|
+
if (droppedCount > 0) {
|
|
498
|
+
this.#pushTelemetry(sessionId, loopId, {
|
|
499
|
+
source: "engine:rail",
|
|
500
|
+
kind: "max_commands_exceeded",
|
|
501
|
+
emitted: opsCount,
|
|
502
|
+
dropped: droppedCount,
|
|
442
503
|
});
|
|
443
504
|
}
|
|
505
|
+
// Zero ops is NOT an error to report — the model knows it emitted
|
|
506
|
+
// nothing. Strike accounting (engine-internal) treats it as a
|
|
507
|
+
// struck turn; the model just sees an empty packet next turn.
|
|
508
|
+
// Per SPEC §15.1 gamification policy.
|
|
444
509
|
return { turnId, status: turnStatus, statuses, fingerprint: fingerprintTurn(packetAssistant.ops) };
|
|
445
510
|
}
|
|
446
511
|
// Split the wire-level ProviderResponse into the two destinations:
|
|
447
512
|
// packet.assistant gets the model's emission (content, ops, reasoning);
|
|
448
513
|
// Turn columns get the call-metadata (usage, finishReason, model).
|
|
449
|
-
//
|
|
450
|
-
// owns the parse and the scraping rule, providers stay
|
|
514
|
+
// SPEC §2.1 / plurnk-providers#1: text-fragment scraping policy lives
|
|
515
|
+
// here — engine owns the parse and the scraping rule, providers stay
|
|
516
|
+
// grammar-unaware.
|
|
451
517
|
//
|
|
452
518
|
// Test-fixture escape hatch: the Mock provider may pre-supply `ops` on
|
|
453
519
|
// its assistant payload to skip the parse roundtrip. The wire Provider
|
|
@@ -458,6 +524,10 @@ export default class Engine {
|
|
|
458
524
|
const preParsedOps = assistant.ops;
|
|
459
525
|
const ops = [];
|
|
460
526
|
const textFragments = [];
|
|
527
|
+
// Full PlurnkParseError context (line/column/source) is preserved
|
|
528
|
+
// here so runTurn can build TelemetryEvent envelopes per the
|
|
529
|
+
// grammar 0.17.0 protocol — model needs position info to locate
|
|
530
|
+
// its own offending content on the next turn.
|
|
461
531
|
const parseErrors = [];
|
|
462
532
|
if (preParsedOps !== undefined) {
|
|
463
533
|
ops.push(...preParsedOps);
|
|
@@ -474,8 +544,13 @@ export default class Engine {
|
|
|
474
544
|
}
|
|
475
545
|
else if (item.kind === "error") {
|
|
476
546
|
const err = item.error;
|
|
477
|
-
|
|
478
|
-
|
|
547
|
+
if (err instanceof PlurnkParseError) {
|
|
548
|
+
parseErrors.push({ message: err.message, line: err.line, column: err.column, source: err.source });
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
const msg = err?.message ?? "parse error";
|
|
552
|
+
parseErrors.push({ message: msg, line: 0, column: 0, source: "parser" });
|
|
553
|
+
}
|
|
479
554
|
}
|
|
480
555
|
}
|
|
481
556
|
}
|
|
@@ -493,10 +568,7 @@ export default class Engine {
|
|
|
493
568
|
// and §user) BEFORE the provider call. The same packet object is then
|
|
494
569
|
// completed with assistant + assistantRaw after the model responds, so
|
|
495
570
|
// the stored packet and the wire payload share one source of truth.
|
|
496
|
-
|
|
497
|
-
// a continuation turn"; the turn-N-of-M continuation marker rides on
|
|
498
|
-
// user.system_requirements (per-turn rules), NOT a mutated prompt.
|
|
499
|
-
async #buildRequestPacket({ initialMessages, persona: defaultPersona, runId, loopId, turnNumber, currentTurnSeq, maxTurns, provider, }) {
|
|
571
|
+
async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, }) {
|
|
500
572
|
const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
|
|
501
573
|
const system_definition = byRole("system");
|
|
502
574
|
// user.prompt sources from the loop's most recent prompt entry first
|
|
@@ -516,13 +588,6 @@ export default class Engine {
|
|
|
516
588
|
const index = await this.#buildIndex(runId, loopId);
|
|
517
589
|
const log = await this.#buildLog(runId);
|
|
518
590
|
const telemetryErrors = await this.#buildTelemetryErrors(loopId, currentTurnSeq);
|
|
519
|
-
// Rummy AgentLoop.js #buildContinuationPrompt: literally
|
|
520
|
-
// `Turn ${turn}/${maxTurns}`. That's the whole string. The model
|
|
521
|
-
// can read the action log to see what it already did; it does
|
|
522
|
-
// not need editorial instructions from us about not repeating.
|
|
523
|
-
const system_requirements = turnNumber > 1
|
|
524
|
-
? `Turn ${turnNumber}/${maxTurns}`
|
|
525
|
-
: "";
|
|
526
591
|
// Per-section render-cost subtotals via provider's tokenizer.
|
|
527
592
|
// Engine approximates each section by tokenizing its serialized
|
|
528
593
|
// form — wire-payload tokens may differ slightly because chat-
|
|
@@ -533,13 +598,13 @@ export default class Engine {
|
|
|
533
598
|
provider.countTokens(JSON.stringify(index)) +
|
|
534
599
|
provider.countTokens(JSON.stringify(log));
|
|
535
600
|
// user.telemetry.budget — shimmed with section-aggregate table.
|
|
536
|
-
// Real per-scheme breakdown is
|
|
537
|
-
//
|
|
538
|
-
const budget = this.#renderBudgetShim(systemTokens, provider, prompt,
|
|
601
|
+
// Real per-scheme breakdown is the tokenomics chapter (SPEC.md §14.2);
|
|
602
|
+
// depends on provider.getContextSize and write-time per-row tokens.
|
|
603
|
+
const budget = this.#renderBudgetShim(systemTokens, provider, prompt, telemetryErrors);
|
|
539
604
|
const userTokens = provider.countTokens(prompt) +
|
|
540
|
-
provider.countTokens(system_requirements) +
|
|
541
605
|
provider.countTokens(JSON.stringify(telemetryErrors)) +
|
|
542
|
-
provider.countTokens(budget)
|
|
606
|
+
provider.countTokens(budget) +
|
|
607
|
+
provider.countTokens(requirements);
|
|
543
608
|
return {
|
|
544
609
|
system: {
|
|
545
610
|
tokens: systemTokens,
|
|
@@ -552,20 +617,19 @@ export default class Engine {
|
|
|
552
617
|
tokens: userTokens,
|
|
553
618
|
prompt,
|
|
554
619
|
telemetry: { budget, errors: telemetryErrors },
|
|
555
|
-
system_requirements,
|
|
620
|
+
system_requirements: requirements,
|
|
556
621
|
},
|
|
557
622
|
};
|
|
558
623
|
}
|
|
559
|
-
// user.telemetry.budget — SHIM.
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
// aggregate counts we already compute so the wire's
|
|
563
|
-
// Budget` section is non-empty for picking-apart purposes.
|
|
564
|
-
#renderBudgetShim(systemTokens, provider, prompt,
|
|
624
|
+
// user.telemetry.budget — SHIM. Real per-scheme breakdown +
|
|
625
|
+
// context-window "free/percent-of-total" is SPEC.md §14.2; depends on
|
|
626
|
+
// provider.getContextSize and write-time per-row tokens. Until then,
|
|
627
|
+
// render the section-aggregate counts we already compute so the wire's
|
|
628
|
+
// `# Plurnk System Budget` section is non-empty for picking-apart purposes.
|
|
629
|
+
#renderBudgetShim(systemTokens, provider, prompt, telemetryErrors) {
|
|
565
630
|
const userPromptTokens = provider.countTokens(prompt);
|
|
566
|
-
const userReqTokens = provider.countTokens(system_requirements);
|
|
567
631
|
const userErrTokens = provider.countTokens(JSON.stringify(telemetryErrors));
|
|
568
|
-
const userTokens = userPromptTokens +
|
|
632
|
+
const userTokens = userPromptTokens + userErrTokens;
|
|
569
633
|
const total = systemTokens + userTokens;
|
|
570
634
|
const pct = (n) => total === 0 ? "0.0%" : `${((n / total) * 100).toFixed(1)}%`;
|
|
571
635
|
return [
|
|
@@ -618,7 +682,7 @@ export default class Engine {
|
|
|
618
682
|
op: r.op,
|
|
619
683
|
target,
|
|
620
684
|
status: r.status_rx,
|
|
621
|
-
|
|
685
|
+
error: typeof parsedRx === "object" && parsedRx !== null && "error" in parsedRx
|
|
622
686
|
? parsedRx.error
|
|
623
687
|
: typeof parsedRx === "string" ? parsedRx : "",
|
|
624
688
|
};
|
|
@@ -738,7 +802,7 @@ export default class Engine {
|
|
|
738
802
|
result = denial;
|
|
739
803
|
}
|
|
740
804
|
else {
|
|
741
|
-
//
|
|
805
|
+
// SPEC §3.6 + plurnk-schemes#1: action-entry-as-outcome. Scheme-handler
|
|
742
806
|
// exceptions become the action-entry's outcome (status 500), not a
|
|
743
807
|
// thrown bubble. The log_entry is the durable record; engine never
|
|
744
808
|
// skips it. Logging failures (#writeLog throws) are NOT caught —
|
|
@@ -773,13 +837,13 @@ export default class Engine {
|
|
|
773
837
|
}
|
|
774
838
|
const logEntryId = await this.#writeLog({ statement, result, runId, loopId, turnId, sequence, origin });
|
|
775
839
|
onDispatch?.(logEntryId);
|
|
776
|
-
// Proposal lifecycle (
|
|
777
|
-
// returns status 202, the entry is written as state='proposed';
|
|
840
|
+
// Proposal lifecycle (SPEC.md §0.5 + §13.5 loop.resolve). When a
|
|
841
|
+
// scheme returns status 202, the entry is written as state='proposed';
|
|
778
842
|
// dispatch then PAUSES on a per-entry waiter until resolution
|
|
779
843
|
// arrives via Engine.resolveProposal (from the loop/resolve RPC,
|
|
780
|
-
// YOLO listener, or timeout
|
|
781
|
-
//
|
|
782
|
-
//
|
|
844
|
+
// YOLO listener, or timeout). The post-resolution status replaces
|
|
845
|
+
// 202 in the result the caller sees, so runTurn never branches on
|
|
846
|
+
// a pending state.
|
|
783
847
|
if (result.status === 202) {
|
|
784
848
|
// Register the resolution waiter SYNCHRONOUSLY before any await
|
|
785
849
|
// yields. A same-tick resolveProposal() (e.g. from a test that
|
|
@@ -1007,8 +1071,8 @@ export default class Engine {
|
|
|
1007
1071
|
});
|
|
1008
1072
|
return { status, outcome, body: resolution.body };
|
|
1009
1073
|
}
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1074
|
+
// SPEC §3.6: engine rejects writes whose origin is outside the target
|
|
1075
|
+
// scheme's manifest.writableBy.
|
|
1012
1076
|
// - Read-side ops (READ, FIND, SHOW, HIDE) are not gated.
|
|
1013
1077
|
// - SEND broadcast (path=null) has no target scheme; not gated.
|
|
1014
1078
|
// - COPY: dst scheme writableBy applies.
|
|
@@ -1154,8 +1218,8 @@ export default class Engine {
|
|
|
1154
1218
|
return { status: 415, error: `mimetype mismatch on channel '${channelName}': ${channelData.mimetype} vs ${expectedMimetype}` };
|
|
1155
1219
|
}
|
|
1156
1220
|
}
|
|
1157
|
-
// `<L>` source range slicing per
|
|
1158
|
-
//
|
|
1221
|
+
// `<L>` source range slicing per SPEC.md §16.9 (symmetric with READ
|
|
1222
|
+
// `<L>` — source range, no line-number prefix).
|
|
1159
1223
|
// Applied to every channel of the source entry. Binary channels return
|
|
1160
1224
|
// 415 since line semantics don't apply.
|
|
1161
1225
|
const lineMarker = statement.lineMarker ?? null;
|
|
@@ -1225,9 +1289,36 @@ export default class Engine {
|
|
|
1225
1289
|
// consumes on accept. All other statuses are terminal — state =
|
|
1226
1290
|
// 'resolved' for the common case.
|
|
1227
1291
|
const isProposed = result.status === 202;
|
|
1228
|
-
|
|
1229
|
-
?
|
|
1230
|
-
:
|
|
1292
|
+
let attrsObj = (result.attrs !== undefined && result.attrs !== null)
|
|
1293
|
+
? { ...result.attrs }
|
|
1294
|
+
: {};
|
|
1295
|
+
// EXEC pathname is coordinate-based — the stream entry lives at
|
|
1296
|
+
// exec://<loop_seq>/<turn_seq>/<sequence>/EXEC, mirroring the log
|
|
1297
|
+
// row's own URI at log://<...>/EXEC. The model can correlate the
|
|
1298
|
+
// log meta to its stream output by structural parallelism — no
|
|
1299
|
+
// random slug injection. Coordinate is resolved here because both
|
|
1300
|
+
// log row write and applyResolution need it.
|
|
1301
|
+
if (statement.op === "EXEC") {
|
|
1302
|
+
const seqs = await this.#db.engine_loop_turn_seqs.get({
|
|
1303
|
+
loop_id: loopId, turn_id: turnId,
|
|
1304
|
+
});
|
|
1305
|
+
if (seqs === undefined)
|
|
1306
|
+
throw new Error(`Engine.#writeLog: loop_turn_seqs returned no row for loop=${loopId} turn=${turnId}`);
|
|
1307
|
+
const coordPathname = `${seqs.loop_seq}/${seqs.turn_seq}/${sequence}/EXEC`;
|
|
1308
|
+
target.scheme = "exec";
|
|
1309
|
+
target.pathname = coordPathname;
|
|
1310
|
+
attrsObj.pathname = coordPathname;
|
|
1311
|
+
// Mutate the in-memory result.attrs too: the dispatch path
|
|
1312
|
+
// hands originalResult.attrs to handler.applyResolution after
|
|
1313
|
+
// proposal accept (see #acceptResolution). Both views — the
|
|
1314
|
+
// stored row AND the in-memory proposal — need the same
|
|
1315
|
+
// coordinate-based pathname so applyResolution writes the
|
|
1316
|
+
// entry at exec://<coord>/EXEC.
|
|
1317
|
+
if (result.attrs !== undefined && result.attrs !== null) {
|
|
1318
|
+
result.attrs.pathname = coordPathname;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const attrs = JSON.stringify(attrsObj);
|
|
1231
1322
|
const row = await this.#db.engine_insert_log_entry.get({
|
|
1232
1323
|
run_id: runId,
|
|
1233
1324
|
loop_id: loopId,
|