@cortexkit/opencode-magic-context 0.49.1 → 0.49.3

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 (114) hide show
  1. package/README.md +2 -38
  2. package/dist/auto-search-worker.js +3 -3
  3. package/dist/compaction-marker-candidates-dd638ypn.js +19 -0
  4. package/dist/compaction-marker-candidates-worker.js +24 -0
  5. package/dist/{conflict-warning-hook-asnt6hfc.js → conflict-warning-hook-ae8nmv7c.js} +5 -4
  6. package/dist/{context-tfyhtdcs.js → context-9tbxh883.js} +63 -29
  7. package/dist/{embedding-backfill-wyc3ykyf.js → embedding-backfill-aj488s8x.js} +5 -5
  8. package/dist/features/magic-context/compaction-marker-candidates-worker.d.ts +2 -0
  9. package/dist/features/magic-context/compaction-marker-candidates-worker.d.ts.map +1 -0
  10. package/dist/features/magic-context/compaction-marker-candidates.d.ts +47 -0
  11. package/dist/features/magic-context/compaction-marker-candidates.d.ts.map +1 -0
  12. package/dist/features/magic-context/compaction-marker.d.ts +12 -0
  13. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/project-identity.d.ts +5 -1
  15. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  16. package/dist/features/magic-context/message-index-async.d.ts +3 -0
  17. package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
  18. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  19. package/dist/features/magic-context/storage-clone.d.ts +2 -0
  20. package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
  21. package/dist/features/magic-context/storage-tags.d.ts +1 -1
  22. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  23. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +3 -0
  24. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/history-boundary-repair.d.ts +11 -2
  27. package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/protected-tail-boundary.d.ts +4 -3
  29. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts +10 -0
  31. package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  35. package/dist/{index-sfhndpcb.js → index-3wckxwfp.js} +39 -39
  36. package/dist/{index-gzky5rva.js → index-41gqa557.js} +4 -2
  37. package/dist/{index-r2zcs71c.js → index-7ndnvw7a.js} +1 -1
  38. package/dist/{index-sv0bb7vg.js → index-7zg9dvx0.js} +35 -4
  39. package/dist/{index-xgrwmj82.js → index-bhk710rw.js} +1 -1
  40. package/dist/{index-fg00p1cg.js → index-c0b01zrc.js} +2 -2
  41. package/dist/{index-rafsaxac.js → index-fjtgekd4.js} +2 -2
  42. package/dist/{index-fdtkt5r1.js → index-fst3t0d9.js} +9 -9
  43. package/dist/{index-6tfxmc4n.js → index-je2na55n.js} +4 -4
  44. package/dist/{index-h5zfkjyk.js → index-jpm52v9x.js} +6 -24
  45. package/dist/{index-s7fc0fkn.js → index-m4p77myp.js} +1 -1
  46. package/dist/{index-xnanvj3w.js → index-mgy5dwcw.js} +607 -579
  47. package/dist/{index-0pjvpfkf.js → index-qvr78ej6.js} +1 -1
  48. package/dist/{index-zfzdpeq2.js → index-s4ztdn8v.js} +1311 -1271
  49. package/dist/index-v0p37hm2.js +22 -0
  50. package/dist/{index-eypn4ba6.js → index-w9pe0nx1.js} +3 -3
  51. package/dist/{index-p0n5ryq8.js → index-ws3gcy5m.js} +19 -9
  52. package/dist/{index-att4bfan.js → index-xfbfrngb.js} +1 -1
  53. package/dist/{index-nydv6g8r.js → index-yqps2zsv.js} +44 -6
  54. package/dist/index-yxawqmdd.js +212 -0
  55. package/dist/{index-zg8yq4ya.js → index-znzgze2v.js} +3 -3
  56. package/dist/index.js +38 -35
  57. package/dist/{message-index-bounded-hqktkhdy.js → message-index-bounded-pv6ct15d.js} +18 -17
  58. package/dist/message-index-jqa7qd0s.js +60 -0
  59. package/dist/migration-worker.js +3 -3
  60. package/dist/{project-embedding-registry-0gv3h6y8.js → project-embedding-registry-fcmszatg.js} +4 -4
  61. package/dist/raw-ordinal-warmup-worker.js +22 -5
  62. package/dist/{read-session-chunk-5mh0bh8y.js → read-session-chunk-cwgjwjd0.js} +7 -6
  63. package/dist/{read-session-raw-8sjbc9h0.js → read-session-raw-z38cn0as.js} +2 -1
  64. package/dist/{recomp-orchestrator-kpkhkjhq.js → recomp-orchestrator-txcc802e.js} +19 -17
  65. package/dist/{search-p9w39qv4.js → search-v1t47s2q.js} +14 -13
  66. package/dist/{send-session-notification-xffh3bbe.js → send-session-notification-yp9m5g4p.js} +4 -3
  67. package/dist/shared/tag-owner-id.d.ts +1 -0
  68. package/dist/shared/tag-owner-id.d.ts.map +1 -1
  69. package/dist/{storage-tags-k0pda32s.js → storage-tags-0ssbmzg9.js} +2 -2
  70. package/dist/{transform-postprocess-phase-481j5a7z.js → transform-postprocess-phase-113rwew1.js} +19 -17
  71. package/dist/v2/auto-search-worker.js +3 -3
  72. package/dist/v2/compaction-marker-candidates-1s21e2fz.js +18 -0
  73. package/dist/v2/compaction-marker-candidates-worker.js +24 -0
  74. package/dist/v2/{context-gsdtha5v.js → context-p1xf3w5q.js} +115 -44
  75. package/dist/v2/{embedding-backfill-dbvjbcmm.js → embedding-backfill-9hbkfvnz.js} +5 -5
  76. package/dist/v2/{message-index-bounded-p6v0n8qh.js → message-index-bounded-c0810p5w.js} +17 -16
  77. package/dist/v2/message-index-h8ssgq3q.js +59 -0
  78. package/dist/v2/migration-worker.js +3 -3
  79. package/dist/v2/{project-embedding-registry-nyq7z8y6.js → project-embedding-registry-42th7k1z.js} +4 -4
  80. package/dist/v2/raw-ordinal-warmup-worker.js +22 -5
  81. package/dist/v2/{read-session-chunk-fhyfe4qa.js → read-session-chunk-n86v9xfg.js} +6 -5
  82. package/dist/v2/{read-session-raw-j8dvp7f4.js → read-session-raw-3b3ehga4.js} +2 -1
  83. package/dist/v2/{recomp-orchestrator-5xy7w136.js → recomp-orchestrator-v168jwgg.js} +16 -14
  84. package/dist/v2/{search-j7bakcwa.js → search-048v8y02.js} +13 -12
  85. package/dist/v2/{send-session-notification-f6v698r5.js → send-session-notification-a4h2azxa.js} +2 -1
  86. package/dist/v2/{server-bgzy0phk.js → server-3bc6k4k4.js} +62 -33
  87. package/dist/v2/{server-7h861yms.js → server-3dqjhchy.js} +5 -18
  88. package/dist/v2/{server-c7n2ftng.js → server-4n2tknpc.js} +34 -3
  89. package/dist/v2/server-51779zs2.js +19 -0
  90. package/dist/v2/{server-kh958yj8.js → server-5zrpr764.js} +1 -1
  91. package/dist/v2/{server-6tcrjfw8.js → server-8pqnnmf9.js} +1 -1
  92. package/dist/v2/server-9290a1mb.js +212 -0
  93. package/dist/v2/{server-7vkj6zhb.js → server-9a6v0wth.js} +3 -3
  94. package/dist/v2/{server-1fszbabv.js → server-cjz51jm2.js} +2 -2
  95. package/dist/v2/{server-7spkq1y6.js → server-cxxjb33r.js} +2286 -2246
  96. package/dist/v2/{server-mqvtz7wz.js → server-hetyb88z.js} +9 -9
  97. package/dist/v2/{server-4c7jt8gq.js → server-pcygfdbs.js} +44 -6
  98. package/dist/v2/{server-8xmq9h3f.js → server-q6j5dasr.js} +4 -2
  99. package/dist/v2/{server-zbxjj2mz.js → server-rygvffsf.js} +2 -2
  100. package/dist/v2/server.js +4 -4
  101. package/dist/v2/{storage-tags-5yxrvkaf.js → storage-tags-3a4cavqb.js} +2 -2
  102. package/dist/v2/{wrapup-orchestrator-yem5422s.js → wrapup-orchestrator-zcyf8ymw.js} +14 -12
  103. package/dist/{wrapup-orchestrator-xvrr27ws.js → wrapup-orchestrator-5aa2q4v3.js} +20 -18
  104. package/package.json +3 -3
  105. package/src/config/latch-permanence-guard.test.ts +6 -1
  106. package/src/shared/sqlite-writer-diagnostics.test.ts +21 -10
  107. package/src/shared/tag-owner-id.ts +13 -0
  108. package/src/shared/tag-transcript.test.ts +6 -1
  109. package/src/shared/tag-transcript.ts +2 -1
  110. package/src/shared/windows-hidden-spawns.test.ts +60 -4
  111. /package/dist/{index-69vas84q.js → index-e6xnpach.js} +0 -0
  112. /package/dist/{index-p1hnwfd8.js → index-gcdb3rqw.js} +0 -0
  113. /package/dist/v2/{index-69vas84q.js → index-e6xnpach.js} +0 -0
  114. /package/dist/v2/{server-s2st631p.js → server-8wcf79fe.js} +0 -0
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  buildCanonicalChunkTextFromFts,
19
19
  loadCompartmentChunkEmbeddingsForSearch
