@cortexkit/opencode-magic-context 0.49.16 → 0.49.17

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 (204) hide show
  1. package/dist/auto-search-worker.js +76 -63
  2. package/dist/chunk-embedding-write-worker.js +11 -11
  3. package/dist/{compaction-marker-candidates-v5c6997n.js → compaction-marker-candidates-n4krhat4.js} +2 -2
  4. package/dist/compaction-marker-candidates-worker.js +2 -2
  5. package/dist/config/schema/magic-context.d.ts +394 -394
  6. package/dist/{conflict-warning-hook-znry4j76.js → conflict-warning-hook-4pmz879v.js} +5 -5
  7. package/dist/{context-3znk23va.js → context-yhetp0kn.js} +294 -144
  8. package/dist/{embedding-backfill-q4n8dend.js → embedding-backfill-fmz50tsy.js} +11 -11
  9. package/dist/embedding-worker-process.js +1 -1
  10. package/dist/embedding-worker.js +4 -4
  11. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +1 -1
  12. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  13. package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  15. package/dist/features/magic-context/memory/project-identity-cache.d.ts +2 -0
  16. package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -1
  17. package/dist/features/magic-context/message-index-async.d.ts +1 -1
  18. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  19. package/dist/features/magic-context/message-index.d.ts +5 -0
  20. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  21. package/dist/features/magic-context/search.d.ts +6 -1
  22. package/dist/features/magic-context/search.d.ts.map +1 -1
  23. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage-meta-shared.d.ts +2 -2
  25. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  26. package/dist/features/magic-context/storage-session-tables.d.ts +9 -1
  27. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/auto-search-deadline.d.ts +2 -1
  30. package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/auto-search-hint.d.ts +3 -0
  32. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/auto-search-worker-client.d.ts +10 -2
  35. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
  37. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/historian-host-runner.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/lkg-recovery-fence.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts +8 -0
  43. package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts.map +1 -0
  44. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/tag-messages.d.ts +6 -1
  46. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/todo-view.d.ts +2 -2
  48. package/dist/hooks/magic-context/tool-drop-target.d.ts +3 -2
  49. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/transform-stage-logger.d.ts +3 -0
  51. package/dist/hooks/magic-context/transform-stage-logger.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/transform.d.ts +1 -0
  53. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  54. package/dist/{index-9nkg2dzk.js → index-24fezq97.js} +3 -3
  55. package/dist/{index-a8bvhwqs.js → index-2vxtcb95.js} +32 -3
  56. package/dist/{index-f71thw8j.js → index-4s3t0nd0.js} +1 -1
  57. package/dist/{index-kn4t1haw.js → index-4yt7dtf4.js} +450 -227
  58. package/dist/{index-v9asb16b.js → index-5a1ex6x8.js} +5 -5
  59. package/dist/{index-fkx2qnvw.js → index-6x57ar45.js} +2 -2
  60. package/dist/{index-dqe8pq4x.js → index-a6mc0v4d.js} +6 -3
  61. package/dist/{index-ayym1x7f.js → index-a7c4r5re.js} +145 -736
  62. package/dist/{index-ek1yxvvz.js → index-aabzay3h.js} +165 -46
  63. package/dist/{index-hehkf6zm.js → index-b5vqckbw.js} +12 -6
  64. package/dist/{v2/server-wafzvyhg.js → index-beht37jy.js} +8 -0
  65. package/dist/{index-7b2cfmaz.js → index-bjcnntmz.js} +2 -2
  66. package/dist/{index-hqkczed7.js → index-c5pytzne.js} +22 -2
  67. package/dist/{index-p4vde22c.js → index-cwzfcbtq.js} +956 -77
  68. package/dist/{index-cj9g0j7a.js → index-fb2qxs2y.js} +7 -7
  69. package/dist/{index-nd7m2a5a.js → index-hqhm3wke.js} +25 -3
  70. package/dist/{index-5mwk1nek.js → index-jn12fvvh.js} +1 -1
  71. package/dist/{index-n6m03s3n.js → index-ke416mdc.js} +1 -1
  72. package/dist/{index-6a6dq53h.js → index-kmdj7dsq.js} +5 -5
  73. package/dist/{index-mtw9e71a.js → index-kq82mtg0.js} +2 -2
  74. package/dist/{index-n78avt2p.js → index-n37sebck.js} +1 -1
  75. package/dist/{index-v63beg1z.js → index-nkpmm9q8.js} +3 -3
  76. package/dist/{index-j6zw70pf.js → index-p7m7j0cp.js} +24 -16
  77. package/dist/{index-wk0n1cgt.js → index-qn7xstt2.js} +2 -2
  78. package/dist/{index-saews2s3.js → index-rr2bb45z.js} +1 -1
  79. package/dist/{index-7pa32z07.js → index-s734jcsr.js} +1 -1
  80. package/dist/{index-b17m64g9.js → index-s74zqahq.js} +1 -1
  81. package/dist/{index-wv9n2zcq.js → index-v8awdgvd.js} +4 -4
  82. package/dist/{index-4qfy7v57.js → index-vrzjhftj.js} +31 -20
  83. package/dist/{index-qrahzcqv.js → index-ze7mvk54.js} +10 -7
  84. package/dist/index.js +39 -39
  85. package/dist/{message-fts-rowid-map-yvx5a9jw.js → message-fts-rowid-map-17s0dfn4.js} +5 -3
  86. package/dist/{message-index-bounded-6th8djy9.js → message-index-bounded-1633vc7f.js} +17 -17
  87. package/dist/{message-index-t4pz7akd.js → message-index-dprebmtt.js} +13 -13
  88. package/dist/{message-time-backfill-8egzkd3z.js → message-time-backfill-73mmm76q.js} +1 -1
  89. package/dist/migration-worker.js +8 -8
  90. package/dist/plugin/dream-timer.d.ts +11 -0
  91. package/dist/plugin/dream-timer.d.ts.map +1 -1
  92. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  93. package/dist/{project-embedding-registry-2dea0a77.js → project-embedding-registry-z74mhqkf.js} +10 -10
  94. package/dist/raw-ordinal-warmup-worker.js +10 -10
  95. package/dist/{read-session-chunk-4pwqxm5a.js → read-session-chunk-4zp09ejb.js} +7 -7
  96. package/dist/{read-session-raw-5ar8se5v.js → read-session-raw-rsc8c7qy.js} +2 -2
  97. package/dist/{recomp-orchestrator-tjcaj5x7.js → recomp-orchestrator-z3ecszx6.js} +24 -24
  98. package/dist/{search-rb01n29x.js → search-6mee3vty.js} +20 -20
  99. package/dist/{search-archive-02jws7hw.js → search-archive-znyy44ve.js} +8 -8
  100. package/dist/{send-session-notification-2x9edkrp.js → send-session-notification-6ve0e8p4.js} +4 -4
  101. package/dist/shared/historian-cache.d.ts +8 -0
  102. package/dist/shared/historian-cache.d.ts.map +1 -0
  103. package/dist/shared/model-suggestion-retry.d.ts +2 -0
  104. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  105. package/dist/shared/rpc-discovery-cleanup.d.ts +3 -0
  106. package/dist/shared/rpc-discovery-cleanup.d.ts.map +1 -0
  107. package/dist/shared/rpc-server.d.ts +2 -0
  108. package/dist/shared/rpc-server.d.ts.map +1 -1
  109. package/dist/shared/search-text.d.ts +8 -0
  110. package/dist/shared/search-text.d.ts.map +1 -0
  111. package/dist/shared/sqlite.d.ts +4 -2
  112. package/dist/shared/sqlite.d.ts.map +1 -1
  113. package/dist/shared/storage-file-mutex.d.ts.map +1 -1
  114. package/dist/shared/subagent-runner.d.ts +2 -0
  115. package/dist/shared/subagent-runner.d.ts.map +1 -1
  116. package/dist/shared/system-directive.d.ts +2 -0
  117. package/dist/shared/system-directive.d.ts.map +1 -1
  118. package/dist/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
  119. package/dist/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
  120. package/dist/{storage-tags-4z1w27xa.js → storage-tags-8qns3012.js} +8 -4
  121. package/dist/{transform-postprocess-phase-xaw5c3xp.js → transform-postprocess-phase-3ydzxa62.js} +24 -24
  122. package/dist/v2/auto-search-worker.js +76 -63
  123. package/dist/v2/chunk-embedding-write-worker.js +11 -11
  124. package/dist/v2/{compaction-marker-candidates-1vqwr3s5.js → compaction-marker-candidates-ebx3388k.js} +2 -2
  125. package/dist/v2/compaction-marker-candidates-worker.js +2 -2
  126. package/dist/v2/{context-acw1nhv8.js → context-jn3zpeqh.js} +882 -393
  127. package/dist/v2/embedding-backfill-7c7yr69y.js +23 -0
  128. package/dist/v2/embedding-worker-process.js +1 -1
  129. package/dist/v2/embedding-worker.js +4 -4
  130. package/dist/v2/fold/native-replay.d.ts +11 -2
  131. package/dist/v2/fold/native-replay.d.ts.map +1 -1
  132. package/dist/v2/fold/policy.d.ts.map +1 -1
  133. package/dist/v2/fold/restore.d.ts.map +1 -1
  134. package/dist/v2/hooks/context.d.ts.map +1 -1
  135. package/dist/v2/hooks/hidden-child.d.ts +8 -0
  136. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  137. package/dist/v2/hooks/types.d.ts +17 -0
  138. package/dist/v2/hooks/types.d.ts.map +1 -1
  139. package/dist/v2/{message-fts-rowid-map-8vf0v7cr.js → message-fts-rowid-map-wfx67gfd.js} +5 -3
  140. package/dist/v2/{message-index-bounded-dsrf49y7.js → message-index-bounded-hpejqyd8.js} +17 -17
  141. package/dist/v2/{message-index-e6qzw477.js → message-index-pxehjpzx.js} +13 -13
  142. package/dist/v2/{message-time-backfill-q5pgxf0d.js → message-time-backfill-04t0km1z.js} +1 -1
  143. package/dist/v2/migration-worker.js +8 -8
  144. package/dist/v2/{project-embedding-registry-rek75aqw.js → project-embedding-registry-gnkj56xp.js} +10 -10
  145. package/dist/v2/raw-ordinal-warmup-worker.js +10 -10
  146. package/dist/v2/{read-session-chunk-7bpygfv2.js → read-session-chunk-zdppn4bj.js} +7 -7
  147. package/dist/v2/{read-session-raw-pw2n1qgc.js → read-session-raw-yaegtjzh.js} +2 -2
  148. package/dist/v2/{recomp-orchestrator-hzpgg06e.js → recomp-orchestrator-cmqnfpp3.js} +24 -24
  149. package/dist/v2/{search-sf2vg5pk.js → search-66842dya.js} +20 -20
  150. package/dist/v2/{search-archive-fn4wy8sx.js → search-archive-gfh11vs0.js} +8 -8
  151. package/dist/v2/{send-session-notification-hmr7q0nt.js → send-session-notification-3pxn658x.js} +4 -4
  152. package/dist/v2/{server-785mb90s.js → server-1wy7b9de.js} +6 -3
  153. package/dist/v2/{server-g59aatsz.js → server-3rhpvy84.js} +956 -77
  154. package/dist/v2/{server-kqpawe1a.js → server-46neh5fa.js} +12 -6
  155. package/dist/v2/{server-rrqpvwqd.js → server-6tmj0s7j.js} +2 -2
  156. package/dist/v2/{server-kr2kc9dz.js → server-8by8qfec.js} +10 -7
  157. package/dist/v2/{server-vbe4se57.js → server-a9xqj88f.js} +1 -1
  158. package/dist/{index-wafzvyhg.js → v2/server-beht37jy.js} +8 -0
  159. package/dist/v2/{server-05xjtq6c.js → server-d04vrx0j.js} +1 -1
  160. package/dist/v2/{server-zyw55ygv.js → server-ec4ca92p.js} +5 -5
  161. package/dist/v2/{server-6fyp0v67.js → server-fw7wrk01.js} +145 -737
  162. package/dist/v2/{server-zs8d0s3b.js → server-g3qma5c0.js} +1 -1
  163. package/dist/v2/{server-00hx04yn.js → server-gpvay0xz.js} +2 -2
  164. package/dist/v2/{server-r9gjxcte.js → server-gtfhttbr.js} +7 -7
  165. package/dist/v2/{server-gha9x4qq.js → server-h1v0pyxb.js} +2 -2
  166. package/dist/v2/{server-hgg55hde.js → server-hy3d3vsx.js} +1 -1
  167. package/dist/v2/{server-1hcbw661.js → server-kh7dpa1a.js} +24 -16
  168. package/dist/v2/{server-vgpab6rd.js → server-my7k4eds.js} +4 -4
  169. package/dist/v2/{server-d8tj10db.js → server-n1prf1g6.js} +1 -1
  170. package/dist/v2/{server-n78avt2p.js → server-n37sebck.js} +1 -1
  171. package/dist/v2/{server-y5pznjt9.js → server-nj659894.js} +5 -5
  172. package/dist/v2/{server-e3ftpk7v.js → server-rmygf5pn.js} +32 -3
  173. package/dist/v2/{server-0ppsweh6.js → server-tgnbmwah.js} +22 -2
  174. package/dist/v2/{server-n5q841qn.js → server-vsw8tmm6.js} +31 -20
  175. package/dist/v2/{server-6714jebr.js → server-xj136kt8.js} +1 -1
  176. package/dist/v2/{server-0mzjjw3s.js → server-zyzawhdw.js} +25 -3
  177. package/dist/v2/{server-aavz8pm6.js → server-zzkj2fch.js} +3 -3
  178. package/dist/v2/server.js +4 -4
  179. package/dist/v2/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
  180. package/dist/v2/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
  181. package/dist/v2/{storage-tags-hkwtbp3g.js → storage-tags-1h8k6ra9.js} +8 -4
  182. package/dist/v2/store-reader.d.ts +2 -0
  183. package/dist/v2/store-reader.d.ts.map +1 -1
  184. package/dist/v2/{wrapup-orchestrator-x4xj1782.js → wrapup-orchestrator-xs8redz9.js} +24 -24
  185. package/dist/wrapup-orchestrator-j7d7ry5b.js +48 -0
  186. package/package.json +1 -1
  187. package/src/config/latch-permanence-guard.test.ts +4 -0
  188. package/src/shared/historian-cache.test.ts +69 -0
  189. package/src/shared/historian-cache.ts +55 -0
  190. package/src/shared/model-suggestion-retry.ts +2 -0
  191. package/src/shared/rpc-discovery-cleanup.test.ts +57 -0
  192. package/src/shared/rpc-discovery-cleanup.ts +57 -0
  193. package/src/shared/rpc-server.ts +36 -13
  194. package/src/shared/search-text.test.ts +47 -0
  195. package/src/shared/search-text.ts +63 -0
  196. package/src/shared/sqlite.test.ts +56 -0
  197. package/src/shared/sqlite.ts +51 -2
  198. package/src/shared/storage-file-mutex-review.test.ts +22 -2
  199. package/src/shared/storage-file-mutex.ts +11 -1
  200. package/src/shared/subagent-runner.ts +2 -0
  201. package/src/shared/system-directive.ts +19 -1
  202. package/src/shared/tag-transcript.ts +12 -4
  203. package/dist/v2/embedding-backfill-jyd6492b.js +0 -23
  204. package/dist/wrapup-orchestrator-vj2tbp22.js +0 -48
