@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/packet-wire.js
CHANGED
|
@@ -8,334 +8,398 @@
|
|
|
8
8
|
// 2026-05-22. Standard markdown idioms only — headers as section delimiters,
|
|
9
9
|
// fenced code blocks for entry bodies, lists for arrays. No invented
|
|
10
10
|
// separators. Models parse markdown natively.
|
|
11
|
+
//
|
|
11
12
|
// Section headers follow the `# Plurnk System X` convention so the model
|
|
12
13
|
// sees consistent framing across every section it might receive. Sections
|
|
13
14
|
// with no content are omitted entirely (no empty headers in the wire).
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// # Plurnk System
|
|
19
|
-
// # Plurnk System
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
import { MimetypeBinary } from "../content/index.js";
|
|
16
|
+
export default class PacketWire {
|
|
17
|
+
// Render packet.system → system message content (markdown string).
|
|
18
|
+
// {system_definition verbatim}
|
|
19
|
+
// # Plurnk System Instructions (persona)
|
|
20
|
+
// # Plurnk System Index (entries — only when present)
|
|
21
|
+
// # Plurnk System Log (log entries — only when present)
|
|
22
|
+
static renderSystemContent(system) {
|
|
23
|
+
const parts = [system.system_definition];
|
|
24
|
+
if (typeof system.persona === "string" && system.persona.length > 0) {
|
|
25
|
+
parts.push(`# Plurnk System Instructions\n\n${system.persona}`);
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(system.index) && system.index.length > 0) {
|
|
28
|
+
parts.push(`# Plurnk System Index\n\n${PacketWire.#renderIndexEntries(system.index)}`);
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(system.log) && system.log.length > 0) {
|
|
31
|
+
parts.push(`# Plurnk System Log\n\n${PacketWire.#renderLogEntries(system.log)}`);
|
|
32
|
+
}
|
|
33
|
+
return parts.map((p) => p.replace(/\n+$/, "")).join("\n\n");
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
// # Plurnk System
|
|
35
|
-
// # Plurnk System
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
// Render packet.user → user message content (markdown string).
|
|
36
|
+
// # Plurnk System User Prompt
|
|
37
|
+
// # Plurnk System Budget (token budget table — only when present)
|
|
38
|
+
// # Plurnk System Errors (telemetry errors — only when present)
|
|
39
|
+
// # Plurnk System Requirements (static per-turn rules — only when present)
|
|
40
|
+
// Requirements renders LAST so the contract the model has to honor is the
|
|
41
|
+
// most recent thing in the user message — closest to the assistant turn.
|
|
42
|
+
static renderUserContent(user) {
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (typeof user.prompt === "string" && user.prompt.length > 0) {
|
|
45
|
+
parts.push(`# Plurnk System User Prompt\n\n${user.prompt}`);
|
|
46
|
+
}
|
|
47
|
+
const telemetry = user.telemetry ?? { budget: "", errors: [] };
|
|
48
|
+
if (typeof telemetry.budget === "string" && telemetry.budget.length > 0) {
|
|
49
|
+
parts.push(`# Plurnk System Budget\n\n${telemetry.budget}`);
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(telemetry.errors) && telemetry.errors.length > 0) {
|
|
52
|
+
parts.push(`# Plurnk System Errors\n\n${PacketWire.#renderTelemetryErrors(telemetry.errors)}`);
|
|
53
|
+
}
|
|
54
|
+
if (typeof user.system_requirements === "string" && user.system_requirements.length > 0) {
|
|
55
|
+
parts.push(`# Plurnk System Requirements\n\n${user.system_requirements}`);
|
|
56
|
+
}
|
|
57
|
+
return parts.map((p) => p.replace(/\n+$/, "")).join("\n\n");
|
|
44
58
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
// Project the full request half of a packet to ChatMessage[] for the wire.
|
|
60
|
+
// Engine calls this directly; the result is what provider.generate receives.
|
|
61
|
+
static packetToWireMessages(packet) {
|
|
62
|
+
return [
|
|
63
|
+
{ role: "system", content: PacketWire.renderSystemContent(packet.system) },
|
|
64
|
+
{ role: "user", content: PacketWire.renderUserContent(packet.user) },
|
|
65
|
+
];
|
|
48
66
|
}
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
// Measure the wire-rendered token cost of the curatable sections (index,
|
|
68
|
+
// log) plus the assembled total, using the provider's tokenizer. The budget
|
|
69
|
+
// readout uses this so its subtotals match what actually ships — meta lines
|
|
70
|
+
// and fences included — not a serialized approximation. `total` is measured
|
|
71
|
+
// over whatever the packet currently holds, so the caller renders the budget
|
|
72
|
+
// with a `{{tokensFree}}` placeholder, measures, then substitutes (the
|
|
73
|
+
// placeholder/number length delta is negligible).
|
|
74
|
+
static measureBudgetSections(packet, countTokens) {
|
|
75
|
+
const system = packet.system;
|
|
76
|
+
const user = packet.user;
|
|
77
|
+
const indexEntries = Array.isArray(system.index) ? system.index : [];
|
|
78
|
+
const logEntries = Array.isArray(system.log) ? system.log : [];
|
|
79
|
+
const indexBody = indexEntries.length > 0 ? PacketWire.#renderIndexEntries(indexEntries) : "";
|
|
80
|
+
const logBody = logEntries.length > 0 ? PacketWire.#renderLogEntries(logEntries) : "";
|
|
81
|
+
// A channel renders iff its content is a non-empty string (renderIndexEntries).
|
|
82
|
+
const indexChannels = indexEntries.reduce((n, e) => n + Object.values(e.channels ?? {}).filter((ch) => typeof ch.content === "string" && ch.content.length > 0).length, 0);
|
|
83
|
+
// Per-scheme log breakdown (§14.2 {§14.2-per-scheme-balance}): each entry's
|
|
84
|
+
// render-weight grouped by the scheme it acted on, heaviest first — the
|
|
85
|
+
// model's "what's eating my window" signal and its HIDE target. Render-
|
|
86
|
+
// weight (not stored depth), consistent with the headline; tokenizing per
|
|
87
|
+
// entry is free.
|
|
88
|
+
const byScheme = new Map();
|
|
89
|
+
for (const e of logEntries) {
|
|
90
|
+
const scheme = e.target?.scheme ?? "—";
|
|
91
|
+
const acc = byScheme.get(scheme) ?? { scheme, entries: 0, tokens: 0 };
|
|
92
|
+
acc.entries += 1;
|
|
93
|
+
acc.tokens += countTokens(PacketWire.#renderLogEntries([e]));
|
|
94
|
+
byScheme.set(scheme, acc);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
index: {
|
|
98
|
+
channels: indexChannels,
|
|
99
|
+
tokens: indexBody ? countTokens(`# Plurnk System Index\n\n${indexBody}`) : 0,
|
|
100
|
+
},
|
|
101
|
+
log: {
|
|
102
|
+
entries: logEntries.length,
|
|
103
|
+
tokens: logBody ? countTokens(`# Plurnk System Log\n\n${logBody}`) : 0,
|
|
104
|
+
byScheme: [...byScheme.values()].toSorted((a, b) => b.tokens - a.tokens),
|
|
105
|
+
},
|
|
106
|
+
total: countTokens(PacketWire.renderSystemContent(system)) + countTokens(PacketWire.renderUserContent(user)),
|
|
107
|
+
};
|
|
51
108
|
}
|
|
52
|
-
|
|
53
|
-
|
|
109
|
+
// Number each line of body as `<N>:\t<line>` — mirrors rummy
|
|
110
|
+
// plugins/helpers.js numberLines. The leading digit prevents column-zero
|
|
111
|
+
// fence collisions and gives the model line refs for free (`READ<42-46>`).
|
|
112
|
+
// Used for READ@200 content; index-preview numbering is the framework's
|
|
113
|
+
// job now (baked into the preview string — see renderHeredoc).
|
|
114
|
+
static #numberLines(body, start = 1) {
|
|
115
|
+
if (!body)
|
|
116
|
+
return "";
|
|
117
|
+
const trailingNewline = body.endsWith("\n");
|
|
118
|
+
const source = trailingNewline ? body.slice(0, -1) : body;
|
|
119
|
+
const numbered = source.split("\n").map((line, i) => `${start + i}:\t${line}`).join("\n");
|
|
120
|
+
return trailingNewline ? `${numbered}\n` : numbered;
|
|
54
121
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// fence collisions and gives the model line refs for free (`READ<42-46>`).
|
|
66
|
-
const numberLines = (body, start = 1) => {
|
|
67
|
-
if (!body)
|
|
68
|
-
return "";
|
|
69
|
-
const trailingNewline = body.endsWith("\n");
|
|
70
|
-
const source = trailingNewline ? body.slice(0, -1) : body;
|
|
71
|
-
const numbered = source.split("\n").map((line, i) => `${start + i}:\t${line}`).join("\n");
|
|
72
|
-
return trailingNewline ? `${numbered}\n` : numbered;
|
|
73
|
-
};
|
|
74
|
-
// Tolerant JSON parser for log entries' rx/tx fields. The engine
|
|
75
|
-
// pre-parses application/json mimetypes, but render may also receive
|
|
76
|
-
// strings (legacy paths, manual tests). Returns null on parse failure.
|
|
77
|
-
const safeParse = (s) => {
|
|
78
|
-
try {
|
|
79
|
-
return JSON.parse(s);
|
|
122
|
+
// Tolerant JSON parser for log entries' rx/tx fields. The engine
|
|
123
|
+
// pre-parses application/json mimetypes, but render may also receive
|
|
124
|
+
// strings (legacy paths, manual tests). Returns null on parse failure.
|
|
125
|
+
static #safeParse(s) {
|
|
126
|
+
try {
|
|
127
|
+
return JSON.parse(s);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
80
132
|
}
|
|
81
|
-
|
|
82
|
-
|
|
133
|
+
// Stable JSON: keys sorted alphabetically so the same meta produces the
|
|
134
|
+
// same string across turns — prefix-cache friendly. Mirrors rummy
|
|
135
|
+
// plugins/helpers.js canonicalJson.
|
|
136
|
+
static #canonicalJson(obj) {
|
|
137
|
+
const keys = Object.keys(obj).sort();
|
|
138
|
+
const sorted = {};
|
|
139
|
+
for (const k of keys)
|
|
140
|
+
sorted[k] = obj[k];
|
|
141
|
+
return JSON.stringify(sorted);
|
|
83
142
|
}
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
// Wrap a body in heredoc fences. Leading `\n` always (separates the
|
|
96
|
-
// opening fence from the first body character — necessary because
|
|
97
|
-
// numbered bodies start with `1:\t…` which would otherwise collide
|
|
98
|
-
// visually with the fence's closing `:`). Trailing `\n` only when the
|
|
99
|
-
// body doesn't already end with one — otherwise you get a doubled
|
|
100
|
-
// newline that renders as a blank line before the closing fence, which
|
|
101
|
-
// reads as "the content has a trailing blank line" when actually it
|
|
102
|
-
// doesn't. The body's own whitespace decides the shape.
|
|
103
|
-
const wrapHeredocBody = (fence, body) => {
|
|
104
|
-
const sep = body.endsWith("\n") ? "" : "\n";
|
|
105
|
-
return `<<${fence}:\n${body}${sep}:${fence}`;
|
|
106
|
-
};
|
|
107
|
-
// Heredoc block for one channel of one entry. Fence is `URI#channel`
|
|
108
|
-
// (plurnk-grammar-native form) so model emissions and entry projections
|
|
109
|
-
// share one syntax. When `channel` is null/empty the fence is path-only —
|
|
110
|
-
// this is the default-channel convention: the absence of `#channel` is
|
|
111
|
-
// the addressing of the scheme's default channel, not a missing field.
|
|
112
|
-
// Body is line-numbered.
|
|
113
|
-
const renderHeredoc = (uri, channel, body) => {
|
|
114
|
-
const fence = channel ? `${uri}#${channel}` : uri;
|
|
115
|
-
return wrapHeredocBody(fence, numberLines(body));
|
|
116
|
-
};
|
|
117
|
-
// Re-render a plurnk statement (from log_entries.tx) as the heredoc form
|
|
118
|
-
// the model would have emitted. Used by the log render so the model sees
|
|
119
|
-
// its own ops in its own native syntax — what it wrote, mirrored back.
|
|
120
|
-
//
|
|
121
|
-
// Faithfulness over cleverness: render the parts as recorded. `target.raw`
|
|
122
|
-
// preserves exactly what the model wrote (URL with fragment, bare path,
|
|
123
|
-
// etc.) instead of round-tripping through scheme/pathname/fragment fields.
|
|
124
|
-
// Returns null when tx isn't a parseable PlurnkStatement (callers fall
|
|
125
|
-
// back to the meta line alone).
|
|
126
|
-
//
|
|
127
|
-
// Signal renders to `[…]`:
|
|
128
|
-
// - array of strings (tags) → `[tag1,tag2]`
|
|
129
|
-
// - number (status code, e.g. SEND[200]) → `[200]`
|
|
130
|
-
// - string (runtime, e.g. EXEC[python]) → `[python]`
|
|
131
|
-
// - null/missing → omitted
|
|
132
|
-
// All plurnk statements share the same syntactic frame; signal type
|
|
133
|
-
// varies by op but renders uniformly.
|
|
134
|
-
const renderStatementHeredoc = (tx) => {
|
|
135
|
-
if (tx === null || typeof tx !== "object" || typeof tx.op !== "string" || tx.op.length === 0)
|
|
136
|
-
return null;
|
|
137
|
-
const op = tx.op;
|
|
138
|
-
const suffix = typeof tx.suffix === "string" ? tx.suffix : "";
|
|
139
|
-
let signalStr = "";
|
|
140
|
-
const signal = tx.signal;
|
|
141
|
-
if (Array.isArray(signal)) {
|
|
142
|
-
const tags = signal.filter((t) => typeof t === "string");
|
|
143
|
-
if (tags.length > 0)
|
|
144
|
-
signalStr = `[${tags.join(",")}]`;
|
|
143
|
+
// Wrap a body in heredoc fences. Leading `\n` always (separates the
|
|
144
|
+
// opening fence from the first body character — necessary because
|
|
145
|
+
// numbered bodies start with `1:\t…` which would otherwise collide
|
|
146
|
+
// visually with the `:::FENCE` markers). Trailing `\n` only when the
|
|
147
|
+
// body doesn't already end with one — otherwise you get a doubled
|
|
148
|
+
// newline that renders as a blank line before the closing fence, which
|
|
149
|
+
// reads as "the content has a trailing blank line" when actually it
|
|
150
|
+
// doesn't. The body's own whitespace decides the shape.
|
|
151
|
+
static #wrapHeredocBody(fence, body) {
|
|
152
|
+
const sep = body.endsWith("\n") ? "" : "\n";
|
|
153
|
+
return `<<:::${fence}\n${body}${sep}:::${fence}`;
|
|
145
154
|
}
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
// Heredoc block for one channel of one entry. Fence is `URI#channel`
|
|
156
|
+
// (the `<<:::FENCE` packet-rendering marker per wrapHeredocBody — a
|
|
157
|
+
// projection is read-only context, NOT an emittable op, so it must not
|
|
158
|
+
// wear the DSL op-fence; that conflation was the demo.sh corruption bug).
|
|
159
|
+
// When `channel` is null/empty the fence is path-only —
|
|
160
|
+
// this is the default-channel convention: the absence of `#channel` is
|
|
161
|
+
// the addressing of the scheme's default channel, not a missing field.
|
|
162
|
+
// Body is a mimetypes preview, rendered VERBATIM — the framework owns its
|
|
163
|
+
// formatting (N:\t line numbers for text, source-annotated outline for
|
|
164
|
+
// symbols, correct start-line for tail slices) and bakes it into the
|
|
165
|
+
// preview string as of mimetypes 0.7.3, so the service must not re-number
|
|
166
|
+
// it (re-numbering would double-prefix text and mis-number symbol
|
|
167
|
+
// outlines — plurnk-mimetypes#8).
|
|
168
|
+
static #renderHeredoc(uri, channel, body) {
|
|
169
|
+
const fence = channel ? `${uri}#${channel}` : uri;
|
|
170
|
+
return PacketWire.#wrapHeredocBody(fence, body);
|
|
148
171
|
}
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
// Re-render a plurnk statement (from log_entries.tx) as the heredoc form
|
|
173
|
+
// the model would have emitted. Used by the log render so the model sees
|
|
174
|
+
// its own ops in its own native syntax — what it wrote, mirrored back.
|
|
175
|
+
//
|
|
176
|
+
// Faithfulness over cleverness: render the parts as recorded. `target.raw`
|
|
177
|
+
// preserves exactly what the model wrote (URL with fragment, bare path,
|
|
178
|
+
// etc.) instead of round-tripping through scheme/pathname/fragment fields.
|
|
179
|
+
// Returns null when tx isn't a parseable PlurnkStatement (callers fall
|
|
180
|
+
// back to the meta line alone).
|
|
181
|
+
//
|
|
182
|
+
// Signal renders to `[…]`:
|
|
183
|
+
// - array of strings (tags) → `[tag1,tag2]`
|
|
184
|
+
// - number (status code, e.g. SEND[200]) → `[200]`
|
|
185
|
+
// - string (runtime, e.g. EXEC[python]) → `[python]`
|
|
186
|
+
// - null/missing → omitted
|
|
187
|
+
// All plurnk statements share the same syntactic frame; signal type
|
|
188
|
+
// varies by op but renders uniformly.
|
|
189
|
+
static #renderStatementHeredoc(tx) {
|
|
190
|
+
if (tx === null || typeof tx !== "object" || typeof tx.op !== "string" || tx.op.length === 0)
|
|
191
|
+
return null;
|
|
192
|
+
const op = tx.op;
|
|
193
|
+
const suffix = typeof tx.suffix === "string" ? tx.suffix : "";
|
|
194
|
+
let signalStr = "";
|
|
195
|
+
const signal = tx.signal;
|
|
196
|
+
if (Array.isArray(signal)) {
|
|
197
|
+
const tags = signal.filter((t) => typeof t === "string");
|
|
198
|
+
if (tags.length > 0)
|
|
199
|
+
signalStr = `[${tags.join(",")}]`;
|
|
200
|
+
}
|
|
201
|
+
else if (typeof signal === "number") {
|
|
202
|
+
signalStr = `[${signal}]`;
|
|
203
|
+
}
|
|
204
|
+
else if (typeof signal === "string" && signal.length > 0) {
|
|
205
|
+
signalStr = `[${signal}]`;
|
|
206
|
+
}
|
|
207
|
+
let targetStr = "";
|
|
208
|
+
const target = tx.target;
|
|
209
|
+
if (target !== null && target !== undefined && typeof target === "object" && typeof target.raw === "string") {
|
|
210
|
+
targetStr = `(${target.raw})`;
|
|
211
|
+
}
|
|
212
|
+
let markerStr = "";
|
|
213
|
+
const lm = tx.lineMarker;
|
|
214
|
+
if (lm !== null && lm !== undefined && typeof lm === "object" && typeof lm.first === "number") {
|
|
215
|
+
markerStr = typeof lm.last === "number" ? `<${lm.first},${lm.last}>` : `<${lm.first}>`;
|
|
216
|
+
}
|
|
217
|
+
let body;
|
|
218
|
+
if (typeof tx.body === "string")
|
|
219
|
+
body = tx.body;
|
|
220
|
+
else if (tx.body !== null && tx.body !== undefined && typeof tx.body === "object" && typeof tx.body.raw === "string")
|
|
221
|
+
body = tx.body.raw;
|
|
222
|
+
else
|
|
223
|
+
body = "";
|
|
224
|
+
// Character-perfect: no padding around body. The body string IS
|
|
225
|
+
// whatever the model wrote between the colons, including any leading
|
|
226
|
+
// or trailing whitespace it chose. Adding `\n` here would inflate
|
|
227
|
+
// single-line emissions into multi-line and nudge the model toward
|
|
228
|
+
// verbose forms — and it would violate the grammar's "body content
|
|
229
|
+
// is character-perfect" guarantee on the way back.
|
|
230
|
+
return `<<${op}${suffix}${signalStr}${targetStr}${markerStr}:${body}:${op}${suffix}`;
|
|
151
231
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
232
|
+
// Render a (scheme, pathname) tuple as the URI the model should SEE.
|
|
233
|
+
// Null scheme → bare pathname. The `file` scheme never reaches this
|
|
234
|
+
// function because Engine.#extractTarget normalizes it to null at the
|
|
235
|
+
// storage boundary; storage and wire output are uniform on this.
|
|
236
|
+
static #renderModelUri(scheme, pathname) {
|
|
237
|
+
const path = pathname ?? "";
|
|
238
|
+
if (scheme === null || scheme === undefined)
|
|
239
|
+
return path;
|
|
240
|
+
return `${scheme}://${path}`;
|
|
156
241
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
242
|
+
// Render one Index entry → `* {meta}` line followed by per-channel
|
|
243
|
+
// heredoc blocks. meta describes the entry; nested `channels` carries
|
|
244
|
+
// per-channel mimetype/tokens so the model doesn't have to READ to
|
|
245
|
+
// learn the shape of a channel's content.
|
|
246
|
+
//
|
|
247
|
+
// Empty channels are omitted entirely (no meta entry, no body block) —
|
|
248
|
+
// an empty stderr is an infohazard: the model has to read it and infer
|
|
249
|
+
// "this is intentionally blank." Absence carries the same information
|
|
250
|
+
// with zero tokens spent.
|
|
251
|
+
static #renderIndexEntries(entries) {
|
|
252
|
+
return entries.map((e) => {
|
|
253
|
+
const uri = PacketWire.#renderModelUri(e.scheme, e.pathname);
|
|
254
|
+
const defaultChannel = e.defaultChannel ?? "";
|
|
255
|
+
const meta = { path: uri };
|
|
256
|
+
if (Array.isArray(e.tags) && e.tags.length > 0)
|
|
257
|
+
meta.tags = e.tags;
|
|
258
|
+
const channelsMeta = {};
|
|
259
|
+
const blocks = [];
|
|
260
|
+
for (const [channelName, ch] of Object.entries(e.channels ?? {})) {
|
|
261
|
+
const content = ch.content;
|
|
262
|
+
if (typeof content !== "string" || content.length === 0)
|
|
263
|
+
continue;
|
|
264
|
+
const channelInfo = {};
|
|
265
|
+
if (typeof ch.mimetype === "string")
|
|
266
|
+
channelInfo.mimetype = ch.mimetype;
|
|
267
|
+
if (typeof ch.tokens === "number")
|
|
268
|
+
channelInfo.tokens = ch.tokens;
|
|
269
|
+
if (typeof ch.lines === "number")
|
|
270
|
+
channelInfo.lines = ch.lines;
|
|
271
|
+
channelsMeta[channelName] = channelInfo;
|
|
272
|
+
const fenceChannel = channelName === defaultChannel ? null : channelName;
|
|
273
|
+
blocks.push(PacketWire.#renderHeredoc(uri, fenceChannel, content));
|
|
274
|
+
}
|
|
275
|
+
if (Object.keys(channelsMeta).length > 0)
|
|
276
|
+
meta.channels = channelsMeta;
|
|
277
|
+
return blocks.length > 0
|
|
278
|
+
? `* ${PacketWire.#canonicalJson(meta)}\n${blocks.join("\n")}`
|
|
279
|
+
: `* ${PacketWire.#canonicalJson(meta)}`;
|
|
280
|
+
}).join("\n\n");
|
|
161
281
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
282
|
+
// Render one Log entry → a single bullet line carrying the meta JSON.
|
|
283
|
+
// No body, no fence — every meaningful field is in the JSON. Naming
|
|
284
|
+
// follows the uniform principle: `path` is identity (this log row's
|
|
285
|
+
// own URI), `target` is the URI the action acted on. COPY/MOVE add
|
|
286
|
+
// `source`; currently the engine emits target only (source plumbing
|
|
287
|
+
// pending the COPY/MOVE-specific log shape pass).
|
|
288
|
+
//
|
|
289
|
+
// On error, status >= 400 signals the failure; the message lives in
|
|
290
|
+
// the next packet's user.telemetry.errors[] per SPEC §15.1. (Forward:
|
|
291
|
+
// meta will gain tokensBefore/After + linesBefore/After to convey
|
|
292
|
+
// change scope without carrying the body content.)
|
|
293
|
+
//
|
|
294
|
+
// Per-entry render: one meta JSON line plus a body block that the model
|
|
295
|
+
// can read to know what it did. Two body cases:
|
|
296
|
+
// 1. READ@200 with content → render rx.content under the target fence.
|
|
297
|
+
// The model asked for content; show it the content. (Matcher is in
|
|
298
|
+
// meta.matcher, count is in meta.matches.)
|
|
299
|
+
// 2. Every other op → re-emit tx as a heredoc in the model's native
|
|
300
|
+
// syntax. The model wrote this; mirror it back so the log is a true
|
|
301
|
+
// record of its actions instead of a row of opaque status codes.
|
|
302
|
+
static #renderLogEntries(entries) {
|
|
303
|
+
return entries.map((e) => {
|
|
304
|
+
const meta = {};
|
|
305
|
+
const coordinate = typeof e.coordinate === "string" ? e.coordinate : null;
|
|
306
|
+
const op = typeof e.op === "string" && e.op.length > 0 ? e.op : null;
|
|
307
|
+
if (coordinate !== null && op !== null)
|
|
308
|
+
meta.path = `log://${coordinate}/${op}`;
|
|
309
|
+
else if (coordinate !== null)
|
|
310
|
+
meta.path = `log://${coordinate}`;
|
|
311
|
+
if (typeof e.origin === "string")
|
|
312
|
+
meta.origin = e.origin;
|
|
313
|
+
if (op !== null)
|
|
314
|
+
meta.op = op;
|
|
315
|
+
if (typeof e.status === "number")
|
|
316
|
+
meta.status = e.status;
|
|
317
|
+
const target = PacketWire.#renderActionTarget(e.target);
|
|
318
|
+
if (target !== null)
|
|
319
|
+
meta.target = target;
|
|
320
|
+
// Op-specific meta enrichment for READ: surface the matcher body
|
|
321
|
+
// and match count when a body matcher was used. Without these, the
|
|
322
|
+
// model can't distinguish "0 matches" from "empty content" — both
|
|
323
|
+
// would render as a status-204 line. The matcher comes from the
|
|
324
|
+
// stored statement (tx); the count from the result (rx).
|
|
325
|
+
if (op === "READ") {
|
|
326
|
+
const tx = e.tx;
|
|
327
|
+
if (tx !== null && tx !== undefined && typeof tx === "object" && tx.body !== null && typeof tx.body === "object") {
|
|
328
|
+
if (typeof tx.body.raw === "string")
|
|
329
|
+
meta.matcher = tx.body.raw;
|
|
330
|
+
}
|
|
331
|
+
const rx = (typeof e.rx === "string" ? PacketWire.#safeParse(e.rx) : e.rx);
|
|
332
|
+
if (rx !== null && typeof rx === "object" && typeof rx.matches === "number") {
|
|
333
|
+
meta.matches = rx.matches;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const metaLine = `* ${PacketWire.#canonicalJson(meta)}`;
|
|
337
|
+
// READ@200: expose the response body. READ@204 (successfully empty —
|
|
338
|
+
// 0 matcher hits, sentinel slice, or empty source) has no body to
|
|
339
|
+
// render; the meta line carries the signal via `matches` / status code.
|
|
340
|
+
if (op === "READ" && e.status === 200) {
|
|
341
|
+
const rx = (typeof e.rx === "string" ? PacketWire.#safeParse(e.rx) : e.rx);
|
|
342
|
+
if (rx !== null && typeof rx === "object" && typeof rx.content === "string" && rx.content.length > 0) {
|
|
343
|
+
const fence = target ?? `log://${coordinate}`;
|
|
344
|
+
// Line-navigable mimetypes (text/markdown, text/plain,
|
|
345
|
+
// source code, etc.) get N:\t prefix per plurnk.md. Tree-
|
|
346
|
+
// navigable (JSON, XML, HTML) render verbatim — line
|
|
347
|
+
// numbers in the wrapper would collide with structural
|
|
348
|
+
// navigation (jsonpath/xpath) used on these formats.
|
|
349
|
+
// Classifier is consumer-side in this repo (SPEC.md §16.6).
|
|
350
|
+
const mimetype = typeof rx.mimetype === "string" ? rx.mimetype : "text/plain";
|
|
351
|
+
if (MimetypeBinary.isLineNavigableMimetype(mimetype)) {
|
|
352
|
+
const start = typeof rx.startLine === "number" ? rx.startLine : 1;
|
|
353
|
+
return `${metaLine}\n${PacketWire.#wrapHeredocBody(fence, PacketWire.#numberLines(rx.content, start))}`;
|
|
354
|
+
}
|
|
355
|
+
return `${metaLine}\n${PacketWire.#wrapHeredocBody(fence, rx.content)}`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Every other op: re-emit the model's statement. EDIT, EXEC, SEND,
|
|
359
|
+
// COPY, MOVE, FIND, SHOW, HIDE — each gets its native heredoc form
|
|
360
|
+
// back. Without this the log row is a status code with no record
|
|
361
|
+
// of what the model actually wrote, and the model has to back into
|
|
362
|
+
// its own actions by inference (see reasoning.md trace from the
|
|
363
|
+
// pre-fix count-files run).
|
|
364
|
+
const heredoc = PacketWire.#renderStatementHeredoc(e.tx ?? null);
|
|
365
|
+
if (heredoc !== null)
|
|
366
|
+
return `${metaLine}\n${heredoc}`;
|
|
367
|
+
return metaLine;
|
|
368
|
+
}).join("\n");
|
|
216
369
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
:
|
|
222
|
-
}).join("\n\n");
|
|
223
|
-
// Render one Log entry → a single bullet line carrying the meta JSON.
|
|
224
|
-
// No body, no fence — every meaningful field is in the JSON. Naming
|
|
225
|
-
// follows the uniform principle: `path` is identity (this log row's
|
|
226
|
-
// own URI), `target` is the URI the action acted on. COPY/MOVE add
|
|
227
|
-
// `source`; currently the engine emits target only (source plumbing
|
|
228
|
-
// pending the COPY/MOVE-specific log shape pass).
|
|
229
|
-
//
|
|
230
|
-
// On error, status >= 400 signals the failure; the message lives in
|
|
231
|
-
// the next packet's user.telemetry.errors[] per SPEC §15.1. (Forward:
|
|
232
|
-
// meta will gain tokensBefore/After + linesBefore/After to convey
|
|
233
|
-
// change scope without carrying the body content.)
|
|
234
|
-
//
|
|
235
|
-
// Per-entry render: one meta JSON line plus a body block that the model
|
|
236
|
-
// can read to know what it did. Two body cases:
|
|
237
|
-
// 1. READ@200 with content → render rx.content under the target fence.
|
|
238
|
-
// The model asked for content; show it the content. (Matcher is in
|
|
239
|
-
// meta.matcher, count is in meta.matches.)
|
|
240
|
-
// 2. Every other op → re-emit tx as a heredoc in the model's native
|
|
241
|
-
// syntax. The model wrote this; mirror it back so the log is a true
|
|
242
|
-
// record of its actions instead of a row of opaque status codes.
|
|
243
|
-
const renderLogEntries = (entries) => entries.map((e) => {
|
|
244
|
-
const meta = {};
|
|
245
|
-
const coordinate = typeof e.coordinate === "string" ? e.coordinate : null;
|
|
246
|
-
const op = typeof e.op === "string" && e.op.length > 0 ? e.op : null;
|
|
247
|
-
if (coordinate !== null && op !== null)
|
|
248
|
-
meta.path = `log://${coordinate}/${op}`;
|
|
249
|
-
else if (coordinate !== null)
|
|
250
|
-
meta.path = `log://${coordinate}`;
|
|
251
|
-
if (typeof e.origin === "string")
|
|
252
|
-
meta.origin = e.origin;
|
|
253
|
-
if (op !== null)
|
|
254
|
-
meta.op = op;
|
|
255
|
-
if (typeof e.status === "number")
|
|
256
|
-
meta.status = e.status;
|
|
257
|
-
const target = renderActionTarget(e.target);
|
|
258
|
-
if (target !== null)
|
|
259
|
-
meta.target = target;
|
|
260
|
-
// Op-specific meta enrichment for READ: surface the matcher body
|
|
261
|
-
// and match count when a body matcher was used. Without these, the
|
|
262
|
-
// model can't distinguish "0 matches" from "empty content" — both
|
|
263
|
-
// would render as a status-204 line. The matcher comes from the
|
|
264
|
-
// stored statement (tx); the count from the result (rx).
|
|
265
|
-
if (op === "READ") {
|
|
266
|
-
const tx = e.tx;
|
|
267
|
-
if (tx !== null && typeof tx === "object" && tx.body !== null && typeof tx.body === "object") {
|
|
268
|
-
if (typeof tx.body.raw === "string")
|
|
269
|
-
meta.matcher = tx.body.raw;
|
|
270
|
-
}
|
|
271
|
-
const rx = typeof e.rx === "string" ? safeParse(e.rx) : e.rx;
|
|
272
|
-
if (rx !== null && typeof rx === "object" && typeof rx.matches === "number") {
|
|
273
|
-
meta.matches = rx.matches;
|
|
274
|
-
}
|
|
370
|
+
static #renderActionTarget(target) {
|
|
371
|
+
if (target === null || target === undefined)
|
|
372
|
+
return null;
|
|
373
|
+
const rendered = PacketWire.#renderModelUri(target.scheme, target.pathname);
|
|
374
|
+
return rendered.length > 0 ? rendered : null;
|
|
275
375
|
}
|
|
276
|
-
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
376
|
+
// Render TelemetryEvent[] → meta line per event, optionally followed by
|
|
377
|
+
// an N:\t-prefixed snippet block when the event carries `snippet` (the
|
|
378
|
+
// convention plurnk-service uses for content-offset positions — model
|
|
379
|
+
// sees its own offending bytes alongside the error, not an abstract
|
|
380
|
+
// message it can't trace).
|
|
381
|
+
//
|
|
382
|
+
// Snippet renders verbatim — already N:\t-prefixed at production time
|
|
383
|
+
// (Engine.#extractSnippet). The fence is `error://<line>` to give the
|
|
384
|
+
// model a stable URI shape it can ignore or reference; the `error://`
|
|
385
|
+
// scheme isn't writable, it just identifies "this block is locator
|
|
386
|
+
// context, not addressable content."
|
|
387
|
+
//
|
|
388
|
+
// `snippet` is stripped from the meta JSON so the snippet appears once,
|
|
389
|
+
// in the body block, not also as a quoted string in the meta.
|
|
390
|
+
static #renderTelemetryErrors(errors) {
|
|
391
|
+
return errors.map((e) => {
|
|
392
|
+
const snippet = typeof e.snippet === "string" ? e.snippet : null;
|
|
393
|
+
const meta = { ...e };
|
|
394
|
+
if (snippet !== null)
|
|
395
|
+
delete meta.snippet;
|
|
396
|
+
const metaLine = `* ${PacketWire.#canonicalJson(meta)}`;
|
|
397
|
+
if (snippet === null || snippet.length === 0)
|
|
398
|
+
return metaLine;
|
|
399
|
+
const line = typeof e.position?.line === "number" ? e.position.line : 0;
|
|
400
|
+
const fence = `error://${line}`;
|
|
401
|
+
return `${metaLine}\n${PacketWire.#wrapHeredocBody(fence, snippet)}`;
|
|
402
|
+
}).join("\n");
|
|
297
403
|
}
|
|
298
|
-
|
|
299
|
-
// COPY, MOVE, FIND, SHOW, HIDE — each gets its native heredoc form
|
|
300
|
-
// back. Without this the log row is a status code with no record
|
|
301
|
-
// of what the model actually wrote, and the model has to back into
|
|
302
|
-
// its own actions by inference (see reasoning.md trace from the
|
|
303
|
-
// pre-fix count-files run).
|
|
304
|
-
const heredoc = renderStatementHeredoc(e.tx);
|
|
305
|
-
if (heredoc !== null)
|
|
306
|
-
return `${metaLine}\n${heredoc}`;
|
|
307
|
-
return metaLine;
|
|
308
|
-
}).join("\n");
|
|
309
|
-
const renderActionTarget = (target) => {
|
|
310
|
-
if (target === null || target === undefined)
|
|
311
|
-
return null;
|
|
312
|
-
const rendered = renderModelUri(target.scheme, target.pathname);
|
|
313
|
-
return rendered.length > 0 ? rendered : null;
|
|
314
|
-
};
|
|
315
|
-
// Render TelemetryEvent[] → meta line per event, optionally followed by
|
|
316
|
-
// an N:\t-prefixed snippet block when the event carries `snippet` (the
|
|
317
|
-
// convention plurnk-service uses for content-offset positions — model
|
|
318
|
-
// sees its own offending bytes alongside the error, not an abstract
|
|
319
|
-
// message it can't trace).
|
|
320
|
-
//
|
|
321
|
-
// Snippet renders verbatim — already N:\t-prefixed at production time
|
|
322
|
-
// (Engine.#extractSnippet). The fence is `error://<line>` to give the
|
|
323
|
-
// model a stable URI shape it can ignore or reference; the `error://`
|
|
324
|
-
// scheme isn't writable, it just identifies "this block is locator
|
|
325
|
-
// context, not addressable content."
|
|
326
|
-
//
|
|
327
|
-
// `snippet` is stripped from the meta JSON so the snippet appears once,
|
|
328
|
-
// in the body block, not also as a quoted string in the meta.
|
|
329
|
-
const renderTelemetryErrors = (errors) => errors.map((e) => {
|
|
330
|
-
const snippet = typeof e.snippet === "string" ? e.snippet : null;
|
|
331
|
-
const meta = { ...e };
|
|
332
|
-
if (snippet !== null)
|
|
333
|
-
delete meta.snippet;
|
|
334
|
-
const metaLine = `* ${canonicalJson(meta)}`;
|
|
335
|
-
if (snippet === null || snippet.length === 0)
|
|
336
|
-
return metaLine;
|
|
337
|
-
const line = typeof e.position?.line === "number" ? e.position.line : 0;
|
|
338
|
-
const fence = `error://${line}`;
|
|
339
|
-
return `${metaLine}\n${wrapHeredocBody(fence, snippet)}`;
|
|
340
|
-
}).join("\n");
|
|
404
|
+
}
|
|
341
405
|
//# sourceMappingURL=packet-wire.js.map
|