@plurnk/plurnk-service 0.7.0 → 0.8.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.
Files changed (258) hide show
  1. package/SPEC.md +104 -74
  2. package/bin/plurnk-service.ts +132 -0
  3. package/dist/Paths.d.ts +8 -0
  4. package/dist/Paths.d.ts.map +1 -0
  5. package/dist/Paths.js +47 -0
  6. package/dist/Paths.js.map +1 -0
  7. package/dist/content/index.d.ts +9 -0
  8. package/dist/content/index.d.ts.map +1 -0
  9. package/dist/content/index.js +10 -0
  10. package/dist/content/index.js.map +1 -0
  11. package/dist/content/line-marker.d.ts +26 -0
  12. package/dist/content/line-marker.d.ts.map +1 -0
  13. package/dist/content/line-marker.js +323 -0
  14. package/dist/content/line-marker.js.map +1 -0
  15. package/dist/content/matcher.d.ts +15 -0
  16. package/dist/content/matcher.d.ts.map +1 -0
  17. package/dist/content/matcher.js +112 -0
  18. package/dist/content/matcher.js.map +1 -0
  19. package/dist/content/mimetype-binary.d.ts +9 -0
  20. package/dist/content/mimetype-binary.d.ts.map +1 -0
  21. package/dist/content/mimetype-binary.js +86 -0
  22. package/dist/content/mimetype-binary.js.map +1 -0
  23. package/dist/content/path-mimetype.d.ts +6 -0
  24. package/dist/content/path-mimetype.d.ts.map +1 -0
  25. package/dist/content/path-mimetype.js +49 -0
  26. package/dist/content/path-mimetype.js.map +1 -0
  27. package/dist/content/read-resolve.d.ts +20 -0
  28. package/dist/content/read-resolve.d.ts.map +1 -0
  29. package/dist/content/read-resolve.js +60 -0
  30. package/dist/content/read-resolve.js.map +1 -0
  31. package/dist/core/ChannelWrite.d.ts +33 -30
  32. package/dist/core/ChannelWrite.d.ts.map +1 -1
  33. package/dist/core/ChannelWrite.js +43 -41
  34. package/dist/core/ChannelWrite.js.map +1 -1
  35. package/dist/core/Engine.d.ts +14 -10
  36. package/dist/core/Engine.d.ts.map +1 -1
  37. package/dist/core/Engine.js +269 -94
  38. package/dist/core/Engine.js.map +1 -1
  39. package/dist/core/EnvFlags.d.ts +6 -3
  40. package/dist/core/EnvFlags.d.ts.map +1 -1
  41. package/dist/core/EnvFlags.js +62 -60
  42. package/dist/core/EnvFlags.js.map +1 -1
  43. package/dist/core/PluginLoader.d.ts +6 -3
  44. package/dist/core/PluginLoader.d.ts.map +1 -1
  45. package/dist/core/PluginLoader.js +77 -73
  46. package/dist/core/PluginLoader.js.map +1 -1
  47. package/dist/core/ProviderInstantiate.d.ts +4 -2
  48. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  49. package/dist/core/ProviderInstantiate.js +23 -22
  50. package/dist/core/ProviderInstantiate.js.map +1 -1
  51. package/dist/core/SchemeRegistry.d.ts +1 -1
  52. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  53. package/dist/core/SchemeRegistry.js +3 -3
  54. package/dist/core/SchemeRegistry.js.map +1 -1
  55. package/dist/core/git-membership.d.ts +8 -0
  56. package/dist/core/git-membership.d.ts.map +1 -0
  57. package/dist/core/git-membership.js +125 -0
  58. package/dist/core/git-membership.js.map +1 -0
  59. package/dist/core/packet-wire.d.ts +47 -6
  60. package/dist/core/packet-wire.d.ts.map +1 -1
  61. package/dist/core/packet-wire.js +376 -312
  62. package/dist/core/packet-wire.js.map +1 -1
  63. package/dist/core/resolveForLoop.d.ts +14 -0
  64. package/dist/core/resolveForLoop.d.ts.map +1 -0
  65. package/dist/core/resolveForLoop.js +34 -0
  66. package/dist/core/resolveForLoop.js.map +1 -0
  67. package/dist/core/results.d.ts +40 -0
  68. package/dist/core/results.d.ts.map +1 -0
  69. package/dist/core/results.js +52 -0
  70. package/dist/core/results.js.map +1 -0
  71. package/dist/core/scheme-types.d.ts +5 -4
  72. package/dist/core/scheme-types.d.ts.map +1 -1
  73. package/dist/core/scheme-types.js +4 -4
  74. package/dist/core/scheme-types.js.map +1 -1
  75. package/dist/core/types.d.ts +27 -0
  76. package/dist/core/types.d.ts.map +1 -0
  77. package/dist/core/types.js +17 -0
  78. package/dist/core/types.js.map +1 -0
  79. package/dist/index.d.ts +1 -6
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +4 -43
  82. package/dist/index.js.map +1 -1
  83. package/dist/schemes/Exec.d.ts +1 -0
  84. package/dist/schemes/Exec.d.ts.map +1 -1
  85. package/dist/schemes/Exec.js +37 -21
  86. package/dist/schemes/Exec.js.map +1 -1
  87. package/dist/schemes/File.d.ts +0 -1
  88. package/dist/schemes/File.d.ts.map +1 -1
  89. package/dist/schemes/File.js +32 -66
  90. package/dist/schemes/File.js.map +1 -1
  91. package/dist/schemes/Known.d.ts.map +1 -1
  92. package/dist/schemes/Known.js +13 -13
  93. package/dist/schemes/Known.js.map +1 -1
  94. package/dist/schemes/Log.d.ts.map +1 -1
  95. package/dist/schemes/Log.js +8 -52
  96. package/dist/schemes/Log.js.map +1 -1
  97. package/dist/schemes/Plurnk.d.ts.map +1 -1
  98. package/dist/schemes/Plurnk.js +13 -13
  99. package/dist/schemes/Plurnk.js.map +1 -1
  100. package/dist/schemes/Skill.d.ts.map +1 -1
  101. package/dist/schemes/Skill.js +13 -13
  102. package/dist/schemes/Skill.js.map +1 -1
  103. package/dist/schemes/Unknown.d.ts.map +1 -1
  104. package/dist/schemes/Unknown.js +13 -13
  105. package/dist/schemes/Unknown.js.map +1 -1
  106. package/dist/schemes/_entry-crud.d.ts +5 -3
  107. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  108. package/dist/schemes/_entry-crud.js +55 -50
  109. package/dist/schemes/_entry-crud.js.map +1 -1
  110. package/dist/schemes/_entry-find.d.ts +10 -3
  111. package/dist/schemes/_entry-find.d.ts.map +1 -1
  112. package/dist/schemes/_entry-find.js +99 -77
  113. package/dist/schemes/_entry-find.js.map +1 -1
  114. package/dist/schemes/_entry-manifest.d.ts +6 -0
  115. package/dist/schemes/_entry-manifest.d.ts.map +1 -0
  116. package/dist/schemes/_entry-manifest.js +45 -0
  117. package/dist/schemes/_entry-manifest.js.map +1 -0
  118. package/dist/schemes/_entry-ops.d.ts +7 -4
  119. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  120. package/dist/schemes/_entry-ops.js +198 -316
  121. package/dist/schemes/_entry-ops.js.map +1 -1
  122. package/dist/schemes/_entry-send.d.ts +4 -1
  123. package/dist/schemes/_entry-send.d.ts.map +1 -1
  124. package/dist/schemes/_entry-send.js +57 -55
  125. package/dist/schemes/_entry-send.js.map +1 -1
  126. package/dist/server/ClientConnection.js +3 -3
  127. package/dist/server/ClientConnection.js.map +1 -1
  128. package/dist/server/Daemon.d.ts +5 -5
  129. package/dist/server/Daemon.d.ts.map +1 -1
  130. package/dist/server/Daemon.js +221 -176
  131. package/dist/server/Daemon.js.map +1 -1
  132. package/dist/server/clientTurn.d.ts +4 -1
  133. package/dist/server/clientTurn.d.ts.map +1 -1
  134. package/dist/server/clientTurn.js +19 -17
  135. package/dist/server/clientTurn.js.map +1 -1
  136. package/dist/server/dsl.d.ts +19 -16
  137. package/dist/server/dsl.d.ts.map +1 -1
  138. package/dist/server/dsl.js +127 -105
  139. package/dist/server/dsl.js.map +1 -1
  140. package/dist/server/envelope.d.ts +22 -19
  141. package/dist/server/envelope.d.ts.map +1 -1
  142. package/dist/server/envelope.js +116 -102
  143. package/dist/server/envelope.js.map +1 -1
  144. package/dist/server/logEntry.d.ts +4 -1
  145. package/dist/server/logEntry.d.ts.map +1 -1
  146. package/dist/server/logEntry.js +41 -39
  147. package/dist/server/logEntry.js.map +1 -1
  148. package/dist/server/methods/_dispatchAsClient.d.ts +3 -1
  149. package/dist/server/methods/_dispatchAsClient.d.ts.map +1 -1
  150. package/dist/server/methods/_dispatchAsClient.js +31 -29
  151. package/dist/server/methods/_dispatchAsClient.js.map +1 -1
  152. package/dist/server/methods/discover.d.ts +3 -1
  153. package/dist/server/methods/discover.d.ts.map +1 -1
  154. package/dist/server/methods/discover.js +8 -6
  155. package/dist/server/methods/discover.js.map +1 -1
  156. package/dist/server/methods/entry_read.d.ts +4 -1
  157. package/dist/server/methods/entry_read.d.ts.map +1 -1
  158. package/dist/server/methods/entry_read.js +54 -52
  159. package/dist/server/methods/entry_read.js.map +1 -1
  160. package/dist/server/methods/log_read.d.ts +4 -1
  161. package/dist/server/methods/log_read.d.ts.map +1 -1
  162. package/dist/server/methods/log_read.js +38 -36
  163. package/dist/server/methods/log_read.js.map +1 -1
  164. package/dist/server/methods/loop_cancel.d.ts +3 -1
  165. package/dist/server/methods/loop_cancel.d.ts.map +1 -1
  166. package/dist/server/methods/loop_cancel.js +19 -17
  167. package/dist/server/methods/loop_cancel.js.map +1 -1
  168. package/dist/server/methods/loop_resolve.d.ts +3 -1
  169. package/dist/server/methods/loop_resolve.d.ts.map +1 -1
  170. package/dist/server/methods/loop_resolve.js +42 -40
  171. package/dist/server/methods/loop_resolve.js.map +1 -1
  172. package/dist/server/methods/loop_run.d.ts +3 -1
  173. package/dist/server/methods/loop_run.d.ts.map +1 -1
  174. package/dist/server/methods/loop_run.js +104 -102
  175. package/dist/server/methods/loop_run.js.map +1 -1
  176. package/dist/server/methods/op_copy.d.ts +3 -1
  177. package/dist/server/methods/op_copy.d.ts.map +1 -1
  178. package/dist/server/methods/op_copy.js +25 -23
  179. package/dist/server/methods/op_copy.js.map +1 -1
  180. package/dist/server/methods/op_dispatch.d.ts +3 -1
  181. package/dist/server/methods/op_dispatch.d.ts.map +1 -1
  182. package/dist/server/methods/op_dispatch.js +18 -16
  183. package/dist/server/methods/op_dispatch.js.map +1 -1
  184. package/dist/server/methods/op_edit.d.ts +3 -1
  185. package/dist/server/methods/op_edit.d.ts.map +1 -1
  186. package/dist/server/methods/op_edit.js +23 -21
  187. package/dist/server/methods/op_edit.js.map +1 -1
  188. package/dist/server/methods/op_exec.d.ts +3 -1
  189. package/dist/server/methods/op_exec.d.ts.map +1 -1
  190. package/dist/server/methods/op_exec.js +20 -18
  191. package/dist/server/methods/op_exec.js.map +1 -1
  192. package/dist/server/methods/op_find.d.ts +3 -1
  193. package/dist/server/methods/op_find.d.ts.map +1 -1
  194. package/dist/server/methods/op_find.js +23 -21
  195. package/dist/server/methods/op_find.js.map +1 -1
  196. package/dist/server/methods/op_hide.d.ts +3 -1
  197. package/dist/server/methods/op_hide.d.ts.map +1 -1
  198. package/dist/server/methods/op_hide.js +23 -21
  199. package/dist/server/methods/op_hide.js.map +1 -1
  200. package/dist/server/methods/op_move.d.ts +3 -1
  201. package/dist/server/methods/op_move.d.ts.map +1 -1
  202. package/dist/server/methods/op_move.js +23 -21
  203. package/dist/server/methods/op_move.js.map +1 -1
  204. package/dist/server/methods/op_parse.d.ts +3 -1
  205. package/dist/server/methods/op_parse.d.ts.map +1 -1
  206. package/dist/server/methods/op_parse.js +24 -22
  207. package/dist/server/methods/op_parse.js.map +1 -1
  208. package/dist/server/methods/op_read.d.ts +3 -1
  209. package/dist/server/methods/op_read.d.ts.map +1 -1
  210. package/dist/server/methods/op_read.js +23 -21
  211. package/dist/server/methods/op_read.js.map +1 -1
  212. package/dist/server/methods/op_send.d.ts +3 -1
  213. package/dist/server/methods/op_send.d.ts.map +1 -1
  214. package/dist/server/methods/op_send.js +22 -20
  215. package/dist/server/methods/op_send.js.map +1 -1
  216. package/dist/server/methods/op_show.d.ts +3 -1
  217. package/dist/server/methods/op_show.d.ts.map +1 -1
  218. package/dist/server/methods/op_show.js +23 -21
  219. package/dist/server/methods/op_show.js.map +1 -1
  220. package/dist/server/methods/ping.d.ts +3 -1
  221. package/dist/server/methods/ping.d.ts.map +1 -1
  222. package/dist/server/methods/ping.js +8 -6
  223. package/dist/server/methods/ping.js.map +1 -1
  224. package/dist/server/methods/providers_list.d.ts +3 -1
  225. package/dist/server/methods/providers_list.d.ts.map +1 -1
  226. package/dist/server/methods/providers_list.js +19 -17
  227. package/dist/server/methods/providers_list.js.map +1 -1
  228. package/dist/server/methods/session_attach.d.ts +3 -1
  229. package/dist/server/methods/session_attach.d.ts.map +1 -1
  230. package/dist/server/methods/session_attach.js +43 -41
  231. package/dist/server/methods/session_attach.js.map +1 -1
  232. package/dist/server/methods/session_create.d.ts +3 -1
  233. package/dist/server/methods/session_create.d.ts.map +1 -1
  234. package/dist/server/methods/session_create.js +51 -49
  235. package/dist/server/methods/session_create.js.map +1 -1
  236. package/dist/server/methods/session_list.d.ts +3 -1
  237. package/dist/server/methods/session_list.d.ts.map +1 -1
  238. package/dist/server/methods/session_list.js +9 -7
  239. package/dist/server/methods/session_list.js.map +1 -1
  240. package/dist/server/methods/session_runs.d.ts +3 -1
  241. package/dist/server/methods/session_runs.d.ts.map +1 -1
  242. package/dist/server/methods/session_runs.js +19 -17
  243. package/dist/server/methods/session_runs.js.map +1 -1
  244. package/dist/server/methods/session_set_persona.d.ts +3 -1
  245. package/dist/server/methods/session_set_persona.d.ts.map +1 -1
  246. package/dist/server/methods/session_set_persona.js +28 -26
  247. package/dist/server/methods/session_set_persona.js.map +1 -1
  248. package/dist/server/methods/session_set_root.d.ts +3 -1
  249. package/dist/server/methods/session_set_root.d.ts.map +1 -1
  250. package/dist/server/methods/session_set_root.js +31 -29
  251. package/dist/server/methods/session_set_root.js.map +1 -1
  252. package/dist/server/yolo.d.ts +3 -1
  253. package/dist/server/yolo.d.ts.map +1 -1
  254. package/dist/server/yolo.js +15 -13
  255. package/dist/server/yolo.js.map +1 -1
  256. package/package.json +74 -30
  257. package/bin/plurnk-service.js +0 -112
  258. /package/migrations/{001_schema.sql → 0000-00-00.01_schema.sql} +0 -0
