@neuralnomads/codenomad-dev 0.20.0-dev-20260909-855d2ee7 → 0.20.0-dev-20260911-ea37f97b

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 (177) hide show
  1. package/dist/api-types.js.map +1 -1
  2. package/dist/index.js +42 -31
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.test.js +9 -1
  5. package/dist/index.test.js.map +1 -1
  6. package/dist/opencode/pruning-installation.js +65 -0
  7. package/dist/opencode/pruning-installation.js.map +1 -0
  8. package/dist/opencode/pruning-installation.test.js +101 -0
  9. package/dist/opencode/pruning-installation.test.js.map +1 -0
  10. package/dist/opencode/pruning-lifecycle.js +66 -0
  11. package/dist/opencode/pruning-lifecycle.js.map +1 -0
  12. package/dist/opencode/session-pruning/claim-fence.js +33 -0
  13. package/dist/opencode/session-pruning/claim-fence.js.map +1 -0
  14. package/dist/opencode/session-pruning/claim-fence.test.js +104 -0
  15. package/dist/opencode/session-pruning/claim-fence.test.js.map +1 -0
  16. package/dist/opencode/session-pruning/contract.js +40 -0
  17. package/dist/opencode/session-pruning/contract.js.map +1 -0
  18. package/dist/opencode/session-pruning/database-path.js +17 -0
  19. package/dist/opencode/session-pruning/database-path.js.map +1 -0
  20. package/dist/opencode/session-pruning/database-path.test.js +22 -0
  21. package/dist/opencode/session-pruning/database-path.test.js.map +1 -0
  22. package/dist/opencode/session-pruning/desktop-plugin.js +14 -0
  23. package/dist/opencode/session-pruning/desktop-plugin.js.map +1 -0
  24. package/dist/opencode/session-pruning/index.js +2 -0
  25. package/dist/opencode/session-pruning/index.js.map +1 -0
  26. package/dist/opencode/session-pruning/isolated-store.js +18 -0
  27. package/dist/opencode/session-pruning/isolated-store.js.map +1 -0
  28. package/dist/opencode/session-pruning/planner.js +36 -0
  29. package/dist/opencode/session-pruning/planner.js.map +1 -0
  30. package/dist/opencode/session-pruning/plugin.js +45 -0
  31. package/dist/opencode/session-pruning/plugin.js.map +1 -0
  32. package/dist/opencode/session-pruning/presence.js +42 -0
  33. package/dist/opencode/session-pruning/presence.js.map +1 -0
  34. package/dist/opencode/session-pruning/preview-store.js +21 -0
  35. package/dist/opencode/session-pruning/preview-store.js.map +1 -0
  36. package/dist/opencode/session-pruning/pruning.test.js +172 -0
  37. package/dist/opencode/session-pruning/pruning.test.js.map +1 -0
  38. package/dist/opencode/session-pruning/revision.js +18 -0
  39. package/dist/opencode/session-pruning/revision.js.map +1 -0
  40. package/dist/opencode/session-pruning/service.js +56 -0
  41. package/dist/opencode/session-pruning/service.js.map +1 -0
  42. package/dist/opencode/session-pruning/service.test.js +74 -0
  43. package/dist/opencode/session-pruning/service.test.js.map +1 -0
  44. package/dist/opencode/session-pruning/storage-path.js +6 -0
  45. package/dist/opencode/session-pruning/storage-path.js.map +1 -0
  46. package/dist/opencode/session-pruning/transaction.js +53 -0
  47. package/dist/opencode/session-pruning/transaction.js.map +1 -0
  48. package/dist/opencode/session-pruning/tui-reload.js +84 -0
  49. package/dist/opencode/session-pruning/tui-reload.js.map +1 -0
  50. package/dist/opencode/session-pruning/tui-reload.native.js +269 -0
  51. package/dist/opencode/session-pruning/tui-reload.native.js.map +1 -0
  52. package/dist/opencode/session-pruning/tui-reload.test.js +71 -0
  53. package/dist/opencode/session-pruning/tui-reload.test.js.map +1 -0
  54. package/dist/opencode/session-pruning/tui.js +12 -0
  55. package/dist/opencode/session-pruning/tui.js.map +1 -0
  56. package/dist/plugins/session-pruning/plugin.mjs +26559 -0
  57. package/dist/server/__tests__/instance-proxy.test.js +10 -5
  58. package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
  59. package/dist/server/__tests__/listener-base-url.test.js +26 -1
  60. package/dist/server/__tests__/listener-base-url.test.js.map +1 -1
  61. package/dist/server/__tests__/network-addresses.test.js +40 -0
  62. package/dist/server/__tests__/network-addresses.test.js.map +1 -1
  63. package/dist/server/__tests__/network-host.test.js +34 -0
  64. package/dist/server/__tests__/network-host.test.js.map +1 -0
  65. package/dist/server/__tests__/tls-ipv6.test.js +54 -0
  66. package/dist/server/__tests__/tls-ipv6.test.js.map +1 -0
  67. package/dist/server/http-server.js +8 -6
  68. package/dist/server/http-server.js.map +1 -1
  69. package/dist/server/listener-base-url.js +12 -3
  70. package/dist/server/listener-base-url.js.map +1 -1
  71. package/dist/server/network-addresses.js +20 -42
  72. package/dist/server/network-addresses.js.map +1 -1
  73. package/dist/server/network-host.js +62 -0
  74. package/dist/server/network-host.js.map +1 -0
  75. package/dist/server/routes/meta.js +2 -4
  76. package/dist/server/routes/meta.js.map +1 -1
  77. package/dist/server/routes/session-pruning.js +45 -0
  78. package/dist/server/routes/session-pruning.js.map +1 -0
  79. package/dist/server/routes/session-pruning.test.js +98 -0
  80. package/dist/server/routes/session-pruning.test.js.map +1 -0
  81. package/dist/server/tls.js +52 -32
  82. package/dist/server/tls.js.map +1 -1
  83. package/dist/workspaces/manager.js +1 -0
  84. package/dist/workspaces/manager.js.map +1 -1
  85. package/package.json +6 -3
  86. package/public/assets/App-0-FcHzPS.js +44 -0
  87. package/public/assets/App-0-FcHzPS.js.map +1 -0
  88. package/public/assets/FilesTab-CRpGDs1B.js +3 -0
  89. package/public/assets/{FilesTab-z7UrjcAQ.js.map → FilesTab-CRpGDs1B.js.map} +1 -1
  90. package/public/assets/{GitChangesTab-C04bYKhs.js → GitChangesTab-DxFebTiq.js} +3 -3
  91. package/public/assets/{GitChangesTab-C04bYKhs.js.map → GitChangesTab-DxFebTiq.js.map} +1 -1
  92. package/public/assets/{SplitFilePanel-B29MqCuU.js → SplitFilePanel-DgTMrrO-.js} +2 -2
  93. package/public/assets/{SplitFilePanel-B29MqCuU.js.map → SplitFilePanel-DgTMrrO-.js.map} +1 -1
  94. package/public/assets/{StatusTab-wUKfwnFf.js → StatusTab-LKUA9cbH.js} +2 -2
  95. package/public/assets/{StatusTab-wUKfwnFf.js.map → StatusTab-LKUA9cbH.js.map} +1 -1
  96. package/public/assets/{align-justify-glU678D9.js → align-justify-v_yPXSys.js} +2 -2
  97. package/public/assets/{align-justify-glU678D9.js.map → align-justify-v_yPXSys.js.map} +1 -1
  98. package/public/assets/{bundle-full-BrsCElYv.js → bundle-full-HzL_Jtqk.js} +2 -2
  99. package/public/assets/{bundle-full-BrsCElYv.js.map → bundle-full-HzL_Jtqk.js.map} +1 -1
  100. package/public/assets/{client-state-j9Iu7F-E.js → client-state-CF-PJ0GG.js} +2 -2
  101. package/public/assets/{client-state-j9Iu7F-E.js.map → client-state-CF-PJ0GG.js.map} +1 -1
  102. package/public/assets/{diff-viewer-B0VS1voD.js → diff-viewer-BFDQBDVy.js} +2 -2
  103. package/public/assets/{diff-viewer-B0VS1voD.js.map → diff-viewer-BFDQBDVy.js.map} +1 -1
  104. package/public/assets/index-8iPlm2r0.js +2 -0
  105. package/public/assets/index-8iPlm2r0.js.map +1 -0
  106. package/public/assets/index-B185mC7m.js +2 -0
  107. package/public/assets/index-B185mC7m.js.map +1 -0
  108. package/public/assets/index-Bbj1xF8i.js +2 -0
  109. package/public/assets/index-Bbj1xF8i.js.map +1 -0
  110. package/public/assets/index-BsmFN3k1.js +2 -0
  111. package/public/assets/index-BsmFN3k1.js.map +1 -0
  112. package/public/assets/index-Bz_OQJYA.css +1 -0
  113. package/public/assets/index-C52CfqK3.js +2 -0
  114. package/public/assets/index-C52CfqK3.js.map +1 -0
  115. package/public/assets/index-CHYmKl10.js +2 -0
  116. package/public/assets/index-CHYmKl10.js.map +1 -0
  117. package/public/assets/index-CTKYD_VJ.js +2 -0
  118. package/public/assets/index-CTKYD_VJ.js.map +1 -0
  119. package/public/assets/index-D2UHSOG5.js +2 -0
  120. package/public/assets/{index-ClaHfu-1.js.map → index-D2UHSOG5.js.map} +1 -1
  121. package/public/assets/index-DDoB_Z36.js +2 -0
  122. package/public/assets/index-DDoB_Z36.js.map +1 -0
  123. package/public/assets/{index-BzAEHHap.js → index-DcwAtZw-.js} +3 -3
  124. package/public/assets/index-DcwAtZw-.js.map +1 -0
  125. package/public/assets/index-DxUD3RYr.js +2 -0
  126. package/public/assets/index-DxUD3RYr.js.map +1 -0
  127. package/public/assets/{index-B6bX3AMT.js → index-RK9gf0uk.js} +2 -2
  128. package/public/assets/{index-B6bX3AMT.js.map → index-RK9gf0uk.js.map} +1 -1
  129. package/public/assets/{loading-CYiiwb08.js → loading-BSB82snY.js} +2 -2
  130. package/public/assets/{loading-CYiiwb08.js.map → loading-BSB82snY.js.map} +1 -1
  131. package/public/assets/main-CZztwrfv.js +48 -0
  132. package/public/assets/main-CZztwrfv.js.map +1 -0
  133. package/public/assets/{markdown-DWvSdCZz.js → markdown-DtidoL2Z.js} +4 -4
  134. package/public/assets/{markdown-DWvSdCZz.js.map → markdown-DtidoL2Z.js.map} +1 -1
  135. package/public/assets/{monaco-viewer-Cc9bWg7I.js → monaco-viewer-CpxDfB-6.js} +2 -2
  136. package/public/assets/monaco-viewer-CpxDfB-6.js.map +1 -0
  137. package/public/assets/{startup-state-settings-card-2KHpBL8U.js → startup-state-settings-card-BY741Rt0.js} +2 -2
  138. package/public/assets/{startup-state-settings-card-2KHpBL8U.js.map → startup-state-settings-card-BY741Rt0.js.map} +1 -1
  139. package/public/assets/{terminal-square-lzHuj_of.js → terminal-square-BdC_jwm3.js} +2 -2
  140. package/public/assets/{terminal-square-lzHuj_of.js.map → terminal-square-BdC_jwm3.js.map} +1 -1
  141. package/public/assets/{tool-call-DjkszeT4.js → tool-call-DbtCsJ4x.js} +10 -10
  142. package/public/assets/{tool-call-DjkszeT4.js.map → tool-call-DbtCsJ4x.js.map} +1 -1
  143. package/public/assets/{unified-picker-5JmIM6a0.js → unified-picker-C0eo1chJ.js} +2 -2
  144. package/public/assets/{unified-picker-5JmIM6a0.js.map → unified-picker-C0eo1chJ.js.map} +1 -1
  145. package/public/assets/{wrap-text-N0Cav2Vp.js → wrap-text-BuMtdvMx.js} +2 -2
  146. package/public/assets/{wrap-text-N0Cav2Vp.js.map → wrap-text-BuMtdvMx.js.map} +1 -1
  147. package/public/index.html +4 -4
  148. package/public/loading.html +4 -4
  149. package/public/sw.js +1 -1
  150. package/public/sw.js.map +1 -1
  151. package/public/assets/App-Nlt9zV-v.js +0 -43
  152. package/public/assets/App-Nlt9zV-v.js.map +0 -1
  153. package/public/assets/FilesTab-z7UrjcAQ.js +0 -3
  154. package/public/assets/index-BIRLnDWY.js +0 -2
  155. package/public/assets/index-BIRLnDWY.js.map +0 -1
  156. package/public/assets/index-BuScpzpP.js +0 -2
  157. package/public/assets/index-BuScpzpP.js.map +0 -1
  158. package/public/assets/index-BzAEHHap.js.map +0 -1
  159. package/public/assets/index-C-DZt-oz.css +0 -1
  160. package/public/assets/index-C9RXVQl9.js +0 -2
  161. package/public/assets/index-C9RXVQl9.js.map +0 -1
  162. package/public/assets/index-ClaHfu-1.js +0 -2
  163. package/public/assets/index-DBx5htn3.js +0 -2
  164. package/public/assets/index-DBx5htn3.js.map +0 -1
  165. package/public/assets/index-DHHMf3u1.js +0 -2
  166. package/public/assets/index-DHHMf3u1.js.map +0 -1
  167. package/public/assets/index-DN4Kstsd.js +0 -2
  168. package/public/assets/index-DN4Kstsd.js.map +0 -1
  169. package/public/assets/index-DbMyNDyo.js +0 -2
  170. package/public/assets/index-DbMyNDyo.js.map +0 -1
  171. package/public/assets/index-rcsmuqww.js +0 -2
  172. package/public/assets/index-rcsmuqww.js.map +0 -1
  173. package/public/assets/index-uz2QFRlk.js +0 -2
  174. package/public/assets/index-uz2QFRlk.js.map +0 -1
  175. package/public/assets/main-OmXs7qtG.js +0 -48
  176. package/public/assets/main-OmXs7qtG.js.map +0 -1
  177. package/public/assets/monaco-viewer-Cc9bWg7I.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.test.js","sourceRoot":"","sources":["../../../src/opencode/session-pruning/service.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { test } from \"node:test\"\nimport { DatabaseSync } from \"node:sqlite\"\nimport { mkdtemp, rm } from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\nimport plugin from \"./plugin\"\nimport { storageDirectory } from \"./storage-path\"\nimport { storageKey } from \"./claim-fence\"\nimport { revision } from \"./planner\"\n\n// Exercise the real RPC handlers, storage challenge and file-backed transaction.\n// No shared service, user configuration or real conversation is involved.\nfor (const version of [\"0.0.0-beta-19425\", \"future-release\", undefined]) {\n test(`pruning uses storage capabilities, not runtime version ${version}`, async () => {\n const root = await mkdtemp(path.join(os.tmpdir(), \"pruning-capabilities-\"))\n const filename = path.join(root, \"fixture.db\")\n const db = new DatabaseSync(filename)\n const content = [{ type: \"reasoning\", text: \"REMOVE\" }, { type: \"text\", text: \"KEEP\" }]\n const data = { time: { completed: 1 }, content }\n const input = { sessionID: \"s\", messageID: \"m\", revision: revision(content), indexes: [0] }\n let handlers: any\n const events: unknown[] = []\n let cleanup: (() => unknown) | void = undefined\n try {\n db.exec(`CREATE TABLE session_v2(id TEXT PRIMARY KEY,directory TEXT,project_id TEXT,workspace_id TEXT,time_suspended INTEGER,time_compacting INTEGER,revert TEXT);\n CREATE TABLE event_sequence(aggregate_id TEXT PRIMARY KEY,owner_id TEXT);\n INSERT INTO event_sequence VALUES('s',NULL);\n CREATE TABLE event(aggregate_id TEXT);\n CREATE TABLE kv(key TEXT PRIMARY KEY,value TEXT,time_created INTEGER,time_updated INTEGER);\n CREATE TABLE session_message(id TEXT PRIMARY KEY,session_id TEXT,type TEXT,data TEXT);`)\n db.prepare(\"INSERT INTO session_v2 VALUES('s',?,'p',NULL,NULL,NULL,NULL)\").run(storageDirectory(root))\n db.prepare(\"INSERT INTO session_message VALUES('m','s','assistant',?)\").run(JSON.stringify(data))\n cleanup = await plugin.setup({\n app: { version },\n options: { databasePath: filename },\n location: { directory: root, project: { id: \"p\" } },\n session: { get: async () => ({ location: { directory: root }, projectID: \"p\" }) },\n storage: {\n set: async (key: string, value: unknown) => {\n db.prepare(\"INSERT INTO kv VALUES(?,?,1,1)\").run(storageKey(key), JSON.stringify(value))\n },\n remove: async (key: string) => { db.prepare(\"DELETE FROM kv WHERE key=?\").run(storageKey(key)) },\n },\n rpc: { register: async (_definition: unknown, implementation: unknown) => {\n handlers = implementation\n return { dispose: async () => {}, events: { emit: async (...event: unknown[]) => { events.push(event) } } }\n } },\n } as any)\n assert.equal((await handlers.preview({ sessionID: \"s\", messageID: \"m\" })).liveMutation, true)\n const call = { signal: new AbortController().signal }\n // Native storage capabilities still fail closed irrespective of the label.\n db.exec(\"ALTER TABLE session_v2 RENAME COLUMN time_suspended TO missing_claim\")\n assert.deepEqual(await handlers.prune(input, call), { status: \"blocked\", reason: \"unsupported_storage\" })\n assert.deepEqual(JSON.parse(db.prepare(\"SELECT data FROM session_message\").get()!.data as string), data)\n assert.equal(events.length, 0)\n db.exec(\"ALTER TABLE session_v2 RENAME COLUMN missing_claim TO time_suspended\")\n db.exec(\"UPDATE session_v2 SET time_suspended=1\")\n assert.deepEqual(await handlers.prune(input, call), { status: \"blocked\", reason: \"maintenance_required\" })\n db.exec(\"UPDATE session_v2 SET time_suspended=NULL\")\n const result = await handlers.prune(input, call)\n assert.equal(result.status, \"pruned\")\n assert.deepEqual(await handlers.prune(input, call), result)\n assert.deepEqual(JSON.parse(db.prepare(\"SELECT data FROM session_message\").get()!.data as string).content, [content[1]])\n assert.equal(events.length, 2)\n assert.equal(db.prepare(\"SELECT count(*) AS n FROM kv\").get()!.n, 1, \"only the atomic receipt remains\")\n } finally {\n await cleanup?.()\n db.close()\n await rm(root, { recursive: true, force: true })\n }\n })\n}\n"]}