20
- } from "./server-s2st631p.js";
20
+ } from "./server-8wcf79fe.js";
21
21
  import {
22
22
  recordEmbeddingMeasurement,
23
23
  saveCommitEmbedding,
@@ -34,7 +34,7 @@ import {
34
34
  enqueueShadowEmbeddingItems2,
35
35
  getProjectEmbeddingSnapshot2,
36
36
  embedItemsForProject2
37
- } from "./server-1fszbabv.js";
37
+ } from "./server-cjz51jm2.js";
38
38
  import {
39
39
  getProjectEmbeddings2
40
40
  } from "./server-4dhv7mqz.js";
@@ -43,7 +43,7 @@ import {
43
43
  isEmbeddingEnabled,
44
44
  embedText,
45
45
  ensureMemoryEmbeddings2
46
- } from "./server-kh958yj8.js";
46
+ } from "./server-5zrpr764.js";
47
47
  import {
48
48
  ModuleMemoryAuthorityError2,
49
49
  getMemoriesByProject2,
@@ -58,20 +58,20 @@ import {
58
58
  sanitizeFtsQuery,
59
59
  searchMemoriesFTS,
60
60
  searchMemoriesFTSUnion
61
- } from "./server-zbxjj2mz.js";
61
+ } from "./server-rygvffsf.js";
62
62
  import {
63
63
  isRecord
64
- } from "./server-4c7jt8gq.js";
64
+ } from "./server-pcygfdbs.js";
65
65
  import {
66
- getIndexedMessageCorpusSize
67
- } from "./server-bgzy0phk.js";
66
+ getIndexedMessageCorpusSize2
67
+ } from "./server-3bc6k4k4.js";
68
68
  import {
69
69
  resolveWorkspaceShareCategories,
70
70
  resolveWorkspaceIdentitySet,
71
71
  expandWorkspaceIdentitySetWithAliases,
72
72
  resolveStoredPathWorkspaceIdentity,
73
73
  sourceNameForMemory
74
- } from "./server-6tcrjfw8.js";
74
+ } from "./server-8pqnnmf9.js";
75
75
  import {
76
76
  isKnownAutocommit
77
77
  } from "./server-8ajstryz.js";