@@ -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 { writeEntry } from "../schemes/_entry-crud.js";
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 { sliceLinesRaw, isBinaryMimetype } from "@plurnk/plurnk-schemes";
8
+ import { LineMarkerOps, MimetypeBinary } from "../content/index.js";
6
9
  // Plain JS module shared with bin/digest.js so wire projection and
7
10
  // digest projection are structurally one function. tsconfig.build.json
8
11
  // has allowJs:true so this gets copied through to dist/.
9
- import { packetToWireMessages } from "./packet-wire.js";
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,56 @@ const fingerprintOp = (stmt) => {
131
152
  }
132
153
  return base;
133
154
  };
134
- // Per-turn fingerprint: sorted set of per-op fingerprints, joined. Order
135
- // within a turn doesn't matter — we want the SET of activities.
136
- export const fingerprintTurn = (ops) => {
137
- return ops.map(fingerprintOp).toSorted().join(",");
138
- };
139
- // Rail #39 cycle detector. For each candidate period k in [1, maxCyclePeriod],
140
- // check whether the last k*minCycles entries form minCycles repetitions of the
141
- // same length-k pattern. O(maxCyclePeriod × minCycles × max k) ≈ tiny. Rummy
142
- // parallel: src/plugins/error/error.js detectCycle.
143
- export const detectCycle = (history, minCycles, maxCyclePeriod) => {
144
- for (let k = 1; k <= maxCyclePeriod; k++) {
145
- const needed = k * minCycles;
146
- if (history.length < needed)
147
- continue;
148
- const tail = history.slice(-needed);
149
- const cycle = tail.slice(0, k);
150
- let match = true;
151
- outer: for (let rep = 0; rep < minCycles; rep++) {
152
- for (let j = 0; j < k; j++) {
153
- if (tail[rep * k + j] !== cycle[j]) {
154
- match = false;
155
- break outer;
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
- if (match)
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;
169
205
  // Per-loop transient buffer of actionless failures pending surface in the
170
206
  // NEXT packet's user.telemetry.errors[]. Drained by #buildTelemetryErrors.
171
207
  // Map<loopId, TelemetryError[]>. SPEC §15.1.
@@ -200,7 +236,7 @@ export default class Engine {
200
236
  // status code but has no way to surface why the loop degraded.
201
237
  // Per-grammar 0.17.0 protocol — see SPEC §15.1.
202
238
  #telemetryEventNotify;
203
- constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify }) {
239
+ constructor({ db, schemes, mimetypes, streamEventNotify, wakeRunNotify, telemetryEventNotify, tokenize }) {
204
240
  this.#db = db;
205
241
  this.#schemes = schemes;
206
242
  this.#streamEventNotify = streamEventNotify;
@@ -214,6 +250,11 @@ export default class Engine {
214
250
  discovery: { registry: emptyRegistry(), handlers: new Map() },
215
251
  });
216
252
  this.#previewBudget = readBudget();
253
+ this.#budgetCeiling = readCeiling();
254
+ // Tripwire default matches the Mimetypes boot affordance (SPEC §4.5):
255
+ // the divisor stands in only until the provider-backed tokenizer is
256
+ // wired by the Daemon. Real counts come from provider.countTokens.
257
+ this.#tokenize = tokenize ?? ((text) => Math.ceil(text.length / 4));
217
258
  }
218
259
  #pushTelemetry(sessionId, loopId, event) {
219
260
  const existing = this.#telemetryBuffer.get(loopId);
@@ -305,6 +346,12 @@ export default class Engine {
305
346
  turnNumber: turnIds.length + 1, maxTurns,
306
347
  });
307
348
  turnIds.push(turn.turnId);
349
+ // SPEC §14.4: budget hard-stop — packet won't fit even collapsed → abandon.
350
+ if (turn.budgetHardStop) {
351
+ await this.#db.engine_loop_cancel.run({ loop_id: loopId });
352
+ cleanup("forceful", "budget_overflow");
353
+ return { turnIds, finalStatus: 499, hitMaxTurns: false, reason: "budget_overflow" };
354
+ }
308
355
  // Rail #39: cycle detection. Push this turn's fingerprint to
309
356
  // history, scan for repetition patterns. Detection bumps
310
357
  // turnErrors so the strike system handles abandonment
@@ -315,9 +362,12 @@ export default class Engine {
315
362
  // reason for treating the turn as a failure, not its own alert.
316
363
  const state = this.#strikeState.get(loopId) ?? { streak: 0, turnErrors: 0, history: [] };
317
364
  state.history.push(turn.fingerprint);
318
- const cycle = detectCycle(state.history, minCycles, maxCyclePeriod);
365
+ const cycle = _a.detectCycle(state.history, minCycles, maxCyclePeriod);
319
366
  if (cycle.detected)
320
367
  state.turnErrors++;
368
+ // SPEC §14.4: a non-soft grinder fire counts toward the strike streak.
369
+ if (turn.budgetStruck)
370
+ state.turnErrors++;
321
371
  this.#strikeState.set(loopId, state);
322
372
  // Rail #38: strike accounting. Three sources strike a turn:
323
373
  // 1. recordedFailed — any action-entry at hard failure status
@@ -409,13 +459,61 @@ export default class Engine {
409
459
  nextActionIndex++;
410
460
  }
411
461
  }
462
+ // plurnk://manifest.json — rewritten EVERY turn (a live view of the
463
+ // entry set, which changes each turn). A derived view like the index,
464
+ // NOT an action — written directly (Engine.inject's path): no log entry,
465
+ // no sequence slot, not dispatched. The catalog body is built in the
466
+ // schemes layer (_entry-manifest); the engine only orchestrates the
467
+ // per-turn write. Does not list itself.
468
+ const systemCtx = {
469
+ db: this.#db, sessionId, runId, loopId, turnId,
470
+ writer: "system",
471
+ signal: this.#loopAborts.get(loopId)?.signal,
472
+ streamEventNotify: this.#streamEventNotify,
473
+ wakeRunNotify: this.#wakeRunNotify,
474
+ tokenize: this.#tokenize,
475
+ mimetypes: this.#mimetypes,
476
+ pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
477
+ };
478
+ // SPEC §14.3 D4/D5 — git-ls-files workspace membership, resolved at
479
+ // prompt-composition (EMI is eager + relevance-bounded). When the
480
+ // session's project_root is a git working tree, tracked files are
481
+ // members without a client `add`; active members are materialized
482
+ // (disk → body channel + visibility) so they surface in the index
483
+ // below. No-ops on headless / non-git sessions. Runs BEFORE the
484
+ // manifest + index build so this turn's packet reflects them.
485
+ await GitMembership.indexGitMembership(systemCtx);
486
+ await EntryCrud.writeEntry("manifest.json", {
487
+ channels: { body: { content: await EntryManifest.buildManifestBody(systemCtx, this.#previewBudget), mimetype: "application/json" } },
488
+ tags: [],
489
+ }, systemCtx, "plurnk");
412
490
  // Build the spec'd packet (Packet.json) request half. #buildLog
413
491
  // queries log_entries scoped to the run — the prompt entry just
414
492
  // written (if turn 1) is part of that query result.
415
- const requestPacket = await this.#buildRequestPacket({
493
+ let requestPacket = await this.#buildRequestPacket({
416
494
  initialMessages: messages, persona, requirements, runId, loopId,
417
495
  currentTurnSeq: seq, provider,
418
496
  });
497
+ // SPEC §14.4 — budget grinder, pre-LLM: reclaim window on actual overflow.
498
+ const enforced = await this.#enforceBudget({
499
+ packet: requestPacket, provider, runId, loopId, turnId, sessionId, turnNumber,
500
+ rebuild: (telemetryErrors) => this.#buildRequestPacket({
501
+ initialMessages: messages, persona, requirements, runId, loopId,
502
+ currentTurnSeq: seq, provider, telemetryErrors,
503
+ }),
504
+ });
505
+ requestPacket = enforced.packet;
506
+ if (!enforced.fit) {
507
+ // Hard 413: won't fit even with only the manifest left. Skip the LLM,
508
+ // close the turn, and let runLoop abandon (499).
509
+ const hardPacket = this.#completePacket(requestPacket, { content: "", ops: [], reasoning: null }, null, provider);
510
+ await this.#db.engine_close_turn.run({
511
+ id: turnId, status: 413, packet: JSON.stringify(hardPacket),
512
+ usage_prompt: 0, usage_completion: 0, usage_cached: 0, usage_cost_pico: 0,
513
+ finish_reason: "budget_hard_stop", model: provider.model,
514
+ });
515
+ return { turnId, status: 413, statuses: [], fingerprint: "", budgetStruck: enforced.struck, budgetHardStop: true };
516
+ }
419
517
  const modelMessages = this.#packetToWireMessages(requestPacket);