@@ -1,7 +1,8 @@
1
1
  import { randomBytes, timingSafeEqual } from "node:crypto";
2
- import { readdirSync, readFileSync, unlinkSync } from "node:fs";
2
+ import { readdirSync, readFileSync, rmdirSync, unlinkSync } from "node:fs";
3
3
  import type { Server, ServerWebSocket } from "bun";
4
4
  import { log } from "./logger";
5
+ import { reapDeadRpcDiscovery } from "./rpc-discovery-cleanup";
5
6
  import {
6
7
  acknowledgeNotifications,
7
8
  drainNotifications,
@@ -82,6 +83,7 @@ export class MagicContextRpcServer {
82
83
  private portFilePath: string;
83
84
  private portDir: string;
84
85
  private startedAt = Date.now();
86
+ private stopped = false;
85
87
  private readonly instanceId = randomBytes(8).toString("hex");
86
88
  private unregisterOwnInstance: (() => void) | null = null;
87
89
  /** Every authenticated WS socket, so dispose can close them all. */
@@ -93,7 +95,10 @@ export class MagicContextRpcServer {
93
95
  // discovers/guesses the port.
94
96
  private readonly token = randomBytes(32).toString("hex");
95
97
 
96
- constructor(storageDir: string, directory: string) {
98
+ constructor(
99
+ private storageDir: string,
100
+ directory: string,
101
+ ) {
97
102
  this.portFilePath = rpcPortFilePath(storageDir, directory, process.pid, this.instanceId);
98
103
  this.portDir = rpcPortDir(storageDir, directory);
99
104
  }
@@ -131,6 +136,9 @@ export class MagicContextRpcServer {
131
136
  log("rpc server skipped: Bun runtime not available (no TUI consumer)");
132
137
  return 0;
133
138
  }
139
+ this.stopped = false;
140
+ await reapDeadRpcDiscovery(this.storageDir);
141
+ if (this.stopped) return 0;
134
142
  this.startedAt = Date.now();
135
143
  const self = this;
136
144
  const server = inRootAsyncContext(() =>
@@ -172,17 +180,26 @@ export class MagicContextRpcServer {
172
180
  // The discovery file carries the RPC bearer token. The shared writer
173
181
  // creates its directory and temporary file with owner-only modes.
174
182
  this.unregisterOwnInstance ??= registerOwnRpcServerInstance(this.instanceId);
175
- writeStorageFileAtomicSync(
176
- this.portFilePath,
177
- JSON.stringify({
178
- port: this.port,
179
- pid: process.pid,
180
- started_at: this.startedAt,
181
- kind: "OpenCode server",
182
- token: this.token,
183
- instance_id: this.instanceId,
184
- }),
185
- );
183
+ const publish = () =>
184
+ writeStorageFileAtomicSync(
185
+ this.portFilePath,
186
+ JSON.stringify({
187
+ port: this.port,
188
+ pid: process.pid,
189
+ started_at: this.startedAt,
190
+ kind: "OpenCode server",
191
+ token: this.token,
192
+ instance_id: this.instanceId,
193
+ }),
194
+ );
195
+ try {
196
+ publish();
197
+ } catch (error) {
198
+ // An empty-directory reaper can race the writer before its temp
199
+ // file exists. Recreate the directory and retry that case only.
200
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
201
+ publish();
202
+ }
186
203
  log(`[rpc] server listening on 127.0.0.1:${this.port}`);
187
204
  } catch (err) {
188
205
  log(`[rpc] failed to write port file: ${err}`);
@@ -193,6 +210,7 @@ export class MagicContextRpcServer {
193
210
 
194
211
  /** Stop the server: close every socket, stop accepting, remove port file. */
195
212
  stop(): void {
213
+ this.stopped = true;
196
214
  for (const ws of this.sockets) {
197
215
  try {
198
216
  if (ws.data.authTimer) clearTimeout(ws.data.authTimer);
@@ -213,6 +231,11 @@ export class MagicContextRpcServer {
213
231
  } catch {
214
232
  // Intentional: port file may already be gone
215
233
  }
234
+ try {
235
+ rmdirSync(this.portDir);
236
+ } catch {
237
+ // Other instances' discovery records must survive this server's stop.
238
+ }
216
239
  this.unregisterOwnInstance?.();
217
240
  this.unregisterOwnInstance = null;
218
241
  }
@@ -0,0 +1,47 @@
1
+ import { expect, test } from "bun:test";
2
+ import { cleanSearchText, keywordRelevance, matchedSearchSnippet } from "./search-text";
3
+ import { removeSystemReminders } from "./system-directive";
4
+
5
+ test("nested reminder contents stay quarantined through the outer closing tag", () => {
6
+ expect(
7
+ removeSystemReminders(
8
+ 'before <SYSTEM-REMINDER origin="host">outer <system-reminder>inner</system-reminder> leaked tail</SYSTEM-REMINDER> after',
9
+ ),
10
+ ).toBe("before after");
11
+ expect(removeSystemReminders("before <system-reminder>unfinished")).toBe("before");
12
+ expect(removeSystemReminders("before </system-reminder> after")).toBe("before after");
13
+ });
14
+
15
+ test("mismatched notification closes cannot release hidden content", () => {
16
+ expect(cleanSearchText("kept <task>hidden </task_cohort> still hidden</task> kept")).toBe(
17
+ "kept kept",
18
+ );
19
+ expect(cleanSearchText("kept <task id='finished'/> kept")).toBe("kept kept");
20
+ });
21
+
22
+ test("task notification trees and cut legacy XML never become hint text", () => {
23
+ expect(
24
+ cleanSearchText(
25
+ '<task_cohort id="parent"><task id="child">background result</task>status</task_cohort> actual answer',
26
+ ),
27
+ ).toBe("actual answer");
28
+ expect(cleanSearchText('<task id="truncated">background result…')).toBe("");
29
+ });
30
+
31
+ test("keyword confidence measures absolute distinct term coverage, not result rank", () => {
32
+ expect(keywordRelevance("explain historian cache wiring", "cache icons")).toBeCloseTo(0.2);
33
+ expect(
34
+ keywordRelevance("historian cache wiring", "historian cache wiring details"),
35
+ ).toBeCloseTo(0.8);
36
+ expect(keywordRelevance("historian cache wiring", "<task>historian cache wiring</task>")).toBe(
37
+ 0,
38
+ );
39
+ });
40
+
41
+ test("matched snippets prefer a dense passage over an irrelevant first line", () => {
42
+ const content = `Header cache. ${"padding ".repeat(150)}Historian cache wiring uses incremental pools. ${"trailing ".repeat(100)}`;
43
+ const snippet = matchedSearchSnippet(content, "historian cache wiring", 80);
44
+ expect(snippet).toContain("Historian cache wiring");
45
+ expect(snippet).not.toContain("Header");
46
+ expect(matchedSearchSnippet(content, "unrelated deployment")).toBe("");
47
+ });
@@ -0,0 +1,63 @@
1
+ import { removeNestedBlocks } from "./system-directive";
2
+
3
+ export function removeTaskNotifications(text: string): string {
4
+ return removeNestedBlocks(text, ["task", "task_cohort"]);
5
+ }
6
+
7
+ /** Remove injected notifications before scoring or rendering; previously saved hints replay unchanged. */
8
+ export function cleanSearchText(text: string): string {
9
+ return removeNestedBlocks(text, ["system-reminder", "task", "task_cohort"])
10
+ .replace(/\s+/g, " ")
11
+ .trim();
12
+ }
13
+
14
+ const QUERY_STOP_WORDS = new Set(
15
+ "a an and are as at be been by can could did do does for from how i in is it me my of on or our please should that the their them there these they this to us was we were what when where which who why will with would you your".split(
16
+ " ",
17
+ ),
18
+ );
19
+
20
+ function terms(text: string): string[] {
21
+ return [
22
+ ...new Set(
23
+ (text.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter(
24
+ (term) => !QUERY_STOP_WORDS.has(term),
25
+ ),
26
+ ),
27
+ ];
28
+ }
29
+
30
+ /** Score the share of distinct query terms present, independently of other results. */
31
+ export function keywordRelevance(query: string, content: string): number {
32
+ const needles = terms(query);
33
+ if (needles.length === 0) return 0;
34
+ const words = new Set(terms(cleanSearchText(content)));
35
+ return (0.8 * needles.filter((term) => words.has(term)).length) / needles.length;
36
+ }
37
+
38
+ /** Pick the densest matching passage before compression can discard the matched words. */
39
+ export function matchedSearchSnippet(content: string, query: string, cap = 220): string {
40
+ const text = cleanSearchText(content);
41
+ const needles = new Set(terms(query));
42
+ const matches = [...text.matchAll(/[\p{L}\p{N}_]+/gu)].filter((match) =>
43
+ needles.has(match[0].toLowerCase()),
44
+ );
45
+ if (matches.length === 0) return "";
46
+ let bestStart = matches[0].index;
47
+ let bestCoverage = -1;
48
+ for (const match of matches) {
49
+ const start = Math.max(0, match.index - Math.floor(cap / 5));
50
+ const coverage = keywordRelevance(query, text.slice(start, start + cap));
51
+ if (coverage > bestCoverage) {
52
+ bestStart = start;
53
+ bestCoverage = coverage;
54
+ }
55
+ }
56
+ // Avoid emitting half a word at the left edge of a passage.
57
+ if (bestStart > 0) {
58
+ const boundary = text.indexOf(" ", bestStart);
59
+ if (boundary >= 0 && boundary < matches[matches.length - 1].index) bestStart = boundary + 1;
60
+ }
61
+ const passage = text.slice(bestStart, bestStart + cap).trim();
62
+ return `${bestStart > 0 ? "…" : ""}${passage}${bestStart + cap < text.length ? "…" : ""}`;
63
+ }
@@ -1,14 +1,40 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import { spawnSync } from "node:child_process";
3
+ import { createHash } from "node:crypto";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { join } from "node:path";
3
6
  import {
4
7
  Database,
5
8
  detectSqliteRuntime,
6
9
  getSqliteMemoryStats,
7
10
  loadSqliteModule,
11
+ openImmutableDatabase,
8
12
  SqliteRuntimeUnavailableError,
9
13
  } from "./sqlite";
14
+ import { createTestTempDir } from "./test-temp-dir";
10
15
 
11
16
  describe("SQLite runtime selector", () => {
17
+ it("imports the shared SQLite module in Bun without node:sqlite available", () => {
18
+ const sqliteUrl = new URL("./sqlite.ts", import.meta.url).href;
19
+ const script = `
20
+ const getBuiltinModule = process.getBuiltinModule;
21
+ process.getBuiltinModule = function (specifier) {
22
+ if (specifier === "node:sqlite") throw new Error("node:sqlite deliberately unavailable");
23
+ return getBuiltinModule.call(process, specifier);
24
+ };
25
+ await import(${JSON.stringify(sqliteUrl)});
26
+ console.log("shared SQLite import succeeded");
27
+ `;
28
+ const child = spawnSync(process.execPath, ["-e", script], {
29
+ encoding: "utf8",
30
+ windowsHide: true,
31
+ });
32
+
33
+ expect(child.error).toBeUndefined();
34
+ expect(child.status).toBe(0);
35
+ expect(child.stdout).toContain("shared SQLite import succeeded");
36
+ });
37
+
12
38
  it("suppresses only the node:sqlite experimental warning in a real Node process", () => {
13
39
  // Node 24.21 no longer emits this warning, so inject it during loading
14
40
  // to keep the suppression assertion load-bearing on newer runtimes.
@@ -80,6 +106,36 @@ describe("SQLite runtime selector", () => {
80
106
  expect(getSqliteMemoryStats().connectionCount).toBe(before);
81
107
  });
82
108
 
109
+ it("reads a checkpointed WAL database without creating sidecars or changing bytes", () => {
110
+ const { dir: directory, cleanup } = createTestTempDir(
111
+ "mc-plugin-test-xdg-pid-",
112
+ "immutable-sqlite-",
113
+ );
114
+ const path = join(directory, "context.db");
115
+ try {
116
+ const writer = new Database(path);
117
+ writer.exec(
118
+ "PRAGMA journal_mode=WAL; CREATE TABLE sample (value INTEGER); INSERT INTO sample VALUES (42); PRAGMA wal_checkpoint(TRUNCATE)",
119
+ );
120
+ writer.close();
121
+ expect(existsSync(`${path}-wal`)).toBe(false);
122
+ expect(existsSync(`${path}-shm`)).toBe(false);
123
+
124
+ const before = createHash("sha256").update(readFileSync(path)).digest("hex");
125
+ const reader = openImmutableDatabase(path);
126
+ try {
127
+ expect(reader.prepare("SELECT value FROM sample").get()).toEqual({ value: 42 });
128
+ } finally {
129
+ reader.close();
130
+ }
131
+ expect(createHash("sha256").update(readFileSync(path)).digest("hex")).toBe(before);
132
+ expect(existsSync(`${path}-wal`)).toBe(false);
133
+ expect(existsSync(`${path}-shm`)).toBe(false);
134
+ } finally {
135
+ cleanup();
136
+ }
137
+ });
138
+
83
139
  it("wraps a missing node:sqlite module with the detected runtime and cause", async () => {
84
140
  const cause = Object.assign(new Error("No such built-in module: node:sqlite"), {
85
141
  code: "ERR_UNKNOWN_BUILTIN_MODULE",
@@ -8,8 +8,8 @@
8
8
  * flag-free since Node 22.13/23.4).
9
9
  *
10
10
  * Bun has no `node:sqlite`, and Node/Electron have no `bun:sqlite`. Static
11
- * imports of either would crash at parse time in the wrong runtime, so we use
12
- * dynamic imports gated by runtime detection.
11
+ * imports of either would crash at parse time in the wrong runtime, so the
12
+ * selected backend is loaded only after runtime detection.
13
13
  *
14
14
  * Why `node:sqlite` instead of `better-sqlite3`: better-sqlite3 is a native
15
15
  * module requiring per-ABI prebuilds, and Electron's ABI never matches the npm
@@ -41,6 +41,7 @@
41
41
 
42
42
  import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
43
43
  import { statSync } from "node:fs";
44
+ import { pathToFileURL } from "node:url";
44
45
 
45
46
  // Type import only — runtime is loaded dynamically below. @types/better-sqlite3
46
47
  // has the richest definitions and is a structural superset of the API surface
@@ -140,6 +141,13 @@ const bunSpec = "bun:" + "sqlite";
140
141
  const nodeSpec = "node:" + "sqlite";
141
142
 
142
143
  async function importSqliteModule(specifier: string): Promise<SqliteModule> {
144
+ if (specifier === nodeSpec && typeof process.getBuiltinModule === "function") {
145
+ const loaded = process.getBuiltinModule(specifier) as SqliteModule | undefined;
146
+ if (loaded) return loaded;
147
+ throw Object.assign(new Error(`No such built-in module: ${specifier}`), {
148
+ code: "ERR_UNKNOWN_BUILTIN_MODULE",
149
+ });
150
+ }
143
151
  // The runtime chooses this specifier; Vite must not resolve it as a
144
152
  // build-time dependency because the other runtime's backend is absent.
145
153
  return (await import(
@@ -252,6 +260,7 @@ const sqliteModule = await loadSqliteModule(detectedRuntime);
252
260
  const DatabaseImpl: typeof BetterSqlite3 = isBun
253
261
  ? (sqliteModule.Database as typeof BetterSqlite3)
254
262
  : buildNodeSqliteDatabaseClass(sqliteModule.DatabaseSync);
263
+ let immutableDatabaseImpl: typeof BetterSqlite3 | undefined;
255
264
 
256
265
  interface TrackedSqliteConnection {
257
266
  sequence: number;
@@ -655,6 +664,46 @@ function buildNodeSqliteDatabaseClass(DatabaseSync: any): typeof BetterSqlite3 {
655
664
 
656
665
  export const Database: typeof BetterSqlite3 = TrackedDatabase;
657
666
 
667
+ function immutableDatabaseImplementation(): typeof BetterSqlite3 {
668
+ if (!isBun) return DatabaseImpl;
669
+ if (immutableDatabaseImpl) return immutableDatabaseImpl;
670
+
671
+ const getBuiltinModule = process.getBuiltinModule;
672
+ if (typeof getBuiltinModule !== "function")
673
+ throw new SqliteRuntimeUnavailableError(
674
+ "Bun",
675
+ nodeSpec,
676
+ new Error("This Bun runtime does not expose process.getBuiltinModule"),
677
+ );
678
+ const nodeSqlite = getBuiltinModule.call(process, nodeSpec) as SqliteModule | undefined;
679
+ if (!nodeSqlite?.DatabaseSync)
680
+ throw new SqliteRuntimeUnavailableError(
681
+ "Bun",
682
+ nodeSpec,
683
+ new Error(`No such built-in module: ${nodeSpec}`),
684
+ );
685
+
686
+ immutableDatabaseImpl = buildNodeSqliteDatabaseClass(nodeSqlite.DatabaseSync);
687
+ return immutableDatabaseImpl;
688
+ }
689
+
690
+ /** Open a closed SQLite file without asking SQLite to create missing WAL sidecars. */
691
+ export function openImmutableDatabase(path: string): BetterSqlite3.Database {
692
+ // Immutable mode avoids creating missing WAL sidecars; callers must first rule out writers.
693
+ const uri = pathToFileURL(path);
694
+ uri.searchParams.set("immutable", "1");
695
+ const target = immutableDatabaseImplementation();
696
+ const db = inRootAsyncContext(
697
+ () =>
698
+ Reflect.construct(
699
+ target,
700
+ [uri.href, { readonly: true }],
701
+ target,
702
+ ) as BetterSqlite3.Database,
703
+ );
704
+ return trackSqliteConnection(db, uri.href, { readonly: true });
705
+ }
706
+
658
707
  /** The constructor path, without querying SQLite on the prompt thread. */
659
708
  export function getSqliteDatabasePath(db: Database): string | null {
660
709
  for (const connection of trackedSqliteConnections.values()) {
@@ -1,13 +1,33 @@
1
- import { expect, test } from "bun:test";
1
+ import { expect, spyOn, test } from "bun:test";
2
2
  import { spawnSync } from "node:child_process";
3
- import { readdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { readdirSync, readFileSync, rmdirSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { __resetRpcIdentityTestHooks, __setRpcIdentityTestHooks } from "./rpc-utils";
6
6
  import { withStorageFileMutex } from "./storage-file-mutex";
7
+ import * as permissions from "./storage-permissions";
7
8
  import { createTestTempDir } from "./test-temp-dir";
8
9
 
9
10
  const mutex = join(import.meta.dir, "storage-file-mutex.ts");
10
11
 
12
+ test("mutex retries ticket creation when empty-directory cleanup wins the mkdir race", () => {
13
+ const { dir: root, cleanup } = createTestTempDir("mc-mutex-rmdir-");
14
+ const directory = join(root, "locks");
15
+ const write = permissions.writeStorageFileSync;
16
+ let attempts = 0;
17
+ const writer = spyOn(permissions, "writeStorageFileSync").mockImplementation((...args) => {
18
+ if (attempts++ === 0) rmdirSync(directory);
19
+ return write(...args);
20
+ });
21
+ try {
22
+ expect(withStorageFileMutex(directory, () => "acquired")).toBe("acquired");
23
+ // The successful contender also rewrites its ticket atomically once.
24
+ expect(attempts).toBe(3);
25
+ } finally {
26
+ writer.mockRestore();
27
+ cleanup();
28
+ }
29
+ });
30
+
11
31
  async function holder(root: string) {
12
32
  const script = `
13
33
  import { existsSync } from 'node:fs';
@@ -68,7 +68,17 @@ export function withStorageFileMutex<T>(directory: string, work: () => T, timeou
68
68
  const ticket: Ticket = { choosing: true, number: 0, birth, checksum: "" };
69
69
  const encodedTicket = () =>
70
70
  JSON.stringify({ ...ticket, checksum: ticketChecksum(name, ticket) });
71
- writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
71
+ for (let attempt = 0; ; attempt++) {
72
+ try {
73
+ writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
74
+ break;
75
+ } catch (error) {
76
+ // A session cleanup can remove an empty directory between mkdir and
77
+ // ticket creation. No ticket exists yet, so retry before contending.
78
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT" || attempt >= 2) throw error;
79
+ ensurePrivateStorageDirectorySync(directory);
80
+ }
81
+ }
72
82
  const deadline = performance.now() + timeoutMs;
73
83
  const peers = (): [string, Ticket | null][] => {
74
84
  const found: [string, Ticket | null][] = [];
@@ -64,6 +64,8 @@ export interface SubagentRunOptions {
64
64
  agent: string;
65
65
  systemPrompt: string;
66
66
  userMessage: string;
67
+ /** Hidden historian only: use `write` to retain cache markers for retries or later turns. */
68
+ cacheTail?: import("./historian-cache").HistorianCacheTail;
67
69
  model?: string | undefined;
68
70
  fallbackModels?: readonly ModelInput[];
69
71
  timeoutMs?: number | undefined;
@@ -5,5 +5,23 @@ export function isSystemDirective(text: string): boolean {
5
5
  }
6
6
 
7
7
  export function removeSystemReminders(text: string): string {
8
- return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, "").trim();
8
+ return removeNestedBlocks(text, ["system-reminder"]).trim();
9
+ }
10
+
11
+ /** Remove matching tagged regions, including nested regions and regions with no closing tag. */
12
+ export function removeNestedBlocks(text: string, names: readonly string[]): string {
13
+ const tags = new RegExp(`<(/?)(${names.join("|")})(?:\\s[^<>]*?)?\\s*(/?)>`, "gi");
14
+ const stack: string[] = [];
15
+ let end = 0;
16
+ let result = "";
17
+ for (const match of text.matchAll(tags)) {
18
+ if (stack.length === 0) result += text.slice(end, match.index);
19
+ const name = match[2].toLowerCase();
20
+ if (match[1]) {
21
+ if (stack[stack.length - 1] === name) stack.pop();
22
+ } else if (!match[3]) stack.push(name);
23
+ end = match.index + match[0].length;
24
+ }
25
+ if (stack.length === 0) result += text.slice(end);
26
+ return result;
9
27
  }
@@ -835,12 +835,17 @@ class ConversationEndGuard {
835
835
  }
836
836
  }
837
837
 
838
- wouldStrand(occurrences: readonly ToolOccurrence[]): boolean {
838
+ wouldStrand(
839
+ occurrences: readonly ToolOccurrence[],
840
+ plannedRemovals?: ReadonlySet<unknown>,
841
+ ): boolean {
839
842
  const end = this.end;
840
843
  if (!end || !occurrences.some((occ) => occ.message === end)) return false;
841
844
  const leaving = new Set(occurrences.map((occ) => partKey(occ.part)));
842
845
  const survives = (part: TranscriptPart) =>
843
- !leaving.has(partKey(part)) && !this.removed.has(partKey(part));
846
+ !leaving.has(partKey(part)) &&
847
+ !this.removed.has(partKey(part)) &&
848
+ !plannedRemovals?.has(partKey(part));
844
849
  return end.info.role === "assistant"
845
850
  ? !end.parts.some((part) => part.kind === "tool_result" && survives(part))
846
851
  : !end.parts.some((part) => partHasContent(part) && survives(part));
@@ -1380,8 +1385,11 @@ function buildAggregateTarget(
1380
1385
  }
1381
1386
  return null;
1382
1387
  },
1383
- wouldStrandConversationEnd(): boolean {
1384
- return conversationEnd?.wouldStrand(occurrences) ?? false;
1388
+ wouldStrandConversationEnd(plannedRemovals): boolean {
1389
+ return conversationEnd?.wouldStrand(occurrences, plannedRemovals) ?? false;
1390
+ },
1391
+ notePlannedRemoval(plannedRemovals): void {
1392
+ for (const occ of occurrences) plannedRemovals.add(partKey(occ.part));
1385
1393
  },
1386
1394
  editMarker(): "truncated" | "absent" {
1387
1395
  // Edit-marker: preserve the tool_use input's filePath + a region
@@ -1,23 +0,0 @@
1
- import {
2
- ensureMemoryEmbeddings2
3
- } from "./server-aavz8pm6.js";
4
- import"./server-kr2kc9dz.js";
5
- import"./server-4dhv7mqz.js";
6
- import"./server-qt1qqasc.js";
7
- import"./server-gha9x4qq.js";
8
- import"./server-271qxsf5.js";
9
- import"./server-hgg55hde.js";
10
- import"./server-kqpawe1a.js";
11
- import"./server-rrqpvwqd.js";
12
- import"./server-e3ftpk7v.js";
13
- import"./server-785mb90s.js";
14
- import"./server-6714jebr.js";
15
- import"./server-51779zs2.js";
16
- import"./server-6pmqg9m4.js";
17
- import"./server-0ppsweh6.js";
18
- import"./server-wafzvyhg.js";
19
- import"./server-f2xf5gcb.js";
20
- import"./server-pavzsdc6.js";
21
- export {
22
- ensureMemoryEmbeddings2 as ensureMemoryEmbeddings
23
- };
@@ -1,48 +0,0 @@
1
- import {
2
- runManagedWrapup2
3
- } from "./index-v9asb16b.js";
4
- import"./index-v63beg1z.js";
5
- import"./index-ayym1x7f.js";
6
- import"./index-f71thw8j.js";
7
- import"./index-wv9n2zcq.js";
8
- import"./index-p4vde22c.js";
9
- import"./index-sd3egckz.js";
10
- import"./index-9nkg2dzk.js";
11
- import"./index-qrahzcqv.js";
12
- import"./index-n78avt2p.js";
13
- import"./index-4dhv7mqz.js";
14
- import"./index-v10f1rjv.js";
15
- import"./index-b17m64g9.js";
16
- import"./index-qt1qqasc.js";
17
- import"./index-j6zw70pf.js";
18
- import"./index-nd7m2a5a.js";
19
- import"./index-1zx87sdm.js";
20
- import"./index-mtw9e71a.js";
21
- import"./index-x8a7jmkh.js";
22
- import"./index-y9f76s3f.js";
23
- import"./index-7b2cfmaz.js";
24
- import"./index-wgypt3cs.js";
25
- import"./index-saews2s3.js";
26
- import"./index-hehkf6zm.js";
27
- import"./index-fkx2qnvw.js";
28
- import"./index-cj9g0j7a.js";
29
- import"./index-fb3zxn06.js";
30
- import"./index-wk0n1cgt.js";
31
- import"./index-a8bvhwqs.js";
32
- import"./index-dqe8pq4x.js";
33
- import"./index-fr0wbhdn.js";
34
- import"./index-nj7yykkx.js";
35
- import"./index-n6m03s3n.js";
36
- import"./index-bp5083x2.js";
37
- import"./index-58e3y90n.js";
38
- import"./index-7pa32z07.js";
39
- import"./index-v0p37hm2.js";
40
- import"./index-k29z4aqh.js";
41
- import"./index-hqkczed7.js";
42
- import"./index-wafzvyhg.js";
43
- import"./index-j75v2476.js";
44
- import"./index-g2q4htwp.js";
45
- import"./index-pavzsdc6.js";
46
- export {
47
- runManagedWrapup2 as runManagedWrapup
48
- };