@@ -2435,7 +2435,7 @@ function searchMessagesInSnapshot(args, sessionFirst) {
2435
2435
  return renderRows(args.limit);
2436
2436
  }
2437
2437
  const sanitizedProbes = probes.map((probe) => ({ probe, query: sanitizeFtsQuery(probe) })).filter((entry) => entry.query.length > 0);
2438
- const corpusSize = getIndexedMessageCorpusSize(args.db, args.sessionId, cutoff);
2438
+ const corpusSize = getIndexedMessageCorpusSize2(args.db, args.sessionId, cutoff);
2439
2439
  const probeCounts = countSessionFtsMatchesBatch(args.db, args.sessionId, sanitizedProbes.map((entry) => entry.query), cutoff, args.dateRange, sessionFirst);
2440
2440
  const collectBaseDiagnostics = args.diagnostics !== undefined && cutoff !== null;
2441
2441
  const baseOutcome = collectBaseDiagnostics && baseQuery.length > 0 ? runMessageFtsQueryWithDiagnostics({
@@ -129,6 +129,7 @@ function isRecord(value) {
129
129
 
130
130
  // src/shared/tag-owner-id.ts
131
131
  var TEXT_TAG_IDENTITY_MARKER = ":mc-text-v1:";
132
+ var PI_FALLBACK_PREFIXES_JSON = JSON.stringify([..."pi-msg-"].reduce((prefixes, char) => prefixes.flatMap((prefix) => /[a-z]/.test(char) ? [prefix + char, prefix + char.toUpperCase()] : [prefix + char]), [""]));
132
133
  var POSITIONAL_CONTENT_SUFFIX = /:(?:p|file)\d+$/;
133
134
  var CONTENT_DERIVED_TEXT_SUFFIX = /:mc-text-v1:[0-9a-f]+:[0-9a-f]+:o\d+$/;
134
135
  function contentTagOwnerMessageId(contentId) {
@@ -2257,8 +2258,41 @@ function getPersistedToolTagAccounting2(db, sessionId, tagNumber) {
2257
2258
  }
2258
2259
  return row;
2259
2260
  }
2260
- function getAllStatusTagTokenTotalsFlat2(db, sessionId, floor = 0, calibration = { proseRatio: 1, toolsRatio: 1 }) {
2261
- const rows = floor > 0 ? db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
2261
+ function getAllStatusTagTokenTotalsFlat2(db, sessionId, floor = 0, calibration = { proseRatio: 1, toolsRatio: 1 }, ownerMessageIds) {
2262
+ const columns = "type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count";
2263
+ const owners = ownerMessageIds ? new Set(ownerMessageIds) : undefined;
2264
+ let scanOrder = "id";
2265
+ if (owners) {
2266
+ const planStatement = db.prepare(`EXPLAIN QUERY PLAN SELECT ${columns} FROM tags WHERE session_id = ?${floor > 0 ? " AND tag_number >= ?" : ""}`);
2267
+ let plan;
2268
+ try {
2269
+ plan = planStatement.all(sessionId, ...floor > 0 ? [floor] : []);
2270
+ } finally {
2271
+ planStatement.finalize?.();
2272
+ }
2273
+ const index = plan.map((row) => /USING (?:COVERING )?INDEX (\S+)/.exec(row.detail)?.[1]).find(Boolean);
2274
+ if (index) {
2275
+ const keyStatement = db.prepare("SELECT name, coll, desc FROM pragma_index_xinfo(?) WHERE key = 1 ORDER BY seqno");
2276
+ let keys;
2277
+ try {
2278
+ keys = keyStatement.all(index);
2279
+ } finally {
2280
+ keyStatement.finalize?.();
2281
+ }
2282
+ scanOrder = [
2283
+ ...keys.map((key) => `"${(key.name ?? "id").replaceAll('"', '""')}" COLLATE "${key.coll.replaceAll('"', '""')}"${key.desc ? " DESC" : ""}`),
2284
+ "id"
2285
+ ].join(", ");
2286
+ }
2287
+ }
2288
+ const rows = owners ? db.prepare(`SELECT ${columns} FROM tags WHERE id IN (
2289
+ SELECT t.id FROM json_each(?) o CROSS JOIN tags t INDEXED BY idx_tags_session_message_id
2290
+ ON t.session_id = ? AND t.message_id >= o.value AND t.message_id < o.value || ';'
2291
+ UNION
2292
+ SELECT t.id FROM json_each(?) o CROSS JOIN tags t INDEXED BY idx_tags_pi_fallback_tool_owner
2293
+ ON t.session_id = ? AND t.tool_owner_message_id = o.value
2294
+ WHERE t.type = 'tool'
2295
+ ) AND tag_number >= ? ORDER BY ${scanOrder}`).all(JSON.stringify([...owners]), sessionId, JSON.stringify([...owners]), sessionId, floor) : floor > 0 ? db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
2262
2296
  FROM tags
2263
2297
  WHERE session_id = ? AND tag_number >= ?`).all(sessionId, floor) : db.prepare(`SELECT type, message_id, tool_owner_message_id, token_count, input_token_count, reasoning_token_count
2264
2298
  FROM tags
@@ -2269,6 +2303,8 @@ function getAllStatusTagTokenTotalsFlat2(db, sessionId, floor = 0, calibration =
2269
2303
  if (row.type === "tool" && row.tool_owner_message_id === null)
2270
2304
  continue;
2271
2305
  const owner = ownerMessageIdForTagRow(row);
2306
+ if (owners && !owners.has(owner))
2307
+ continue;
2272
2308
  if (row.token_count === null) {
2273
2309
  nullMessageIds.add(owner);
2274
2310
  totals.delete(owner);
@@ -3032,11 +3068,13 @@ function findPiFallbackToolOwnerTags2(db, sessionId) {
3032
3068
  message_id AS callId,
3033
3069
  tool_owner_message_id AS toolOwnerMessageId,
3034
3070
  status
3035
- FROM tags
3071
+ FROM json_each(?) prefix CROSS JOIN tags
3036
3072
  WHERE session_id = ?
3037
- AND type = 'tool'
3038
- AND tool_owner_message_id LIKE 'pi-msg-%'
3039
- ORDER BY tag_number ASC`).all(sessionId).filter(isPiFallbackToolOwnerTag);
3073
+ AND tags.type = 'tool'
3074
+ AND tool_owner_message_id >= prefix.value
3075
+ AND tool_owner_message_id < substr(prefix.value, 1, 6) || '.'
3076
+ AND tool_owner_message_id LIKE 'pi-msg-%'
3077
+ ORDER BY tag_number ASC`).all(PI_FALLBACK_PREFIXES_JSON, sessionId).filter(isPiFallbackToolOwnerTag);
3040
3078
  }
3041
3079
 
3042
3080
  class PiTagIdentityConflictError extends Error {
@@ -198,10 +198,10 @@ function classifyGitError(error, rawDirectory) {
198
198
  }
199
199
  return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
200
200
  }
201
- function resolveProjectIdentityStrict(directory, allowHomeProject = homeProjectPermission) {
201
+ function resolveProjectIdentityStrict(directory, allowHomeProject = homeProjectPermission, options = {}) {
202
202
  assertProjectAllowed(directory, allowHomeProject);
203
203
  const canonical = path2.resolve(directory);
204
- const cached = identityCache.get(canonical);
204
+ const cached = options.cache === false ? undefined : identityCache.get(canonical);
205
205
  if (cached !== undefined) {
206
206
  return cached;
207
207
  }
@@ -232,6 +232,8 @@ function resolveProjectIdentityStrict(directory, allowHomeProject = homeProjectP
232
232
  throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
233
233
  }
234
234
  const identity = `git:${rootCommit}`;
235
+ if (options.cache === false)
236
+ return identity;
235
237
  identityCache.set(canonical, identity);
236
238
  lastKnownGitIdentityCache.set(canonical, identity);
237
239
  rememberGitIdentity(canonical, identity);
@@ -14,10 +14,10 @@ import {
14
14
  } from "./server-t4k4qexw.js";
15
15
  import {
16
16
  managedAuthorityNoteRow
17
- } from "./server-6tcrjfw8.js";
17
+ } from "./server-8pqnnmf9.js";
18
18
  import {
19
19
  embedTextForProject2
20
- } from "./server-1fszbabv.js";
20
+ } from "./server-cjz51jm2.js";
21
21
  import {
22
22
  saveEmbeddingIfHashMatches
23
23
  } from "./server-4dhv7mqz.js";
package/dist/v2/server.js CHANGED
@@ -7870,7 +7870,7 @@ var require_src2 = __commonJS(function(exports, module) {
7870
7870
  // package.json
7871
7871
  var package_default = {
7872
7872
  name: "@cortexkit/opencode-magic-context",
7873
- version: "0.49.1",
7873
+ version: "0.49.3",
7874
7874
  type: "module",
7875
7875
  description: "OpenCode plugin for Magic Context — cross-session memory and context management",
7876
7876
  main: "dist/index.js",
@@ -7907,8 +7907,8 @@ var package_default = {
7907
7907
  "README.md"
7908
7908
  ],
7909
7909
  scripts: {
7910
- build: "bun ../../scripts/check-bun.mjs && tsc -p ../retina-local-fs/tsconfig.build.json && bun run build:tui && bun ../../scripts/clean-dist-chunks.mjs dist index.js transformers-web.js transformers-node-wasm.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist && bun build src/index.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts --outdir dist --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode-ai/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite && bun run build:v2 && tsc --emitDeclarationOnly",
7911
- "build:v2": "bun ../../scripts/clean-dist-chunks.mjs dist/v2 server.js embedding-worker.js embedding-worker-process.js migration-worker.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/v2/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist/v2 && bun build src/v2/server.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts --outdir dist/v2 --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite",
7910
+ build: "bun ../../scripts/check-bun.mjs && tsc -p ../retina-local-fs/tsconfig.build.json && bun run build:tui && bun ../../scripts/clean-dist-chunks.mjs dist index.js transformers-web.js transformers-node-wasm.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist && bun build src/index.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts src/features/magic-context/compaction-marker-candidates-worker.ts --outdir dist --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode-ai/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite && bun run build:v2 && tsc --emitDeclarationOnly",
7911
+ "build:v2": "bun ../../scripts/clean-dist-chunks.mjs dist/v2 server.js embedding-worker.js embedding-worker-process.js migration-worker.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/v2/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist/v2 && bun build src/v2/server.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts src/features/magic-context/compaction-marker-candidates-worker.ts --outdir dist/v2 --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite",
7912
7912
  "build:tui": "bun scripts/build-tui.ts",
7913
7913
  "check:tui-compiled": 'git diff --exit-code -- src/tui-compiled && test -z "$(git status --porcelain -- src/tui-compiled)"',
7914
7914
  "check:transformers-node-wasm": "bun scripts/verify-transformers-node-wasm.ts",
@@ -8186,7 +8186,7 @@ async function setup(context) {
8186
8186
  log2("v2 setup() called on a host without session.hook; v1 server lane owns this host, v2 lane inert");
8187
8187
  return async () => {};
8188
8188
  }
8189
- const { registerContext } = await import("./context-gsdtha5v.js");
8189
+ const { registerContext } = await import("./context-p1xf3w5q.js");
8190
8190
  setHarness2("opencode2");
8191
8191
  bindStaleBuildNotice({}, import.meta.url, "opencode2");
8192
8192
  const duties = await registerContext(context);
@@ -60,11 +60,11 @@ import {
60
60
  adoptNullOwnerToolTag2,
61
61
  getCandidateToolOwners2,
62
62
  pickNearestPriorOwner2
63
- } from "./server-4c7jt8gq.js";
63
+ } from "./server-pcygfdbs.js";
64
64
  import"./server-1zx87sdm.js";
65
65
  import"./server-6byqfbqf.js";
66
- import"./server-kzns2hbz.js";
67
66
  import"./server-kb9nn0a0.js";
67
+ import"./server-kzns2hbz.js";
68
68
  import"./server-t4k4qexw.js";
69
69
  import"./server-pavzsdc6.js";
70
70
  export {
@@ -19,13 +19,13 @@ import {
19
19
  registerActiveCompartmentRun,
20
20
  setRecompStarting2,
21
21
  setRecompTerminal2
22
- } from "./server-7spkq1y6.js";
22
+ } from "./server-cxxjb33r.js";
23
23
  import"./server-kzns2hbz.js";
24
24
  import"./server-hb6xhh9q.js";
25
25
  import"./server-kb9nn0a0.js";
26
26
  import"./server-2htmbqbv.js";
27
- import"./server-s2st631p.js";
28
- import"./server-bgzy0phk.js";
27
+ import"./server-8wcf79fe.js";
28
+ import"./server-3bc6k4k4.js";
29
29
  import {
30
30
  getWrapupInProgressState,
31
31
  acquireWrapupInProgress,
@@ -34,29 +34,31 @@ import {
34
34
  getHistorianFailureState,
35
35
  clearEmergencyRecovery,
36
36
  prepareRawSessionOrdinals2
37
- } from "./server-c7n2ftng.js";
37
+ } from "./server-4n2tknpc.js";
38
38
  import"./server-6byqfbqf.js";
39
- import"./server-7vkj6zhb.js";
39
+ import"./server-9a6v0wth.js";
40
40
  import"./server-1zx87sdm.js";
41
- import"./server-4c7jt8gq.js";
41
+ import"./server-pcygfdbs.js";
42
42
  import"./server-cw6521wn.js";
43
- import"./server-7h861yms.js";
43
+ import"./server-51779zs2.js";
44
+ import"./server-3dqjhchy.js";
44
45
  import"./server-2ra6pwf3.js";
45
46
  import"./server-czb2rkpk.js";
46
47
  import"./server-7ekna9zs.js";
47
48
  import"./server-fr0wbhdn.js";
48
- import"./server-6tcrjfw8.js";
49
- import"./server-8xmq9h3f.js";
50
- import"./server-1fszbabv.js";
49
+ import"./server-8pqnnmf9.js";
50
+ import"./server-q6j5dasr.js";
51
+ import"./server-cjz51jm2.js";
51
52
  import"./server-fb3zxn06.js";
52
- import"./server-zbxjj2mz.js";
53
+ import"./server-rygvffsf.js";
53
54
  import {
54
55
  pushNotification2
55
56
  } from "./server-x8a7jmkh.js";
57
+ import"./server-9290a1mb.js";
56
58
  import"./server-qt1qqasc.js";
57
59
  import"./server-4dhv7mqz.js";
58
60
  import"./server-ras2sxnc.js";
59
- import"./server-kh958yj8.js";
61
+ import"./server-5zrpr764.js";
60
62
  import"./server-7jzrrxap.js";
61
63
  import"./server-d6g4whc2.js";
62
64
  import"./server-vb7pbd9p.js";
@@ -1,42 +1,44 @@
1
1
  import {
2
2
  runManagedWrapup2
3
- } from "./index-6tfxmc4n.js";
4
- import"./index-eypn4ba6.js";
5
- import"./index-zfzdpeq2.js";
6
- import"./index-rafsaxac.js";
7
- import"./index-sfhndpcb.js";
8
- import"./index-r2zcs71c.js";
3
+ } from "./index-je2na55n.js";
4
+ import"./index-w9pe0nx1.js";
5
+ import"./index-s4ztdn8v.js";
6
+ import"./index-yxawqmdd.js";
7
+ import"./index-sd3egckz.js";
8
+ import"./index-fjtgekd4.js";
9
+ import"./index-3wckxwfp.js";
10
+ import"./index-7ndnvw7a.js";
9
11
  import"./index-7jzrrxap.js";
10
- import"./index-fg00p1cg.js";
11
- import"./index-p1hnwfd8.js";
12
+ import"./index-c0b01zrc.js";
13
+ import"./index-gcdb3rqw.js";
12
14
  import"./index-4dhv7mqz.js";
13
15
  import"./index-w23dh319.js";
14
- import"./index-sd3egckz.js";
15
- import"./index-xgrwmj82.js";
16
+ import"./index-bhk710rw.js";
16
17
  import"./index-qt1qqasc.js";
17
18
  import"./index-x8a7jmkh.js";
18
19
  import"./index-tqf7yg27.js";
19
20
  import"./index-gbmqa9x9.js";
20
21
  import"./index-vkbqdnkj.js";
21
- import"./index-zg8yq4ya.js";
22
+ import"./index-znzgze2v.js";
22
23
  import"./index-fb3zxn06.js";
23
- import"./index-att4bfan.js";
24
+ import"./index-xfbfrngb.js";
24
25
  import"./index-6m7rdv7y.js";
25
26
  import"./index-fr0wbhdn.js";
26
- import"./index-gzky5rva.js";
27
+ import"./index-41gqa557.js";
27
28
  import"./index-7w18gg78.js";
28
- import"./index-sv0bb7vg.js";
29
- import"./index-nydv6g8r.js";
29
+ import"./index-7zg9dvx0.js";
30
+ import"./index-yqps2zsv.js";
30
31
  import"./index-1zx87sdm.js";
31
- import"./index-0pjvpfkf.js";
32
- import"./index-h5zfkjyk.js";
32
+ import"./index-qvr78ej6.js";
33
+ import"./index-jpm52v9x.js";
34
+ import"./index-v0p37hm2.js";
33
35
  import"./index-k6xvbj2e.js";
34
36
  import"./index-h2wc01qk.js";
35
37
  import"./index-1bt88epp.js";
36
38
  import"./index-58e3y90n.js";
37
- import"./index-qx53vezw.js";
38
39
  import"./index-4mjhqadz.js";
39
40
  import"./index-kb9nn0a0.js";
41
+ import"./index-qx53vezw.js";
40
42
  import"./index-nsjyysj0.js";
41
43
  import"./index-0sahzkwr.js";
42
44
  import"./index-pavzsdc6.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.49.1",
3
+ "version": "0.49.3",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -37,8 +37,8 @@
37
37
  "README.md"
38
38
  ],
39
39
  "scripts": {
40
- "build": "bun ../../scripts/check-bun.mjs && tsc -p ../retina-local-fs/tsconfig.build.json && bun run build:tui && bun ../../scripts/clean-dist-chunks.mjs dist index.js transformers-web.js transformers-node-wasm.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist && bun build src/index.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts --outdir dist --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode-ai/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite && bun run build:v2 && tsc --emitDeclarationOnly",
41
- "build:v2": "bun ../../scripts/clean-dist-chunks.mjs dist/v2 server.js embedding-worker.js embedding-worker-process.js migration-worker.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/v2/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist/v2 && bun build src/v2/server.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts --outdir dist/v2 --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite",
40
+ "build": "bun ../../scripts/check-bun.mjs && tsc -p ../retina-local-fs/tsconfig.build.json && bun run build:tui && bun ../../scripts/clean-dist-chunks.mjs dist index.js transformers-web.js transformers-node-wasm.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist && bun build src/index.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts src/features/magic-context/compaction-marker-candidates-worker.ts --outdir dist --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode-ai/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite && bun run build:v2 && tsc --emitDeclarationOnly",
41
+ "build:v2": "bun ../../scripts/clean-dist-chunks.mjs dist/v2 server.js embedding-worker.js embedding-worker-process.js migration-worker.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/v2/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist/v2 && bun build src/v2/server.ts src/features/magic-context/memory/embedding-worker.ts src/features/magic-context/memory/embedding-worker-process.ts src/features/magic-context/migration-worker.ts src/hooks/magic-context/auto-search-worker.ts src/hooks/magic-context/raw-ordinal-warmup-worker.ts src/features/magic-context/compaction-marker-candidates-worker.ts --outdir dist/v2 --entry-naming '[name].[ext]' --target node --format esm --splitting --external @opencode/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:ffi --external bun:sqlite --external node:sqlite",
42
42
  "build:tui": "bun scripts/build-tui.ts",
43
43
  "check:tui-compiled": "git diff --exit-code -- src/tui-compiled && test -z \"$(git status --porcelain -- src/tui-compiled)\"",
44
44
  "check:transformers-node-wasm": "bun scripts/verify-transformers-node-wasm.ts",
@@ -19,6 +19,10 @@ type KnownSlot = {
19
19
  * a negative cache cannot quietly become permanent.
20
20
  */
21
21
  const KNOWN_SLOTS: Record<string, KnownSlot> = {
22
+ "packages/plugin/src/features/magic-context/compaction-marker-candidates.ts:exitRegistered": {
23
+ classification: "PUBLICATION",
24
+ reason: "Records that the process-exit handler stopping marker candidate workers is installed; it stores no candidate, absence or failure verdict.",
25
+ },
22
26
  "packages/pi-plugin/src/index.ts:databaseExitHookRegistered": {
23
27
  classification: "PUBLICATION",
24
28
  reason: "Correct: records that the one process-exit handler closing the shared database is installed, so /reload doesn't register a second one; it holds no failure or absence verdict.",
@@ -195,7 +199,8 @@ function sourceFiles(directory: string): string[] {
195
199
  for (const entry of readdirSync(directory, { withFileTypes: true })) {
196
200
  const path = join(directory, entry.name);
197
201
  if (entry.isDirectory()) {
198
- files.push(...sourceFiles(path));
202
+ // Package fixture harnesses are excluded from production compilation.
203
+ if (entry.name !== "__tests__") files.push(...sourceFiles(path));
199
204
  } else if (entry.isFile() && path.endsWith(".ts") && !path.endsWith(".test.ts")) {
200
205
  files.push(path);
201
206
  }
@@ -7,11 +7,13 @@ import { createTestTempDirFromPath } from "./test-temp-dir";
7
7
  // The scenario runs in a child process with NODE_ENV=production because the
8
8
  // shared logger is silent under test. It imports storage-db for its side effect
9
9
  // of registering the real log sink, then drives three privileged writes and one
10
- // background writer through two connections on a throwaway database:
11
- // wait - another connection holds the lock for 300 ms, the hold is short
12
- // hold - the lock comes at once, the operation keeps it for 300 ms
13
- // fast - neither crosses the threshold, so nothing is logged
14
- // async - beginSqliteWriterAsync acquires at once and holds for 300 ms
10
+ // background writer through two connections on a throwaway database.
11
+ // Acquisition measures waiting for BEGIN to succeed; hold measures time from
12
+ // successful BEGIN through COMMIT. The injected clock separates those costs:
13
+ // wait - 300 ms before BEGIN and 0 ms held: log a foreground acquisition
14
+ // hold - 0 ms before BEGIN and 300 ms held: log a foreground hold
15
+ // fast - 0 ms for both: do not log anything
16
+ // async - 0 ms before BEGIN and 300 ms held: log the background writer's own site
15
17
  const scenario = `
16
18
  await import(${JSON.stringify(new URL("../features/magic-context/storage-db.ts", import.meta.url).href)});
17
19
  const sqlite = await import(${JSON.stringify(new URL("./sqlite.ts", import.meta.url).href)});
@@ -20,18 +22,21 @@ const scenario = `
20
22
  const holder = new sqlite.Database(dbPath);
21
23
  holder.exec("PRAGMA journal_mode=WAL; CREATE TABLE context_privilege_state(id INTEGER PRIMARY KEY, enabled INTEGER)");
22
24
  const writer = new sqlite.Database(dbPath);
23
- const spin = (ms) => { const end = performance.now() + ms; while (performance.now() < end) {} };
25
+ let now = 0;
26
+ performance.now = () => now;
27
+ const hold = () => { now += 300; };
24
28
 
25
29
  holder.exec("BEGIN IMMEDIATE");
26
- setTimeout(() => holder.exec("COMMIT"), 300);
27
- await sqlite.withAsyncPrivilegedWriter(writer, () => undefined);
30
+ await sqlite.withAsyncPrivilegedWriter(writer, () => undefined, {
31
+ beforeRetry: () => { now += 300; holder.exec("COMMIT"); },
32
+ });
28
33
 
29
- await sqlite.withAsyncPrivilegedWriter(writer, () => spin(300));
34
+ await sqlite.withAsyncPrivilegedWriter(writer, hold);
30
35
 
31
36
  await sqlite.withAsyncPrivilegedWriter(writer, () => undefined);
32
37
 
33
38
  await sqlite.beginSqliteWriterAsync(writer, "historian-publish");
34
- spin(300);
39
+ hold();
35
40
  writer.exec("COMMIT");
36
41
 
37
42
  logger.flushLogger();
@@ -109,6 +114,8 @@ describe("sqlite writer diagnostics", () => {
109
114
  expect(waited).toContain("outcome=committed");
110
115
  expect(field(waited, "acquire_ms")).toBeGreaterThanOrEqual(250);
111
116
  expect(field(waited, "hold_ms")).toBeLessThan(250);
117
+ expect(field(waited, "acquire_ms")).toBe(300);
118
+ expect(field(waited, "hold_ms")).toBe(0);
112
119
  });
113
120
 
114
121
  test("a writer that held the lock reports the hold, not its quick acquisition", () => {
@@ -118,6 +125,8 @@ describe("sqlite writer diagnostics", () => {
118
125
  expect(held).toHaveLength(1);
119
126
  expect(field(held[0], "acquire_ms")).toBeLessThan(250);
120
127
  expect(field(held[0], "hold_ms")).toBeGreaterThanOrEqual(300);
128
+ expect(field(held[0], "acquire_ms")).toBe(0);
129
+ expect(field(held[0], "hold_ms")).toBe(300);
121
130
  });
122
131
 
123
132
  test("an async background writer reports its hold under its own site", () => {
@@ -126,6 +135,8 @@ describe("sqlite writer diagnostics", () => {
126
135
  expect(held[0]).toContain("lane=background");
127
136
  expect(field(held[0], "acquire_ms")).toBeLessThan(250);
128
137
  expect(field(held[0], "hold_ms")).toBeGreaterThanOrEqual(300);
138
+ expect(field(held[0], "acquire_ms")).toBe(0);
139
+ expect(field(held[0], "hold_ms")).toBe(300);
129
140
  });
130
141
 
131
142
  test("a fast writer stays out of the log", () => {
@@ -13,6 +13,19 @@
13
13
 
14
14
  export const TEXT_TAG_IDENTITY_MARKER = ":mc-text-v1:";
15
15
 
16
+ // SQLite LIKE folds ASCII case, but its binary ownership indexes cannot seek
17
+ // that prefix. Enumerate the 32 spellings so range seeks retain LIKE's matches
18
+ // without building a second index over every persisted tag.
19
+ export const PI_FALLBACK_PREFIXES_JSON = JSON.stringify(
20
+ [..."pi-msg-"].reduce<string[]>(
21
+ (prefixes, char) =>
22
+ prefixes.flatMap((prefix) =>
23
+ /[a-z]/.test(char) ? [prefix + char, prefix + char.toUpperCase()] : [prefix + char],
24
+ ),
25
+ [""],
26
+ ),
27
+ );
28
+
16
29
  const POSITIONAL_CONTENT_SUFFIX = /:(?:p|file)\d+$/;
17
30
  const CONTENT_DERIVED_TEXT_SUFFIX = /:mc-text-v1:[0-9a-f]+:[0-9a-f]+:o\d+$/;
18
31
 
@@ -219,9 +219,13 @@ describe("tagTranscript tool aggregation", () => {
219
219
  it("truncates assistant tool_use parts via text fallback when setToolOutput asserts", () => {
220
220
  const tagger = new FakeTagger();
221
221
  const toolUse = new ThrowingToolOutputPart("tool_use", "read:99", '{"file":"long-a"}');
222
+ const toolResult = new TestPart("tool_result", "read:99", "result");
222
223
  const transcript: Transcript = {
223
224
  harness: "pi",
224
- messages: [{ info: { id: "assistant-1", role: "assistant" }, parts: [toolUse] }],
225
+ messages: [
226
+ { info: { id: "assistant-1", role: "assistant" }, parts: [toolUse] },
227
+ { info: { id: "user-1", role: "user" }, parts: [toolResult] },
228
+ ],
225
229
  commit() {},
226
230
  };
227
231
 
@@ -241,6 +245,7 @@ describe("tagTranscript tool aggregation", () => {
241
245
  // Skeleton-drop now renders the one canonical placeholder, not a
242
246
  // separate "[truncated]" vocabulary.
243
247
  expect(toolUse.getText()).toBe(`[dropped \u00a7${tag}\u00a7]`);
248
+ expect(toolResult.getText()).toBe(`[dropped \u00a7${tag}\u00a7]`);
244
249
  });
245
250
 
246
251
  it("drops every contiguous folded tool_result block for the paired callId", () => {
@@ -1106,7 +1106,8 @@ function buildAggregateTarget(
1106
1106
  const role = occurrences[0]?.message.info.role ?? "user";
1107
1107
  const messageId = occurrences[0]?.message.info.id;
1108
1108
 
1109
- const truncate = (): "truncated" | "absent" => {
1109
+ const truncate = (): "truncated" | "absent" | "incomplete" => {
1110
+ if (!complete()) return "incomplete";
1110
1111
  // Keep the tool call and its result, but replace the call arguments with
1111
1112
  // a non-executable marker and the result content with the dropped marker.
1112
1113
  // Legacy render, replayed only for `drop_mode = 'truncated'` tags until a
@@ -4,6 +4,13 @@ import { join, relative, resolve } from "node:path";
4
4
  import ts from "typescript";
5
5
 
6
6
  const root = resolve(import.meta.dir, "../../../..");
7
+ const visibleGuiInstallers = [
8
+ {
9
+ path: "packages/cli/src/lib/dashboard-install.ts",
10
+ callee: "installerPath",
11
+ reason: "GUI installer in passive mode: its progress window must stay visible.",
12
+ },
13
+ ] as const;
7
14
  const subprocessNames = new Set([
8
15
  "exec",
9
16
  "execSync",
@@ -20,8 +27,7 @@ function sourceFiles(dir: string): string[] {
20
27
  });
21
28
  }
22
29
 
23
- function missingHiddenOptions(path: string): string[] {
24
- const text = readFileSync(path, "utf8");
30
+ function missingHiddenOptions(path: string, text = readFileSync(path, "utf8")): string[] {
25
31
  if (!/node:child_process|Bun\.spawn/.test(text)) return [];
26
32
  const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true);
27
33
  const imports = new Set<string>();
@@ -67,6 +73,18 @@ function missingHiddenOptions(path: string): string[] {
67
73
  }
68
74
  return false;
69
75
  }
76
+ function explicitlyVisible(node: ts.Expression | undefined): boolean {
77
+ if (!node || !ts.isObjectLiteralExpression(node)) return false;
78
+ for (const property of [...node.properties].reverse()) {
79
+ if (ts.isSpreadAssignment(property)) return false;
80
+ if (
81
+ ts.isPropertyAssignment(property) &&
82
+ property.name.getText(source) === "windowsHide"
83
+ )
84
+ return property.initializer.kind === ts.SyntaxKind.FalseKeyword;
85
+ }
86
+ return false;
87
+ }
70
88
  const failures: string[] = [];
71
89
  function visit(node: ts.Node): void {
72
90
  if (ts.isCallExpression(node)) {
@@ -109,7 +127,15 @@ function missingHiddenOptions(path: string): string[] {
109
127
  : 2;
110
128
  // A single-argument Bun.spawnSync({cmd, ...}) passes options as its first argument.
111
129
  const option = node.arguments[index];
112
- if (!hidden(option)) {
130
+ const visibleGuiInstaller =
131
+ name === "execFileAsync" &&
132
+ visibleGuiInstallers.some(
133
+ (entry) =>
134
+ relative(root, path).replaceAll("\\", "/") === entry.path &&
135
+ node.arguments[0]?.getText(source) === entry.callee,
136
+ ) &&
137
+ explicitlyVisible(option);
138
+ if (!hidden(option) && !visibleGuiInstaller) {
113
139
  const line =
114
140
  source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
115
141
  failures.push(`${relative(root, path)}:${line} ${name}`);
@@ -125,8 +151,38 @@ function missingHiddenOptions(path: string): string[] {
125
151
  describe("Windows child processes", () => {
126
152
  test("every plugin, Pi and CLI subprocess hides its Windows console", () => {
127
153
  const failures = ["plugin", "pi-plugin", "cli"].flatMap((pkg) =>
128
- sourceFiles(join(root, "packages", pkg, "src")).flatMap(missingHiddenOptions),
154
+ sourceFiles(join(root, "packages", pkg, "src")).flatMap((path) =>
155
+ missingHiddenOptions(path),
156
+ ),
129
157
  );
130
158
  expect(failures).toEqual([]);
131
159
  });
160
+
161
+ test("another dashboard subprocess still requires windowsHide true", () => {
162
+ const path = join(root, "packages/cli/src/lib/dashboard-install.ts");
163
+ const text = `import { execFile } from "node:child_process";
164
+ execFileAsync(installerPath, ["/P"], { windowsHide: false });
165
+ execFileAsync("tar", [], { windowsHide: false });`;
166
+ expect(missingHiddenOptions(path, text)).toEqual([
167
+ "packages/cli/src/lib/dashboard-install.ts:3 execFileAsync",
168
+ ]);
169
+ });
170
+
171
+ test("the GUI installer exemption requires explicit windowsHide false", () => {
172
+ const path = join(root, "packages/cli/src/lib/dashboard-install.ts");
173
+ const text = `import { execFile } from "node:child_process";
174
+ execFileAsync(installerPath, ["/P"]);`;
175
+ expect(missingHiddenOptions(path, text)).toEqual([
176
+ "packages/cli/src/lib/dashboard-install.ts:2 execFileAsync",
177
+ ]);
178
+ });
179
+
180
+ test("the GUI installer callee is not exempt in a different file", () => {
181
+ const path = join(root, "packages/cli/src/lib/other-installer.ts");
182
+ const text = `import { execFile } from "node:child_process";
183
+ execFileAsync(installerPath, ["/P"], { windowsHide: false });`;
184
+ expect(missingHiddenOptions(path, text)).toEqual([
185
+ "packages/cli/src/lib/other-installer.ts:2 execFileAsync",
186
+ ]);
187
+ });
132
188
  });
File without changes
File without changes