420
518
  const response = await provider.generate({ messages: modelMessages, signal });
421
519
  // Engine splits wire-level response: emission (content, reasoning,
@@ -506,7 +604,7 @@ export default class Engine {
506
604
  // nothing. Strike accounting (engine-internal) treats it as a
507
605
  // struck turn; the model just sees an empty packet next turn.
508
606
  // Per SPEC §15.1 gamification policy.
509
- return { turnId, status: turnStatus, statuses, fingerprint: fingerprintTurn(packetAssistant.ops) };
607
+ return { turnId, status: turnStatus, statuses, fingerprint: _a.fingerprintTurn(packetAssistant.ops), budgetStruck: enforced.struck, budgetHardStop: false };
510
608
  }
511
609
  // Split the wire-level ProviderResponse into the two destinations:
512
610
  // packet.assistant gets the model's emission (content, ops, reasoning);
@@ -568,7 +666,7 @@ export default class Engine {
568
666
  // and §user) BEFORE the provider call. The same packet object is then
569
667
  // completed with assistant + assistantRaw after the model responds, so
570
668
  // the stored packet and the wire payload share one source of truth.
571
- async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, }) {
669
+ async #buildRequestPacket({ initialMessages, persona: defaultPersona, requirements, runId, loopId, currentTurnSeq, provider, telemetryErrors: presetTelemetry, }) {
572
670
  const byRole = (role) => initialMessages.filter((m) => m.role === role).map((m) => m.content).join("\n\n");
573
671
  const system_definition = byRole("system");
574
672
  // user.prompt sources from the loop's most recent prompt entry first
@@ -587,65 +685,117 @@ export default class Engine {
587
685
  const persona = (row?.persona !== undefined && row?.persona !== null) ? row.persona : defaultPersona;
588
686
  const index = await this.#buildIndex(runId, loopId);
589
687
  const log = await this.#buildLog(runId);
590
- const telemetryErrors = await this.#buildTelemetryErrors(loopId, currentTurnSeq);
688
+ const telemetryErrors = presetTelemetry ?? await this.#buildTelemetryErrors(loopId, currentTurnSeq);
591
689
  // Per-section render-cost subtotals via provider's tokenizer.
592
690
  // Engine approximates each section by tokenizing its serialized
593
691
  // form — wire-payload tokens may differ slightly because chat-
594
692
  // template scaffolding adds bytes, but the subtotal tracks "what
595
693
  // the model has to process" closely enough for budget diagnostics.
596
- const systemTokens = provider.countTokens(system_definition) +
597
- provider.countTokens(persona) +
598
- provider.countTokens(JSON.stringify(index)) +
599
- provider.countTokens(JSON.stringify(log));
600
- // user.telemetry.budgetshimmed with section-aggregate table.
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);
604
- const userTokens = provider.countTokens(prompt) +
605
- provider.countTokens(JSON.stringify(telemetryErrors)) +
606
- provider.countTokens(budget) +
607
- provider.countTokens(requirements);
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
- },
694
+ const countTokens = (t) => provider.countTokens(t);
695
+ // Budget readout (SPEC.md §14.2). Two-pass: measure the wire-rendered
696
+ // index/log sections (budget-independent), install the readout with a
697
+ // tokensFree placeholder, measure the assembled total, resolve free,
698
+ // substitute. Subtotals come from the real render meta and fences
699
+ // included not a serialized approximation. ceiling is the provider's
700
+ // window × PLURNK_BUDGET_CEILING (null when no window is reported →
701
+ // headline omitted, section lines still shown).
702
+ const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
703
+ const scratch = {
704
+ system: { system_definition, persona, index, log },
705
+ user: { prompt, telemetry: { budget: "", errors: telemetryErrors }, system_requirements: requirements },
622
706
  };