@@ -0,0 +1,6 @@
1
+ // Matches OpenCode's database/path.ts encoding, without changing case or
2
+ // treating POSIX backslashes as separators.
3
+ export function storageDirectory(directory) {
4
+ return process.platform === "win32" ? directory.replaceAll("\\", "/") : directory;
5
+ }
6
+ //# sourceMappingURL=storage-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-path.js","sourceRoot":"","sources":["../../../src/opencode/session-pruning/storage-path.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4CAA4C;AAC5C,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACnF,CAAC","sourcesContent":["// Matches OpenCode's database/path.ts encoding, without changing case or\n// treating POSIX backslashes as separators.\nexport function storageDirectory(directory: string): string {\n return process.platform === \"win32\" ? directory.replaceAll(\"\\\\\", \"/\") : directory\n}\n"]}
@@ -0,0 +1,53 @@
1
+ import { createHash } from "node:crypto";
2
+ import { pruneRequestSchema, pruneResultSchema } from "./contract";
3
+ import { planPrune, revision } from "./planner";
4
+ import { canonicalContent } from "./revision";
5
+ // Internal kernel. No async callback or network work is allowed while the
6
+ // SQLite write lock is held. Production callers must supply the claim fence.
7
+ export function pruneTransaction(db, input, authorize, receiptPrefix) {
8
+ const request = pruneRequestSchema.parse(input);
9
+ if (db.isTransaction)
10
+ return { status: "blocked", reason: "maintenance_required" };
11
+ db.exec("BEGIN IMMEDIATE");
12
+ try {
13
+ const blocked = authorize();
14
+ if (blocked)
15
+ return { status: "blocked", reason: blocked };
16
+ const key = receiptPrefix && receiptPrefix + createHash("sha256")
17
+ .update(canonicalContent({ ...request, indexes: [...request.indexes].sort((a, b) => a - b) })).digest("hex");
18
+ if (key) {
19
+ const receipt = db.prepare("SELECT value FROM kv WHERE key=?").get(key);
20
+ if (typeof receipt?.value === "string") {
21
+ const result = pruneResultSchema.parse(JSON.parse(receipt.value));
22
+ if (result.status !== "pruned" || result.messageID !== request.messageID)
23
+ return { status: "blocked", reason: "unsupported_storage" };
24
+ return result;
25
+ }
26
+ }
27
+ const row = db.prepare(`SELECT type, data FROM session_message WHERE session_id=? AND id=?
28
+ AND length(CAST(data AS BLOB)) <= 16777216`).get(request.sessionID, request.messageID);
29
+ if (!row || row.type !== "assistant" || typeof row.data !== "string")
30
+ return { status: "blocked", reason: "not_deletable" };
31
+ const plan = planPrune(JSON.parse(row.data), request);
32
+ if (plan.status !== "planned")
33
+ return plan;
34
+ const update = db.prepare("UPDATE session_message SET data=json_set(data,'$.content',json(?)) WHERE session_id=? AND id=? AND data=?")
35
+ .run(JSON.stringify(plan.content), request.sessionID, request.messageID, row.data);
36
+ if (update.changes !== 1)
37
+ return { status: "blocked", reason: "conflict" };
38
+ const result = { status: "pruned", messageID: request.messageID, revision: revision(plan.content), removedCount: request.indexes.length };
39
+ if (key) {
40
+ const now = Date.now();
41
+ // A receipt contains hashes/counts/IDs only, not deleted tool payloads.
42
+ db.prepare("INSERT INTO kv(key,value,time_created,time_updated) VALUES(?,?,?,?)")
43
+ .run(key, JSON.stringify(result), now, now);
44
+ }
45
+ db.exec("COMMIT");
46
+ return result;
47
+ }
48
+ finally {
49
+ if (db.isTransaction)
50
+ db.exec("ROLLBACK");
51
+ }
52
+ }
53
+ //# sourceMappingURL=transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.js","sourceRoot":"","sources":["../../../src/opencode/session-pruning/transaction.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DatabaseSync } from \"node:sqlite\"\nimport { createHash } from \"node:crypto\"\nimport { pruneRequestSchema, pruneResultSchema, type PruneRequest, type PruneResult } from \"./contract\"\nimport { planPrune, revision } from \"./planner\"\nimport { canonicalContent } from \"./revision\"\n\ntype BlockReason = Extract<PruneResult, { status: \"blocked\" }>[\"reason\"]\n\n// Internal kernel. No async callback or network work is allowed while the\n// SQLite write lock is held. Production callers must supply the claim fence.\nexport function pruneTransaction(\n db: DatabaseSync, input: PruneRequest, authorize: () => BlockReason | undefined,\n receiptPrefix?: string,\n): PruneResult {\n const request = pruneRequestSchema.parse(input)\n if (db.isTransaction) return { status: \"blocked\", reason: \"maintenance_required\" }\n db.exec(\"BEGIN IMMEDIATE\")\n try {\n const blocked = authorize()\n if (blocked) return { status: \"blocked\", reason: blocked }\n const key = receiptPrefix && receiptPrefix + createHash(\"sha256\")\n .update(canonicalContent({ ...request, indexes: [...request.indexes].sort((a, b) => a - b) })).digest(\"hex\")\n if (key) {\n const receipt = db.prepare(\"SELECT value FROM kv WHERE key=?\").get(key)\n if (typeof receipt?.value === \"string\") {\n const result = pruneResultSchema.parse(JSON.parse(receipt.value))\n if (result.status !== \"pruned\" || result.messageID !== request.messageID) return { status: \"blocked\", reason: \"unsupported_storage\" }\n return result\n }\n }\n const row = db.prepare(`SELECT type, data FROM session_message WHERE session_id=? AND id=?\n AND length(CAST(data AS BLOB)) <= 16777216`).get(request.sessionID, request.messageID)\n if (!row || row.type !== \"assistant\" || typeof row.data !== \"string\") return { status: \"blocked\", reason: \"not_deletable\" }\n const plan = planPrune(JSON.parse(row.data), request)\n if (plan.status !== \"planned\") return plan\n const update = db.prepare(\"UPDATE session_message SET data=json_set(data,'$.content',json(?)) WHERE session_id=? AND id=? AND data=?\")\n .run(JSON.stringify(plan.content), request.sessionID, request.messageID, row.data)\n if (update.changes !== 1) return { status: \"blocked\", reason: \"conflict\" }\n const result: PruneResult = { status: \"pruned\", messageID: request.messageID, revision: revision(plan.content), removedCount: request.indexes.length }\n if (key) {\n const now = Date.now()\n // A receipt contains hashes/counts/IDs only, not deleted tool payloads.\n db.prepare(\"INSERT INTO kv(key,value,time_created,time_updated) VALUES(?,?,?,?)\")\n .run(key, JSON.stringify(result), now, now)\n }\n db.exec(\"COMMIT\")\n return result\n } finally {\n if (db.isTransaction) db.exec(\"ROLLBACK\")\n }\n}\n"]}
@@ -0,0 +1,84 @@
1
+ import { PRUNING_EVENT, prunedEventSchema } from "./contract";
2
+ function hasPagination(message) {
3
+ return "loading" in message && typeof message.loading === "function"
4
+ && "loadMore" in message && typeof message.loadMore === "function";
5
+ }
6
+ export function createPruningReload(cache) {
7
+ // These are public @opencode/client/solid APIs. beta-19419's plugin Data
8
+ // declaration omits them, but its audited TUI host passes the original cache
9
+ // directly (f91c6d8b25, packages/tui/src/plugin/api.tsx: data: host.data).
10
+ // Fail explicitly if that capability changes; invalidate/sync alone is unsafe.
11
+ const messages = cache.message;
12
+ if (!hasPagination(messages))
13
+ throw new Error("Unsupported TUI cache: pagination publication API is unavailable");
14
+ const pending = new Map();
15
+ const lifetime = new AbortController();
16
+ let disposed = false;
17
+ const refresh = (sessionID) => {
18
+ if (disposed)
19
+ return;
20
+ cache.message.invalidate(sessionID);
21
+ const current = pending.get(sessionID);
22
+ if (current) {
23
+ current.dirty = true;
24
+ return;
25
+ }
26
+ const state = { dirty: false };
27
+ pending.set(sessionID, state);
28
+ void (async () => {
29
+ try {
30
+ do {
31
+ state.dirty = false;
32
+ // beta-19419 tracks pagination independently of invalidate/sync. Join
33
+ // the public loadMore publication before the authoritative read; an
34
+ // older page could otherwise reinsert deleted blocks after sync.
35
+ // With no `all` option, loadMore joins an active successful page rather
36
+ // than fetching another. A failed page must not prevent the reread.
37
+ if (messages.loading(sessionID)) {
38
+ await messages.loadMore(sessionID, { signal: lifetime.signal }).catch(() => undefined);
39
+ }
40
+ if (disposed)
41
+ return;
42
+ // Invalidate again after the join: another UI owner may have synced
43
+ // while we waited, and each coalesced prune needs a fresh read.
44
+ cache.message.invalidate(sessionID);
45
+ await cache.message.sync(sessionID);
46
+ } while (state.dirty && !disposed);
47
+ }
48
+ catch {
49
+ // Leave stale data invalidated. Reopening or reconnecting retries it.
50
+ if (!disposed)
51
+ cache.message.invalidate(sessionID);
52
+ }
53
+ finally {
54
+ pending.delete(sessionID);
55
+ }
56
+ })();
57
+ };
58
+ return {
59
+ event(event) {
60
+ if (disposed)
61
+ return;
62
+ if (event.type === "server.connected") {
63
+ for (const session of cache.list()) {
64
+ if (cache.message.list(session.id).length)
65
+ refresh(session.id);
66
+ else
67
+ cache.message.invalidate(session.id);
68
+ }
69
+ return;
70
+ }
71
+ if (event.type !== PRUNING_EVENT)
72
+ return;
73
+ const parsed = prunedEventSchema.safeParse(event.data);
74
+ if (!parsed.success)
75
+ return;
76
+ const session = cache.get(parsed.data.sessionID);
77
+ if (!session || session.location.directory !== event.location?.directory)
78
+ return;
79
+ refresh(parsed.data.sessionID);
80
+ },
81
+ dispose() { disposed = true; lifetime.abort(); },
82
+ };
83
+ }
84
+ //# sourceMappingURL=tui-reload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-reload.js","sourceRoot":"","sources":["../../../src/opencode/session-pruning/tui-reload.ts"],"names":[],"mappings":"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","sourcesContent":["import { PRUNING_EVENT, prunedEventSchema } from \"./contract\"\n\ninterface SessionCache {\n list(): Array<{ id: string }>\n get(id: string): { location: { directory: string } } | undefined\n message: Partial<Pagination> & {\n list(id: string): unknown[]\n invalidate(id: string): void\n sync(id: string): Promise<void>\n }\n}\n\ninterface Pagination {\n loading(id: string): boolean\n loadMore(id: string, options?: { signal?: AbortSignal }): Promise<void>\n}\n\nfunction hasPagination(message: SessionCache[\"message\"]): message is SessionCache[\"message\"] & Pagination {\n return \"loading\" in message && typeof message.loading === \"function\"\n && \"loadMore\" in message && typeof message.loadMore === \"function\"\n}\n\nexport function createPruningReload(cache: SessionCache) {\n // These are public @opencode/client/solid APIs. beta-19419's plugin Data\n // declaration omits them, but its audited TUI host passes the original cache\n // directly (f91c6d8b25, packages/tui/src/plugin/api.tsx: data: host.data).\n // Fail explicitly if that capability changes; invalidate/sync alone is unsafe.\n const messages = cache.message\n if (!hasPagination(messages)) throw new Error(\"Unsupported TUI cache: pagination publication API is unavailable\")\n const pending = new Map<string, { dirty: boolean }>()\n const lifetime = new AbortController()\n let disposed = false\n const refresh = (sessionID: string) => {\n if (disposed) return\n cache.message.invalidate(sessionID)\n const current = pending.get(sessionID)\n if (current) { current.dirty = true; return }\n const state = { dirty: false }\n pending.set(sessionID, state)\n void (async () => {\n try {\n do {\n state.dirty = false\n // beta-19419 tracks pagination independently of invalidate/sync. Join\n // the public loadMore publication before the authoritative read; an\n // older page could otherwise reinsert deleted blocks after sync.\n // With no `all` option, loadMore joins an active successful page rather\n // than fetching another. A failed page must not prevent the reread.\n if (messages.loading(sessionID)) {\n await messages.loadMore(sessionID, { signal: lifetime.signal }).catch(() => undefined)\n }\n if (disposed) return\n // Invalidate again after the join: another UI owner may have synced\n // while we waited, and each coalesced prune needs a fresh read.\n cache.message.invalidate(sessionID)\n await cache.message.sync(sessionID)\n } while (state.dirty && !disposed)\n } catch {\n // Leave stale data invalidated. Reopening or reconnecting retries it.\n if (!disposed) cache.message.invalidate(sessionID)\n } finally { pending.delete(sessionID) }\n })()\n }\n return {\n event(event: { type: string; data?: unknown; location?: { directory: string } }) {\n if (disposed) return\n if (event.type === \"server.connected\") {\n for (const session of cache.list()) {\n if (cache.message.list(session.id).length) refresh(session.id)\n else cache.message.invalidate(session.id)\n }\n return\n }\n if (event.type !== PRUNING_EVENT) return\n const parsed = prunedEventSchema.safeParse(event.data)\n if (!parsed.success) return\n const session = cache.get(parsed.data.sessionID)\n if (!session || session.location.directory !== event.location?.directory) return\n refresh(parsed.data.sessionID)\n },\n dispose() { disposed = true; lifetime.abort() },\n }\n}\n"]}
@@ -0,0 +1,269 @@
1
+ // Executed by tui-reload.test.ts in a browser-conditioned child process. Only
2
+ // the transport is a fixture: publication, pagination, indexing, invalidation,
3
+ // coalescing and Solid stores all come from the unmodified installed npm cache.
4
+ import assert from "node:assert/strict";
5
+ import { readFileSync } from "node:fs";
6
+ import { test } from "node:test";
7
+ import { createRoot } from "solid-js";
8
+ import { createPruningReload } from "./tui-reload";
9
+ import tui from "./tui";
10
+ // Use the exact native client already locked through @opencode/plugin. No npm
11
+ // install, daemon discovery, user config, database, or checkout source access.
12
+ const clientEntry = new URL(import.meta.resolve("@opencode/client/solid"));
13
+ const manifest = JSON.parse(readFileSync(new URL("../../package.json", clientEntry), "utf8"));
14
+ assert.equal(manifest.name, "@opencode/client");
15
+ console.log(`Testing native Solid cache ${manifest.version}`);
16
+ const { createData } = await import(clientEntry.href);
17
+ function deferred() {
18
+ let resolve;
19
+ let reject;
20
+ const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
21
+ return { promise, resolve, reject };
22
+ }
23
+ function message(id, removed = false) {
24
+ return {
25
+ id, type: "assistant", agent: "build", model: { providerID: "fixture", id: "fixture" },
26
+ time: { created: 1, completed: 2 }, finish: "stop",
27
+ content: [
28
+ ...(removed ? [{ type: "reasoning", text: "REMOVED", time: { created: 1, completed: 2 } }] : []),
29
+ { type: "text", text: `KEEP-${id}` },
30
+ ],
31
+ };
32
+ }
33
+ function page(messages, next) {
34
+ return { data: messages, cursor: { next } };
35
+ }
36
+ const event = {
37
+ type: "rpc.codenomad.session-pruning.pruned", location: { directory: "/fixture" },
38
+ data: { sessionID: "s", messageID: "older", revision: "a".repeat(64) },
39
+ };
40
+ const turn = () => new Promise(resolve => setImmediate(resolve));
41
+ function fixture() {
42
+ const listeners = new Set();
43
+ const requests = [];
44
+ const api = {
45
+ message: {
46
+ list: (input) => {
47
+ assert.equal(input.sessionID, "s");
48
+ const response = deferred();
49
+ requests.push({ cursor: input.cursor, response });
50
+ return response.promise;
51
+ },
52
+ },
53
+ };
54
+ let disposeRoot;
55
+ const data = createRoot(dispose => {
56
+ disposeRoot = dispose;
57
+ return createData({
58
+ api: () => api, directory: "/fixture",
59
+ event: {
60
+ on: () => () => { },
61
+ listen: listener => { listeners.add(listener); return () => { listeners.delete(listener); }; },
62
+ },
63
+ });
64
+ });
65
+ data.session.remember({
66
+ id: "s", projectID: "p", cost: 0,
67
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
68
+ time: { created: 1, updated: 2 }, location: { directory: "/fixture" },
69
+ });
70
+ const reload = createPruningReload(data.session);
71
+ return {
72
+ data, reload, requests,
73
+ emitPrune() {
74
+ // The custom RPC envelope is validated by the companion, not the
75
+ // generated native-event union. This is the simulated transport boundary.
76
+ const details = { ...event, id: "evt_prune", created: 3 };
77
+ for (const listener of listeners)
78
+ listener({ name: details.type, details });
79
+ },
80
+ async seed() {
81
+ const sync = data.session.message.sync("s");
82
+ requests[0].response.resolve(page([message("latest")], "older-page"));
83
+ await sync;
84
+ },
85
+ text: () => JSON.stringify(data.session.message.list("s")),
86
+ dispose() { reload.dispose(); disposeRoot(); },
87
+ };
88
+ }
89
+ test("the published plugin entrypoint consumes the host's original native cache", async (t) => {
90
+ const f = fixture();
91
+ t.after(() => f.dispose());
92
+ // Matches the audited host's `data: host.data` assignment; no hidden cache
93
+ // fields or replacement methods are attached by the test or companion.
94
+ // The headless fixture omits unrelated renderer/UI services.
95
+ const cleanup = await tui.setup({ data: f.data });
96
+ t.after(() => { if (typeof cleanup === "function")
97
+ return cleanup(); });
98
+ await f.seed();
99
+ const older = f.data.session.message.loadMore("s");
100
+ f.emitPrune();
101
+ f.requests[1].response.resolve(page([message("older", true)]));
102
+ await older;
103
+ await turn();
104
+ assert.equal(f.requests.length, 3);
105
+ f.requests[2].response.resolve(page([message("latest")], "older-page"));
106
+ await turn();
107
+ assert(!f.text().includes("REMOVED"));
108
+ });
109
+ test("control: invalidate/sync alone leaves REMOVED after late pagination", async (t) => {
110
+ const f = fixture();
111
+ t.after(() => f.dispose());
112
+ await f.seed();
113
+ const older = f.data.session.message.loadMore("s");
114
+ f.data.session.message.invalidate("s");
115
+ const sync = f.data.session.message.sync("s");
116
+ f.requests[2].response.resolve(page([message("latest")], "older-page"));
117
+ await sync;
118
+ assert(!f.text().includes("REMOVED"));
119
+ f.requests[1].response.resolve(page([message("older", true)]));
120
+ await older;
121
+ await turn();
122
+ assert(f.text().includes("REMOVED"), "The control must reproduce the reviewer's actual native-cache failure");
123
+ assert.equal(f.requests.length, 3, "There is no subsequent native refresh");
124
+ });
125
+ for (const trigger of [event, { type: "server.connected" }]) {
126
+ test(`companion joins pending publication and authoritatively rereads on ${trigger.type}`, async (t) => {
127
+ const f = fixture();
128
+ t.after(() => f.dispose());
129
+ await f.seed();
130
+ const older = f.data.session.message.loadMore("s");
131
+ assert(f.data.session.message.loading("s"));
132
+ f.reload.event(trigger);
133
+ assert.equal(f.requests.length, 2, "Joining does not request another page or read too early");
134
+ // The TUI itself can sync while the companion waits. Reproduce the exact
135
+ // latest-read -> late old-page ordering, then demand one final reread.
136
+ const intermediate = f.data.session.message.sync("s");
137
+ f.requests[2].response.resolve(page([message("latest")], "older-page"));
138
+ await intermediate;
139
+ f.requests[1].response.resolve(page([message("older", true)]));
140
+ await older;
141
+ await turn();
142
+ assert.equal(f.requests.length, 4);
143
+ assert.equal(f.requests[3].cursor, undefined, "Final read must replace, not merge, the old window");
144
+ f.requests[3].response.resolve(page([message("latest")], "older-page"));
145
+ await turn();
146
+ assert(!f.text().includes("REMOVED"));
147
+ assert.equal(f.data.session.message.get("s", "older"), undefined);
148
+ // Dropping history permanently is not a fix: it must still paginate back
149
+ // to the pruned message with its remaining content and index intact.
150
+ const cleanOlder = f.data.session.message.loadMore("s");
151
+ f.requests[4].response.resolve(page([message("older")]));
152
+ await cleanOlder;
153
+ assert(f.text().includes("KEEP-older"));
154
+ assert(!f.text().includes("REMOVED"));
155
+ assert.equal(f.data.session.message.get("s", "older")?.id, "older");
156
+ await turn();
157
+ assert.equal(f.requests.length, 5, "No next event or polling is required");
158
+ });
159
+ }
160
+ test("joins all-pages publication, including a prune during a buffered older page", async (t) => {
161
+ const f = fixture();
162
+ t.after(() => f.dispose());
163
+ await f.seed();
164
+ const older = f.data.session.message.loadMore("s", { all: true });
165
+ f.requests[1].response.resolve(page([message("older", true)], "oldest-page"));
166
+ await turn();
167
+ f.reload.event(event);
168
+ f.reload.event(event);
169
+ assert.equal(f.requests.length, 3);
170
+ f.requests[2].response.resolve(page([message("oldest")]));
171
+ await older;
172
+ await turn();
173
+ assert.equal(f.requests[3].cursor, undefined);
174
+ f.requests[3].response.resolve(page([message("latest")], "older-page"));
175
+ await turn();
176
+ // A second event while the first refresh was pending receives a fresh pass.
177
+ assert.equal(f.requests.length, 5);
178
+ f.requests[4].response.resolve(page([message("latest")], "older-page"));
179
+ await turn();
180
+ assert(!f.text().includes("REMOVED"));
181
+ });
182
+ test("failed pagination does not suppress the authoritative refresh", async (t) => {
183
+ const f = fixture();
184
+ t.after(() => f.dispose());
185
+ await f.seed();
186
+ const older = f.data.session.message.loadMore("s");
187
+ const rejected = assert.rejects(older, /offline/);
188
+ f.reload.event(event);
189
+ f.requests[1].response.reject(new Error("offline"));
190
+ await rejected;
191
+ await turn();
192
+ assert.equal(f.requests.length, 3);
193
+ assert.equal(f.requests[2].cursor, undefined);
194
+ f.requests[2].response.resolve(page([message("latest")]));
195
+ await turn();
196
+ assert(!f.data.session.message.loading("s"));
197
+ assert(!f.text().includes("REMOVED"));
198
+ });
199
+ test("a prune during an in-flight sync forces a second authoritative pass", async (t) => {
200
+ const f = fixture();
201
+ t.after(() => f.dispose());
202
+ await f.seed();
203
+ f.reload.event(event);
204
+ f.reload.event(event);
205
+ f.requests[1].response.resolve(page([message("latest", true)]));
206
+ await turn();
207
+ assert.equal(f.requests.length, 3);
208
+ f.requests[2].response.resolve(page([message("latest")]));
209
+ await turn();
210
+ assert(!f.text().includes("REMOVED"));
211
+ });
212
+ test("joins both an older pagination and a latest-window sync started before pruning", async (t) => {
213
+ const f = fixture();
214
+ t.after(() => f.dispose());
215
+ await f.seed();
216
+ const older = f.data.session.message.loadMore("s");
217
+ f.data.session.message.invalidate("s");
218
+ const oldSync = f.data.session.message.sync("s");
219
+ f.reload.event(event);
220
+ f.requests[1].response.resolve(page([message("older", true)]));
221
+ await older;
222
+ await turn();
223
+ assert.equal(f.requests.length, 3, "Native sync serialization must also drain the pre-prune window");
224
+ f.requests[2].response.resolve(page([message("latest", true)], "older-page"));
225
+ await oldSync;
226
+ await turn();
227
+ assert.equal(f.requests.length, 4);
228
+ f.requests[3].response.resolve(page([message("latest")], "older-page"));
229
+ await turn();
230
+ assert(!f.text().includes("REMOVED"));
231
+ });
232
+ test("an aborted TUI page cannot abandon the companion's final reread", async (t) => {
233
+ const f = fixture();
234
+ t.after(() => f.dispose());
235
+ await f.seed();
236
+ const controller = new AbortController();
237
+ const older = f.data.session.message.loadMore("s", { signal: controller.signal });
238
+ f.reload.event(event);
239
+ controller.abort();
240
+ f.requests[1].response.resolve(page([message("older", true)]));
241
+ await older;
242
+ await turn();
243
+ // Native loadMore retries when the joined page did not publish. That new
244
+ // request starts after pruning; the final authoritative read still follows.
245
+ assert.equal(f.requests[2].cursor, "older-page");
246
+ f.requests[2].response.resolve(page([message("older")]));
247
+ await turn();
248
+ assert.equal(f.requests[3].cursor, undefined);
249
+ f.requests[3].response.resolve(page([message("latest")], "older-page"));
250
+ await turn();
251
+ assert(!f.text().includes("REMOVED"));
252
+ assert.equal(f.requests.length, 4);
253
+ });
254
+ test("disposing the companion releases its join without cancelling TUI-owned pagination", async (t) => {
255
+ const f = fixture();
256
+ t.after(() => f.dispose());
257
+ await f.seed();
258
+ const older = f.data.session.message.loadMore("s");
259
+ f.reload.event(event);
260
+ f.reload.dispose();
261
+ await turn();
262
+ assert(f.data.session.message.loading("s"), "The TUI still owns its request");
263
+ f.requests[1].response.resolve(page([message("older")]));
264
+ await older;
265
+ await turn();
266
+ assert.equal(f.requests.length, 2, "A disposed companion must not start a refresh");
267
+ assert(f.text().includes("KEEP-older"));
268
+ });
269
+ //# sourceMappingURL=tui-reload.native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-reload.native.js","sourceRoot":"","sources":["../../../src/opencode/session-pruning/tui-reload.native.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+EAA+E;AAC/E,gFAAgF;AAChF,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAGrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,GAAG,MAAM,OAAO,CAAA;AAEvB,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAA;AAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,oBAAoB,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAC7F,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAA;AAC/C,OAAO,CAAC,GAAG,CAAC,8BAA8B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;AAC7D,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAA4C,CAAA;AAEhG,SAAS,QAAQ;IACf,IAAI,OAA4B,CAAA;IAChC,IAAI,MAA+B,CAAA;IACnC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAI,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,GAAG,GAAG,CAAC,CAAC,MAAM,GAAG,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;IAC3E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AACrC,CAAC;AAED,SAAS,OAAO,CAAC,EAAU,EAAE,OAAO,GAAG,KAAK;IAC1C,OAAO;QACL,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE;QACtF,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM;QAClD,OAAO,EAAE;YACP,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAoB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACzG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;SACrC;KACF,CAAA;AACH,CAAC;AAED,SAAS,IAAI,CAAC,QAA8B,EAAE,IAAa;IACzD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,CAAA;AAC7C,CAAC;AAED,MAAM,KAAK,GAAG;IACZ,IAAI,EAAE,sCAAsC,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE;IACjF,IAAI,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;CACvE,CAAA;AACD,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;AAEtE,SAAS,OAAO;IACd,MAAM,SAAS,GAAG,IAAI,GAAG,EAAqD,CAAA;IAC9E,MAAM,QAAQ,GAAyF,EAAE,CAAA;IACzG,MAAM,GAAG,GAAG;QACV,OAAO,EAAE;YACP,IAAI,EAAE,CAAC,KAA6C,EAAE,EAAE;gBACtD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;gBAClC,MAAM,QAAQ,GAAG,QAAQ,EAAqB,CAAA;gBAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;gBACjD,OAAO,QAAQ,CAAC,OAAO,CAAA;YACzB,CAAC;SACF;KAC+C,CAAA;IAClD,IAAI,WAAwB,CAAA;IAC5B,MAAM,IAAI,GAAS,UAAU,CAAC,OAAO,CAAC,EAAE;QACtC,WAAW,GAAG,OAAO,CAAA;QACrB,OAAO,UAAU,CAAC;YAChB,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU;YACrC,KAAK,EAAE;gBACL,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;gBAClB,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA,CAAC,CAAC,CAAA,CAAC,CAAC;aAC7F;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QACpB,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QAChC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC3E,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE;KACtE,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAChD,OAAO;QACL,IAAI,EAAE,MAAM,EAAE,QAAQ;QACtB,SAAS;YACP,iEAAiE;YACjE,0EAA0E;YAC1E,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAA8B,CAAA;YACrF,KAAK,MAAM,QAAQ,IAAI,SAAS;gBAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;QAC7E,CAAC;QACD,KAAK,CAAC,IAAI;YACR,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAC3C,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;YACrE,MAAM,IAAI,CAAA;QACZ,CAAC;QACD,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1D,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA,CAAC,CAAC;KAC9C,CAAA;AACH,CAAC;AAED,IAAI,CAAC,2EAA2E,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC1F,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,2EAA2E;IAC3E,uEAAuE;IACvE,6DAA6D;IAC7D,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,KAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAA6D,CAAC,CAAA;IAC7G,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,OAAO,KAAK,UAAU;QAAE,OAAO,OAAO,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;IACtE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAClD,CAAC,CAAC,SAAS,EAAE,CAAA;IACb,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9D,MAAM,KAAK,CAAA;IACX,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;IACvE,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,qEAAqE,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACpF,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAClD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC7C,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;IACvE,MAAM,IAAI,CAAA;IACV,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;IACrC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9D,MAAM,KAAK,CAAA;IACX,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,uEAAuE,CAAC,CAAA;IAC7G,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,uCAAuC,CAAC,CAAA;AAC7E,CAAC,CAAC,CAAA;AAEF,KAAK,MAAM,OAAO,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;IAC5D,IAAI,CAAC,sEAAsE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;QACnG,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;QAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;QACd,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QAClD,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3C,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACvB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,yDAAyD,CAAC,CAAA;QAE7F,yEAAyE;QACzE,uEAAuE;QACvE,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACrD,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;QACvE,MAAM,YAAY,CAAA;QAClB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QAC9D,MAAM,KAAK,CAAA;QACX,MAAM,IAAI,EAAE,CAAA;QACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QAClC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,oDAAoD,CAAC,CAAA;QACnG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;QACvE,MAAM,IAAI,EAAE,CAAA;QACZ,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;QACrC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,CAAA;QAEjE,yEAAyE;QACzE,qEAAqE;QACrE,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QACvD,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;QACxD,MAAM,UAAU,CAAA;QAChB,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAA;QACvC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;QACrC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;QACnE,MAAM,IAAI,EAAE,CAAA;QACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,sCAAsC,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,IAAI,CAAC,6EAA6E,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC5F,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAA;IACjE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAA;IAC7E,MAAM,IAAI,EAAE,CAAA;IACZ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IACzD,MAAM,KAAK,CAAA;IACX,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC7C,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;IACvE,MAAM,IAAI,EAAE,CAAA;IACZ,4EAA4E;IAC5E,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;IACvE,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,+DAA+D,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC9E,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAClD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACjD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAA;IACnD,MAAM,QAAQ,CAAA;IACd,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC7C,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IACzD,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5C,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,qEAAqE,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IACpF,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/D,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IACzD,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,gFAAgF,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAC/F,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAClD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;IACtC,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAChD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9D,MAAM,KAAK,CAAA;IACX,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,gEAAgE,CAAC,CAAA;IACpG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;IAC7E,MAAM,OAAO,CAAA;IACb,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;IACvE,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,iEAAiE,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAChF,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAA;IACjF,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,UAAU,CAAC,KAAK,EAAE,CAAA;IAClB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9D,MAAM,KAAK,CAAA;IACX,MAAM,IAAI,EAAE,CAAA;IACZ,yEAAyE;IACzE,4EAA4E;IAC5E,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IAChD,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACxD,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC7C,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAA;IACvE,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;IACrC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;AACpC,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,mFAAmF,EAAE,KAAK,EAAC,CAAC,EAAC,EAAE;IAClG,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACd,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAClD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;IAClB,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,gCAAgC,CAAC,CAAA;IAC7E,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACxD,MAAM,KAAK,CAAA;IACX,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,+CAA+C,CAAC,CAAA;IACnF,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAA;AACzC,CAAC,CAAC,CAAA","sourcesContent":["// Executed by tui-reload.test.ts in a browser-conditioned child process. Only\n// the transport is a fixture: publication, pagination, indexing, invalidation,\n// coalescing and Solid stores all come from the unmodified installed npm cache.\nimport assert from \"node:assert/strict\"\nimport { readFileSync } from \"node:fs\"\nimport { test } from \"node:test\"\nimport { createRoot } from \"solid-js\"\nimport type { CreateDataInput, Data } from \"@opencode/client/solid\"\nimport type { MessageListOutput, OpenCodeEvent, SessionMessageInfo } from \"@opencode/client\"\nimport { createPruningReload } from \"./tui-reload\"\nimport tui from \"./tui\"\n\n// Use the exact native client already locked through @opencode/plugin. No npm\n// install, daemon discovery, user config, database, or checkout source access.\nconst clientEntry = new URL(import.meta.resolve(\"@opencode/client/solid\"))\nconst manifest = JSON.parse(readFileSync(new URL(\"../../package.json\", clientEntry), \"utf8\"))\nassert.equal(manifest.name, \"@opencode/client\")\nconsole.log(`Testing native Solid cache ${manifest.version}`)\nconst { createData } = await import(clientEntry.href) as typeof import(\"@opencode/client/solid\")\n\nfunction deferred<T>() {\n let resolve!: (value: T) => void\n let reject!: (error: Error) => void\n const promise = new Promise<T>((yes, no) => { resolve = yes; reject = no })\n return { promise, resolve, reject }\n}\n\nfunction message(id: string, removed = false): SessionMessageInfo {\n return {\n id, type: \"assistant\", agent: \"build\", model: { providerID: \"fixture\", id: \"fixture\" },\n time: { created: 1, completed: 2 }, finish: \"stop\",\n content: [\n ...(removed ? [{ type: \"reasoning\" as const, text: \"REMOVED\", time: { created: 1, completed: 2 } }] : []),\n { type: \"text\", text: `KEEP-${id}` },\n ],\n }\n}\n\nfunction page(messages: SessionMessageInfo[], next?: string): MessageListOutput {\n return { data: messages, cursor: { next } }\n}\n\nconst event = {\n type: \"rpc.codenomad.session-pruning.pruned\", location: { directory: \"/fixture\" },\n data: { sessionID: \"s\", messageID: \"older\", revision: \"a\".repeat(64) },\n}\nconst turn = () => new Promise<void>(resolve => setImmediate(resolve))\n\nfunction fixture() {\n const listeners = new Set<Parameters<CreateDataInput[\"event\"][\"listen\"]>[0]>()\n const requests: Array<{ cursor?: string; response: ReturnType<typeof deferred<MessageListOutput>> }> = []\n const api = {\n message: {\n list: (input: { sessionID: string; cursor?: string }) => {\n assert.equal(input.sessionID, \"s\")\n const response = deferred<MessageListOutput>()\n requests.push({ cursor: input.cursor, response })\n return response.promise\n },\n },\n } as unknown as ReturnType<CreateDataInput[\"api\"]>\n let disposeRoot!: () => void\n const data: Data = createRoot(dispose => {\n disposeRoot = dispose\n return createData({\n api: () => api, directory: \"/fixture\",\n event: {\n on: () => () => {},\n listen: listener => { listeners.add(listener); return () => { listeners.delete(listener) } },\n },\n })\n })\n data.session.remember({\n id: \"s\", projectID: \"p\", cost: 0,\n tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },\n time: { created: 1, updated: 2 }, location: { directory: \"/fixture\" },\n })\n const reload = createPruningReload(data.session)\n return {\n data, reload, requests,\n emitPrune() {\n // The custom RPC envelope is validated by the companion, not the\n // generated native-event union. This is the simulated transport boundary.\n const details = { ...event, id: \"evt_prune\", created: 3 } as unknown as OpenCodeEvent\n for (const listener of listeners) listener({ name: details.type, details })\n },\n async seed() {\n const sync = data.session.message.sync(\"s\")\n requests[0].response.resolve(page([message(\"latest\")], \"older-page\"))\n await sync\n },\n text: () => JSON.stringify(data.session.message.list(\"s\")),\n dispose() { reload.dispose(); disposeRoot() },\n }\n}\n\ntest(\"the published plugin entrypoint consumes the host's original native cache\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n // Matches the audited host's `data: host.data` assignment; no hidden cache\n // fields or replacement methods are attached by the test or companion.\n // The headless fixture omits unrelated renderer/UI services.\n const cleanup = await tui.setup!({ data: f.data } as unknown as Parameters<NonNullable<typeof tui.setup>>[0])\n t.after(() => { if (typeof cleanup === \"function\") return cleanup() })\n await f.seed()\n const older = f.data.session.message.loadMore(\"s\")\n f.emitPrune()\n f.requests[1].response.resolve(page([message(\"older\", true)]))\n await older\n await turn()\n assert.equal(f.requests.length, 3)\n f.requests[2].response.resolve(page([message(\"latest\")], \"older-page\"))\n await turn()\n assert(!f.text().includes(\"REMOVED\"))\n})\n\ntest(\"control: invalidate/sync alone leaves REMOVED after late pagination\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n const older = f.data.session.message.loadMore(\"s\")\n f.data.session.message.invalidate(\"s\")\n const sync = f.data.session.message.sync(\"s\")\n f.requests[2].response.resolve(page([message(\"latest\")], \"older-page\"))\n await sync\n assert(!f.text().includes(\"REMOVED\"))\n f.requests[1].response.resolve(page([message(\"older\", true)]))\n await older\n await turn()\n assert(f.text().includes(\"REMOVED\"), \"The control must reproduce the reviewer's actual native-cache failure\")\n assert.equal(f.requests.length, 3, \"There is no subsequent native refresh\")\n})\n\nfor (const trigger of [event, { type: \"server.connected\" }]) {\n test(`companion joins pending publication and authoritatively rereads on ${trigger.type}`, async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n const older = f.data.session.message.loadMore(\"s\")\n assert(f.data.session.message.loading(\"s\"))\n f.reload.event(trigger)\n assert.equal(f.requests.length, 2, \"Joining does not request another page or read too early\")\n\n // The TUI itself can sync while the companion waits. Reproduce the exact\n // latest-read -> late old-page ordering, then demand one final reread.\n const intermediate = f.data.session.message.sync(\"s\")\n f.requests[2].response.resolve(page([message(\"latest\")], \"older-page\"))\n await intermediate\n f.requests[1].response.resolve(page([message(\"older\", true)]))\n await older\n await turn()\n assert.equal(f.requests.length, 4)\n assert.equal(f.requests[3].cursor, undefined, \"Final read must replace, not merge, the old window\")\n f.requests[3].response.resolve(page([message(\"latest\")], \"older-page\"))\n await turn()\n assert(!f.text().includes(\"REMOVED\"))\n assert.equal(f.data.session.message.get(\"s\", \"older\"), undefined)\n\n // Dropping history permanently is not a fix: it must still paginate back\n // to the pruned message with its remaining content and index intact.\n const cleanOlder = f.data.session.message.loadMore(\"s\")\n f.requests[4].response.resolve(page([message(\"older\")]))\n await cleanOlder\n assert(f.text().includes(\"KEEP-older\"))\n assert(!f.text().includes(\"REMOVED\"))\n assert.equal(f.data.session.message.get(\"s\", \"older\")?.id, \"older\")\n await turn()\n assert.equal(f.requests.length, 5, \"No next event or polling is required\")\n })\n}\n\ntest(\"joins all-pages publication, including a prune during a buffered older page\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n const older = f.data.session.message.loadMore(\"s\", { all: true })\n f.requests[1].response.resolve(page([message(\"older\", true)], \"oldest-page\"))\n await turn()\n f.reload.event(event)\n f.reload.event(event)\n assert.equal(f.requests.length, 3)\n f.requests[2].response.resolve(page([message(\"oldest\")]))\n await older\n await turn()\n assert.equal(f.requests[3].cursor, undefined)\n f.requests[3].response.resolve(page([message(\"latest\")], \"older-page\"))\n await turn()\n // A second event while the first refresh was pending receives a fresh pass.\n assert.equal(f.requests.length, 5)\n f.requests[4].response.resolve(page([message(\"latest\")], \"older-page\"))\n await turn()\n assert(!f.text().includes(\"REMOVED\"))\n})\n\ntest(\"failed pagination does not suppress the authoritative refresh\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n const older = f.data.session.message.loadMore(\"s\")\n const rejected = assert.rejects(older, /offline/)\n f.reload.event(event)\n f.requests[1].response.reject(new Error(\"offline\"))\n await rejected\n await turn()\n assert.equal(f.requests.length, 3)\n assert.equal(f.requests[2].cursor, undefined)\n f.requests[2].response.resolve(page([message(\"latest\")]))\n await turn()\n assert(!f.data.session.message.loading(\"s\"))\n assert(!f.text().includes(\"REMOVED\"))\n})\n\ntest(\"a prune during an in-flight sync forces a second authoritative pass\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n f.reload.event(event)\n f.reload.event(event)\n f.requests[1].response.resolve(page([message(\"latest\", true)]))\n await turn()\n assert.equal(f.requests.length, 3)\n f.requests[2].response.resolve(page([message(\"latest\")]))\n await turn()\n assert(!f.text().includes(\"REMOVED\"))\n})\n\ntest(\"joins both an older pagination and a latest-window sync started before pruning\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n const older = f.data.session.message.loadMore(\"s\")\n f.data.session.message.invalidate(\"s\")\n const oldSync = f.data.session.message.sync(\"s\")\n f.reload.event(event)\n f.requests[1].response.resolve(page([message(\"older\", true)]))\n await older\n await turn()\n assert.equal(f.requests.length, 3, \"Native sync serialization must also drain the pre-prune window\")\n f.requests[2].response.resolve(page([message(\"latest\", true)], \"older-page\"))\n await oldSync\n await turn()\n assert.equal(f.requests.length, 4)\n f.requests[3].response.resolve(page([message(\"latest\")], \"older-page\"))\n await turn()\n assert(!f.text().includes(\"REMOVED\"))\n})\n\ntest(\"an aborted TUI page cannot abandon the companion's final reread\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n const controller = new AbortController()\n const older = f.data.session.message.loadMore(\"s\", { signal: controller.signal })\n f.reload.event(event)\n controller.abort()\n f.requests[1].response.resolve(page([message(\"older\", true)]))\n await older\n await turn()\n // Native loadMore retries when the joined page did not publish. That new\n // request starts after pruning; the final authoritative read still follows.\n assert.equal(f.requests[2].cursor, \"older-page\")\n f.requests[2].response.resolve(page([message(\"older\")]))\n await turn()\n assert.equal(f.requests[3].cursor, undefined)\n f.requests[3].response.resolve(page([message(\"latest\")], \"older-page\"))\n await turn()\n assert(!f.text().includes(\"REMOVED\"))\n assert.equal(f.requests.length, 4)\n})\n\ntest(\"disposing the companion releases its join without cancelling TUI-owned pagination\", async t => {\n const f = fixture(); t.after(() => f.dispose())\n await f.seed()\n const older = f.data.session.message.loadMore(\"s\")\n f.reload.event(event)\n f.reload.dispose()\n await turn()\n assert(f.data.session.message.loading(\"s\"), \"The TUI still owns its request\")\n f.requests[1].response.resolve(page([message(\"older\")]))\n await older\n await turn()\n assert.equal(f.requests.length, 2, \"A disposed companion must not start a refresh\")\n assert(f.text().includes(\"KEEP-older\"))\n})\n"]}
@@ -0,0 +1,71 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { spawnSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createPruningReload } from "./tui-reload";
6
+ test("TUI companion coalesces invalidations and rereads after a missed-event reconnect", async () => {
7
+ const invalidated = [];
8
+ const read = [];
9
+ let release;
10
+ const pending = new Promise(resolve => { release = resolve; });
11
+ const reload = createPruningReload({
12
+ list: () => [{ id: "s" }, { id: "unloaded" }],
13
+ get: id => id === "s" ? { location: { directory: "/work" } } : undefined,
14
+ message: {
15
+ list: id => id === "s" ? [{}] : [],
16
+ loading: () => false,
17
+ loadMore: async () => assert.fail("unexpected pagination"),
18
+ invalidate: id => { invalidated.push(id); },
19
+ sync: async (id) => { read.push(id); await pending; },
20
+ },
21
+ });
22
+ const event = { type: "rpc.codenomad.session-pruning.pruned", location: { directory: "/work" }, data: { sessionID: "s", messageID: "m", revision: "a".repeat(64) } };
23
+ reload.event(event);
24
+ reload.event(event);
25
+ assert.deepEqual(read, ["s"]);
26
+ release();
27
+ await new Promise(resolve => setImmediate(resolve));
28
+ assert.deepEqual(read, ["s", "s"]);
29
+ reload.event({ type: "server.connected" });
30
+ await new Promise(resolve => setImmediate(resolve));
31
+ assert.deepEqual(read, ["s", "s", "s"]);
32
+ assert(invalidated.includes("unloaded"));
33
+ reload.dispose();
34
+ reload.event(event);
35
+ assert.equal(read.length, 3);
36
+ });
37
+ test("TUI companion rejects foreign locations, malformed events and unknown sessions", () => {
38
+ const reload = createPruningReload({
39
+ list: () => [], get: id => id === "s" ? { location: { directory: "/work" } } : undefined,
40
+ message: {
41
+ list: () => [], loading: () => false, loadMore: async () => assert.fail("unexpected pagination"),
42
+ invalidate: () => assert.fail("unexpected invalidation"), sync: async () => assert.fail("unexpected read"),
43
+ },
44
+ });
45
+ reload.event({ type: "rpc.other.pruned", data: {} });
46
+ reload.event({ type: "rpc.codenomad.session-pruning.pruned", data: {} });
47
+ for (const sessionID of ["s", "other"])
48
+ reload.event({ type: "rpc.codenomad.session-pruning.pruned", location: { directory: "/elsewhere" }, data: { sessionID, messageID: "m", revision: "a".repeat(64) } });
49
+ reload.dispose();
50
+ });
51
+ test("TUI reload regressions against the installed published Solid cache", () => {
52
+ // Keep Solid's browser conditions out of the server test process. Resolve the
53
+ // native client from the pinned plugin dependency, not a checkout, global
54
+ // install, copied cache implementation, or a network install during tests.
55
+ const env = { ...process.env };
56
+ delete env.NODE_TEST_CONTEXT;
57
+ const result = spawnSync(process.execPath, [
58
+ "--conditions=browser", "--import", "tsx", "--test",
59
+ fileURLToPath(new URL("./tui-reload.native.ts", import.meta.url)),
60
+ ], { encoding: "utf8", timeout: 30_000, env });
61
+ assert.ifError(result.error);
62
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
63
+ assert.match(result.stdout, /control: invalidate\/sync/, "The child must actually execute its native-cache tests");
64
+ });
65
+ test("TUI companion refuses a cache without public pagination coordination", () => {
66
+ assert.throws(() => createPruningReload({
67
+ list: () => [], get: () => undefined,
68
+ message: { list: () => [], invalidate: () => { }, sync: async () => { } },
69
+ }), /pagination publication API is unavailable/);
70
+ });
71
+ //# sourceMappingURL=tui-reload.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-reload.test.js","sourceRoot":"","sources":["../../../src/opencode/session-pruning/tui-reload.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport { test } from \"node:test\"\nimport { spawnSync } from \"node:child_process\"\nimport { fileURLToPath } from \"node:url\"\nimport { createPruningReload } from \"./tui-reload\"\n\ntest(\"TUI companion coalesces invalidations and rereads after a missed-event reconnect\", async () => {\n const invalidated: string[] = []\n const read: string[] = []\n let release!: () => void\n const pending = new Promise<void>(resolve => { release = resolve })\n const reload = createPruningReload({\n list: () => [{ id: \"s\" }, { id: \"unloaded\" }],\n get: id => id === \"s\" ? { location: { directory: \"/work\" } } : undefined,\n message: {\n list: id => id === \"s\" ? [{}] : [],\n loading: () => false,\n loadMore: async () => assert.fail(\"unexpected pagination\"),\n invalidate: id => { invalidated.push(id) },\n sync: async id => { read.push(id); await pending },\n },\n })\n const event = { type: \"rpc.codenomad.session-pruning.pruned\", location: { directory: \"/work\" }, data: { sessionID: \"s\", messageID: \"m\", revision: \"a\".repeat(64) } }\n reload.event(event); reload.event(event)\n assert.deepEqual(read, [\"s\"])\n release()\n await new Promise(resolve => setImmediate(resolve))\n assert.deepEqual(read, [\"s\", \"s\"])\n reload.event({ type: \"server.connected\" })\n await new Promise(resolve => setImmediate(resolve))\n assert.deepEqual(read, [\"s\", \"s\", \"s\"])\n assert(invalidated.includes(\"unloaded\"))\n reload.dispose()\n reload.event(event)\n assert.equal(read.length, 3)\n})\n\ntest(\"TUI companion rejects foreign locations, malformed events and unknown sessions\", () => {\n const reload = createPruningReload({\n list: () => [], get: id => id === \"s\" ? { location: { directory: \"/work\" } } : undefined,\n message: {\n list: () => [], loading: () => false, loadMore: async () => assert.fail(\"unexpected pagination\"),\n invalidate: () => assert.fail(\"unexpected invalidation\"), sync: async () => assert.fail(\"unexpected read\"),\n },\n })\n reload.event({ type: \"rpc.other.pruned\", data: {} })\n reload.event({ type: \"rpc.codenomad.session-pruning.pruned\", data: {} })\n for (const sessionID of [\"s\", \"other\"]) reload.event({ type: \"rpc.codenomad.session-pruning.pruned\", location: { directory: \"/elsewhere\" }, data: { sessionID, messageID: \"m\", revision: \"a\".repeat(64) } })\n reload.dispose()\n})\n\ntest(\"TUI reload regressions against the installed published Solid cache\", () => {\n // Keep Solid's browser conditions out of the server test process. Resolve the\n // native client from the pinned plugin dependency, not a checkout, global\n // install, copied cache implementation, or a network install during tests.\n const env = { ...process.env }\n delete env.NODE_TEST_CONTEXT\n const result = spawnSync(process.execPath, [\n \"--conditions=browser\", \"--import\", \"tsx\", \"--test\",\n fileURLToPath(new URL(\"./tui-reload.native.ts\", import.meta.url)),\n ], { encoding: \"utf8\", timeout: 30_000, env })\n assert.ifError(result.error)\n assert.equal(result.status, 0, `${result.stdout}\\n${result.stderr}`)\n assert.match(result.stdout, /control: invalidate\\/sync/, \"The child must actually execute its native-cache tests\")\n})\n\ntest(\"TUI companion refuses a cache without public pagination coordination\", () => {\n assert.throws(() => createPruningReload({\n list: () => [], get: () => undefined,\n message: { list: () => [], invalidate: () => {}, sync: async () => {} },\n }), /pagination publication API is unavailable/)\n})\n"]}