707
+ const sections = PacketWire.measureBudgetSections(scratch, countTokens);
708
+ scratch.user.telemetry.budget = this.#renderBudget(sections, ceiling);
709
+ const total = countTokens(PacketWire.renderSystemContent(scratch.system)) + countTokens(PacketWire.renderUserContent(scratch.user));
710
+ const tokensFree = ceiling === null ? null : Math.max(0, ceiling - total);
711
+ const percent = ceiling === null ? null : Math.round((total / ceiling) * 100);
712
+ const budget = tokensFree === null
713
+ ? scratch.user.telemetry.budget
714
+ : scratch.user.telemetry.budget
715
+ .replace(TOKEN_USAGE_PLACEHOLDER, String(total))
716
+ .replace(TOKEN_PERCENT_PLACEHOLDER, String(percent))
717
+ .replace(TOKENS_FREE_PLACEHOLDER, String(tokensFree));
718
+ const system = { tokens: 0, system_definition, persona, index, log };
719
+ const user = { tokens: 0, prompt, telemetry: { budget, errors: telemetryErrors }, system_requirements: requirements };
720
+ system.tokens = countTokens(PacketWire.renderSystemContent(system));
721
+ user.tokens = countTokens(PacketWire.renderUserContent(user));
722
+ return { system, user };
723
+ }
724
+ // Budget readout body, rendered into the `# Plurnk System Budget` section.
725
+ // Headline `ceiling/free` only when a ceiling exists; section lines for the
726
+ // curatable index/log weight the model can HIDE back. tokensFree is a
727
+ // placeholder here — buildSystem substitutes it after measuring the packet.
728
+ #renderBudget(sections, ceiling) {
729
+ const lines = [];
730
+ if (ceiling !== null)
731
+ lines.push(`ceiling ${ceiling} · usage ${TOKEN_USAGE_PLACEHOLDER} (${TOKEN_PERCENT_PLACEHOLDER}%) · free ${TOKENS_FREE_PLACEHOLDER}`);
732
+ if (sections.index.channels > 0)
733
+ lines.push(`Index previews: ${sections.index.channels} channels, ${sections.index.tokens} tokens`);
734
+ if (sections.log.entries > 0) {
735
+ lines.push(`Log entries: ${sections.log.entries} entries, ${sections.log.tokens} tokens`);
736
+ if (sections.log.byScheme.length > 0) {
737
+ lines.push("| scheme | entries | tokens |", "|---|--:|--:|");
738
+ for (const s of sections.log.byScheme)
739
+ lines.push(`| ${s.scheme} | ${s.entries} | ${s.tokens} |`);
740
+ }
741
+ }
742
+ return lines.join("\n");
623
743
  }
624
- // user.telemetry.budgetSHIM. 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) {
630
- const userPromptTokens = provider.countTokens(prompt);
631
- const userErrTokens = provider.countTokens(JSON.stringify(telemetryErrors));
632
- const userTokens = userPromptTokens + userErrTokens;
633
- const total = systemTokens + userTokens;
634
- const pct = (n) => total === 0 ? "0.0%" : `${((n / total) * 100).toFixed(1)}%`;
635
- return [
636
- "| Section | Used | Percent |",
637
- "|---|---|---|",
638
- `| system | ${systemTokens} | ${pct(systemTokens)} |`,
639
- `| user | ${userTokens} | ${pct(userTokens)} |`,
640
- `| **Total** | **${total}** | **100.0%** |`,
641
- ].join("\n");
744
+ // SPEC §14.4the budget grinder. Runs pre-LLM (in runTurn, after the packet
745
+ // is built, before provider.generate); fires only on actual overflow. Two
746
+ // passes, re-measuring between. Hides (never deletes) the prior turn's logs,
747
+ // then the catalog except the manifest lifeline. The strike it raises and the
748
+ // hard-stop it can signal are returned to runLoop, which owns abandonment.
749
+ async #enforceBudget({ packet, provider, runId, loopId, turnId, sessionId, turnNumber, rebuild }) {
750
+ const ceiling = _a.computeCeiling(provider.contextSize, this.#budgetCeiling);
751
+ const measure = (p) => p.system.tokens + p.user.tokens;
752
+ if (ceiling === null || measure(packet) <= ceiling)
753
+ return { packet, fit: true, struck: false };
754
+ const hidden = new Map();
755
+ const note = (scheme) => { hidden.set(scheme, (hidden.get(scheme) ?? 0) + 1); };
756
+ // Pass 1 prior-turn rollback: hide the latest emissions (the ones that
757
+ // pushed it over). No prior turn (turn 1, env overflow) → no-op → pass 2.
758
+ const priorLogs = await this.#db.engine_grinder_prior_turn_logs.all({ loop_id: loopId, turn_id: turnId });
759
+ for (const le of priorLogs)
760
+ note(le.scheme ?? "log");
761
+ if (priorLogs.length > 0)
762
+ await this.#db.engine_grinder_hide_prior_turn_logs.run({ loop_id: loopId, turn_id: turnId });
763
+ const errors = packet.user.telemetry.errors;
764
+ let current = priorLogs.length > 0 ? await rebuild(errors) : packet;
765
+ if (measure(current) <= ceiling) {
766
+ this.#emitBudgetOverflow(sessionId, loopId, hidden);
767
+ return { packet: current, fit: true, struck: turnNumber > 1 };
768
+ }
769
+ // Pass 2 — index collapse: hide every catalog entry except the manifest.
770
+ const catalog = await this.#db.engine_grinder_catalog.all({ run_id: runId, session_id: sessionId });
771
+ for (const c of catalog)
772
+ note(c.scheme);
773
+ if (catalog.length > 0) {
774
+ const pairs = JSON.stringify(catalog.map((c) => ({ entry_id: c.entry_id, channel: c.channel })));
775
+ await this.#db.engine_grinder_hide_catalog.run({ run_id: runId, pairs });
776
+ }
777
+ current = catalog.length > 0 ? await rebuild(errors) : current;
778
+ this.#emitBudgetOverflow(sessionId, loopId, hidden);
779
+ return { packet: current, fit: measure(current) <= ceiling, struck: turnNumber > 1 };
780
+ }
781
+ // The model-facing budget event (SPEC §14.4, §15.1): which entries left the
782
+ // window, by scheme — the model's own terms, no mechanism vocabulary. The
783
+ // strike this overflow triggers stays engine-internal (gamification policy).
784
+ #emitBudgetOverflow(sessionId, loopId, hidden) {
785
+ if (hidden.size === 0)
786
+ return;
787
+ this.#pushTelemetry(sessionId, loopId, {
788
+ source: "engine:rail",
789
+ kind: "budget_overflow",
790
+ hidden: [...hidden.entries()].map(([scheme, count]) => ({ scheme, count })),
791
+ });
642
792
  }
643
793
  // Wire projection lives in ./packet-wire.js (plain JS) so Engine and
644
794
  // bin/digest.js import the exact same function — structurally one
645
795
  // implementation, no drift between wire and digest possible.
646
796
  // Format: markdown (user pick over rummy's XML alternative, 2026-05-22).
647
797
  #packetToWireMessages(packet) {
648
- return packetToWireMessages(packet);
798
+ return PacketWire.packetToWireMessages(packet);
649
799
  }
650
800
  // Complete the packet by adding the model's response. After this the
651
801
  // packet matches Packet.json fully and is ready for storage.
@@ -779,6 +929,7 @@ export default class Engine {
779
929
  content: result.preview,
780
930
  mimetype: row.mimetype,
781
931
  tokens: row.tokens,
932
+ lines: result.totalLines,
782
933
  };
783
934
  }
784
935
  return [...entries.values()];
@@ -793,6 +944,7 @@ export default class Engine {
793
944
  streamEventNotify: this.#streamEventNotify,
794
945
  wakeRunNotify: this.#wakeRunNotify,
795
946
  mimetypes: this.#mimetypes,
947
+ tokenize: this.#tokenize,
796
948
  pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
797
949
  };
798
950
  let result;
@@ -906,6 +1058,7 @@ export default class Engine {
906
1058
  writer: "model", signal: this.#loopAborts.get(loopId)?.signal,
907
1059
  streamEventNotify: this.#streamEventNotify,
908
1060
  wakeRunNotify: this.#wakeRunNotify,
1061
+ tokenize: this.#tokenize,
909
1062
  pushTelemetry: (event) => this.#pushTelemetry(sessionId, loopId, event),
910
1063
  };
911
1064
  const applyResult = await handler.applyResolution({
@@ -995,13 +1148,14 @@ export default class Engine {
995
1148
  signal: this.#loopAborts.get(loopId)?.signal,
996
1149
  streamEventNotify: this.#streamEventNotify,
997
1150
  wakeRunNotify: this.#wakeRunNotify,
1151
+ tokenize: this.#tokenize,
998
1152
  pushTelemetry: (event) => this.#pushTelemetry(sessionRow.session_id, loopId, event),
999
1153
  };
1000
1154
  const entry = {
1001
1155
  channels: { body: { content: prompt, mimetype: "text/markdown" } },
1002
1156
  tags: [],
1003
1157
  };
1004
- await writeEntry(pathname, entry, ctx, "plurnk");
1158
+ await EntryCrud.writeEntry(pathname, entry, ctx, "plurnk");
1005
1159
  return { loopId, turnSeq };
1006
1160
  }
1007
1161
  // Subscribe to proposal-pending events. Daemon registers a listener
@@ -1173,8 +1327,9 @@ export default class Engine {
1173
1327
  const srcHandler = this.#schemes.get(srcSchemeName);
1174
1328
  if (srcHandler === undefined || typeof srcHandler.deleteEntry !== "function")
1175
1329
  return { status: 501 };
1176
- // Null-body MOVE = delete the source entry (per SPEC §6.5)
1177
- if (dstPath === null) {
1330
+ // MOVE to /dev/null (the grammar's idiomatic delete) or a null-body
1331
+ // MOVE deletes the source entry. SPEC §6.5.
1332
+ if (dstPath === null || pathnameFromPath(dstPath) === "/dev/null") {
1178
1333
  const srcPathname = pathnameFromPath(srcPath);
1179
1334
  const delResult = await srcHandler.deleteEntry(srcPathname, ctx);
1180
1335
  return { status: delResult.status };
@@ -1206,12 +1361,12 @@ export default class Engine {
1206
1361
  if (srcResult.status !== 200 || srcResult.entry === null)
1207
1362
  return { status: 404, error: `COPY/MOVE source not found: ${srcSchemeName}://${srcPathname}` };
1208
1363
  const entry = srcResult.entry;
1209
- // Conflict check on destination
1210
- if (typeof dstHandler.readEntry === "function") {
1211
- const dstExists = await dstHandler.readEntry(dstPathname, ctx);
1212
- if (dstExists.status === 200)
1213
- return { status: 409, error: `COPY/MOVE destination exists: ${dstSchemeName}://${dstPathname}` };
1214
- }
1364
+ // Destination read the conflict/no-op verdict is deferred until the
1365
+ // to-be-written content is known (after <L> slice + tag resolution below),
1366
+ // so an identical re-copy resolves to 304 instead of a phantom 409.
1367
+ const dstExisting = typeof dstHandler.readEntry === "function"
1368
+ ? await dstHandler.readEntry(dstPathname, ctx)
1369
+ : null;
1215
1370
  // Mimetype compatibility check against the destination scheme's manifest
1216
1371
  const dstManifest = dstHandler.constructor.manifest;
1217
1372
  const dstChannels = dstManifest?.channels ?? {};
@@ -1230,10 +1385,10 @@ export default class Engine {
1230
1385
  if (lineMarker !== null) {
1231
1386
  const sliced = {};
1232
1387
  for (const [channelName, channelData] of Object.entries(entry.channels)) {
1233
- if (isBinaryMimetype(channelData.mimetype)) {
1388
+ if (MimetypeBinary.isBinaryMimetype(channelData.mimetype)) {
1234
1389
  return { status: 415, error: `cannot slice <L> on binary channel '${channelName}' (${channelData.mimetype})` };
1235
1390
  }
1236
- const r = sliceLinesRaw(channelData.content ?? "", lineMarker);
1391
+ const r = LineMarkerOps.sliceLinesRaw(channelData.content ?? "", lineMarker);
1237
1392
  if (r.status !== 200)
1238
1393
  return { status: r.status, error: r.error };
1239
1394
  sliced[channelName] = { ...channelData, content: r.text ?? "" };
@@ -1244,6 +1399,21 @@ export default class Engine {
1244
1399
  const tags = (Array.isArray(statement.signal) && statement.signal.length > 0)
1245
1400
  ? statement.signal
1246
1401
  : entry.tags;
1402
+ // 304/409 on an existing destination (SPEC §6.4): a re-copy that would write
1403
+ // exactly what's already there — same channel contents, same tags — is a no-op
1404
+ // (304), mirroring EDIT's 304-on-noop (§6.1). A divergent destination is a real
1405
+ // collision (409); COPY/MOVE never clobbers.
1406
+ if (dstExisting !== null && dstExisting.status === 200 && dstExisting.entry !== null) {
1407
+ const dstChannels = dstExisting.entry.channels;
1408
+ const writeNames = Object.keys(channels).sort();
1409
+ const dstNames = Object.keys(dstChannels).sort();
1410
+ const sameContent = writeNames.length === dstNames.length
1411
+ && writeNames.every((n, i) => n === dstNames[i] && (channels[n]?.content ?? "") === (dstChannels[n]?.content ?? ""));
1412
+ const sameTags = [...tags].sort().join("") === [...dstExisting.entry.tags].sort().join("");
1413
+ if (sameContent && sameTags)
1414
+ return { status: 304 };
1415
+ return { status: 409, error: `COPY/MOVE destination exists: ${dstSchemeName}://${dstPathname}` };
1416
+ }
1247
1417
  const writeResult = await dstHandler.writeEntry(dstPathname, { channels, tags }, ctx);
1248
1418
  return { status: writeResult.status, entryId: writeResult.entryId, created: writeResult.created };
1249
1419
  }
@@ -1322,6 +1492,8 @@ export default class Engine {
1322
1492
  }
1323
1493
  }
1324
1494
  const attrs = JSON.stringify(attrsObj);
1495
+ const txJson = JSON.stringify(statement);
1496
+ const rxJson = JSON.stringify(result);
1325
1497
  const row = await this.#db.engine_insert_log_entry.get({
1326
1498
  run_id: runId,
1327
1499
  loop_id: loopId,
@@ -1340,11 +1512,12 @@ export default class Engine {
1340
1512
  params: target.params,
1341
1513
  fragment: target.fragment,
1342
1514
  lineMarker: lineMarkerJson,
1343
- tx: JSON.stringify(statement),
1515
+ tx: txJson,
1344
1516
  mimetype_tx: "application/json",
1345
- rx: JSON.stringify(result),
1517
+ rx: rxJson,
1346
1518
  mimetype_rx: "application/json",
1347
1519
  status_rx: result.status,
1520
+ tokens: this.#tokenize(txJson) + this.#tokenize(rxJson),
1348
1521
  state: isProposed ? "proposed" : "resolved",
1349
1522
  outcome: null,
1350
1523
  attrs,
@@ -1375,4 +1548,6 @@ export default class Engine {
1375
1548
  return JSON.stringify(signal);
1376
1549
  }
1377
1550
  }
1551
+ _a = Engine;
1552
+ export default Engine;
1378
1553
  //# sourceMappingURL=Engine.js.map