@cortexkit/opencode-magic-context 0.46.1 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/auto-search-worker.js +115 -0
  2. package/dist/config/index.d.ts.map +1 -1
  3. package/dist/config/live-run-config.d.ts.map +1 -1
  4. package/dist/config/migrate-config-location.d.ts.map +1 -1
  5. package/dist/config/schema/magic-context.d.ts +11 -3
  6. package/dist/config/schema/magic-context.d.ts.map +1 -1
  7. package/dist/{conflict-warning-hook-7fj2b9jw.js → conflict-warning-hook-z44ynfz7.js} +5 -4
  8. package/dist/{context-sh2m2vde.js → context-wq4ffwyb.js} +108 -23
  9. package/dist/{derive-budgets-7nj7r1rv.js → derive-budgets-ke12jnnh.js} +1 -1
  10. package/dist/embedding-worker.js +2 -2
  11. package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
  12. package/dist/features/magic-context/overflow-detection.d.ts +2 -0
  13. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  14. package/dist/features/magic-context/project-embedding-registry.d.ts +7 -0
  15. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  16. package/dist/features/magic-context/search.d.ts +2 -0
  17. package/dist/features/magic-context/search.d.ts.map +1 -1
  18. package/dist/features/magic-context/session-project-storage.d.ts +8 -2
  19. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-historian-runs.d.ts +2 -5
  21. package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage-meta-persisted.d.ts +12 -0
  23. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  25. package/dist/features/magic-context/storage-tags.d.ts +7 -2
  26. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  27. package/dist/features/magic-context/storage.d.ts +1 -0
  28. package/dist/features/magic-context/storage.d.ts.map +1 -1
  29. package/dist/features/magic-context/transform-decision-log.d.ts +3 -1
  30. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/active-anthropic-turn.d.ts +8 -0
  32. package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -0
  33. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts +2 -0
  35. package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts.map +1 -0
  36. package/dist/hooks/magic-context/auto-search-deadline.d.ts +7 -0
  37. package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts +3 -0
  39. package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts.map +1 -0
  40. package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts +2 -0
  41. package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts.map +1 -0
  42. package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts +2 -0
  43. package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts.map +1 -0
  44. package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts +4 -0
  45. package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts.map +1 -0
  46. package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts +3 -0
  47. package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts.map +1 -0
  48. package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
  49. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts +4 -0
  51. package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts.map +1 -0
  52. package/dist/hooks/magic-context/auto-search-worker-client.d.ts +69 -0
  53. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -0
  54. package/dist/hooks/magic-context/auto-search-worker.d.ts +2 -0
  55. package/dist/hooks/magic-context/auto-search-worker.d.ts.map +1 -0
  56. package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/compartment-runner-types.d.ts +3 -5
  58. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner.d.ts +2 -1
  60. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/compartment-trigger.d.ts +3 -1
  62. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/event-handler.d.ts +1 -1
  64. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
  68. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/historian-drain-gate.d.ts +11 -0
  71. package/dist/hooks/magic-context/historian-drain-gate.d.ts.map +1 -0
  72. package/dist/hooks/magic-context/hook.d.ts +2 -1
  73. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/latest-assistant-turn.d.ts +25 -0
  75. package/dist/hooks/magic-context/latest-assistant-turn.d.ts.map +1 -0
  76. package/dist/hooks/magic-context/latest-thinking-recovery.d.ts +61 -0
  77. package/dist/hooks/magic-context/latest-thinking-recovery.d.ts.map +1 -0
  78. package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/opencode-proven-floor.d.ts +6 -0
  80. package/dist/hooks/magic-context/opencode-proven-floor.d.ts.map +1 -0
  81. package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
  82. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  83. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  84. package/dist/hooks/magic-context/reasoning-budget-status.d.ts +15 -0
  85. package/dist/hooks/magic-context/reasoning-budget-status.d.ts.map +1 -0
  86. package/dist/hooks/magic-context/reasoning-budget.d.ts +40 -0
  87. package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -0
  88. package/dist/hooks/magic-context/reasoning-removal.d.ts +6 -6
  89. package/dist/hooks/magic-context/reasoning-removal.d.ts.map +1 -1
  90. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  91. package/dist/hooks/magic-context/sentinel.d.ts +4 -0
  92. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  93. package/dist/hooks/magic-context/strip-content.d.ts +7 -3
  94. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  95. package/dist/hooks/magic-context/tag-messages.d.ts +13 -0
  96. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  97. package/dist/hooks/magic-context/tool-drop-target.d.ts +5 -0
  98. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  99. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -28
  100. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  101. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +28 -6
  102. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/transform.d.ts +3 -1
  104. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  105. package/dist/{index-0d3g3461.js → index-0c82743p.js} +2 -1
  106. package/dist/{index-5p8jdswx.js → index-5a2r0p9f.js} +3 -3
  107. package/dist/{index-sjthx78w.js → index-6d9e1m08.js} +4647 -11000
  108. package/dist/{index-majxda46.js → index-6wkcswft.js} +1 -1
  109. package/dist/index-92bp57hg.js +2063 -0
  110. package/dist/{index-sfnj6h8h.js → index-9pk9exrb.js} +3 -3
  111. package/dist/{index-7qw742ev.js → index-adwfkpex.js} +5 -24
  112. package/dist/{index-3h0ybwah.js → index-bb36vgr3.js} +80 -12
  113. package/dist/{index-pkbcrdqs.js → index-cwf6s3ay.js} +1072 -2422
  114. package/dist/index-fnn5pjtt.js +6554 -0
  115. package/dist/{index-sgthv3f7.js → index-gg8j07zz.js} +1 -1
  116. package/dist/{index-8x2v7gg9.js → index-m5fe6jch.js} +7 -3
  117. package/dist/{v2/server-g0fjrkta.js → index-rrbbspwh.js} +42 -27
  118. package/dist/index-rrrsepyw.js +3464 -0
  119. package/dist/{index-hpecdzec.js → index-w2t4ah5j.js} +948 -501
  120. package/dist/{v2/server-fg34g6a5.js → index-wx8a6bw3.js} +48 -8
  121. package/dist/{index-r5hmbx3t.js → index-x3kwrmqk.js} +146 -3571
  122. package/dist/index-yr3zj7e7.js +23 -0
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +624 -65
  125. package/dist/{message-time-backfill-68gj6t3e.js → message-time-backfill-r5epkzj3.js} +1 -1
  126. package/dist/migration-worker.js +7 -5
  127. package/dist/plugin/hooks/create-session-hooks.d.ts +2 -1
  128. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  129. package/dist/plugin/host-stall-profiler-report.d.ts +67 -0
  130. package/dist/plugin/host-stall-profiler-report.d.ts.map +1 -0
  131. package/dist/plugin/host-stall-profiler.d.ts +96 -0
  132. package/dist/plugin/host-stall-profiler.d.ts.map +1 -0
  133. package/dist/plugin/messages-transform.d.ts +21 -6
  134. package/dist/plugin/messages-transform.d.ts.map +1 -1
  135. package/dist/{read-session-chunk-egz7r68d.js → read-session-chunk-4jey7hpt.js} +4 -4
  136. package/dist/{recomp-orchestrator-y15j0rb9.js → recomp-orchestrator-rervg6q4.js} +13 -10
  137. package/dist/{send-session-notification-dyw42dtr.js → send-session-notification-0m68ms3n.js} +4 -3
  138. package/dist/shared/auto-search-lifecycle.d.ts +7 -0
  139. package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
  140. package/dist/shared/historian-tool-expansions.d.ts +1 -1
  141. package/dist/shared/historian-tool-expansions.d.ts.map +1 -1
  142. package/dist/shared/historian-tool-template.d.ts +2 -2
  143. package/dist/shared/historian-tool-template.d.ts.map +1 -1
  144. package/dist/shared/sqlite.d.ts +16 -1
  145. package/dist/shared/sqlite.d.ts.map +1 -1
  146. package/dist/shared/status-view.d.ts.map +1 -1
  147. package/dist/shared/window-geometry.d.ts +4 -0
  148. package/dist/shared/window-geometry.d.ts.map +1 -1
  149. package/dist/tools/ctx-expand/render.d.ts.map +1 -1
  150. package/dist/{transform-postprocess-phase-bqhz6vdr.js → transform-postprocess-phase-z5hzpsxv.js} +13 -10
  151. package/dist/v2/auto-search-worker.js +115 -0
  152. package/dist/v2/{context-zecmzk5s.js → context-14v1hr8b.js} +3783 -4668
  153. package/dist/v2/{derive-budgets-kdgp4s3r.js → derive-budgets-nc9avxz8.js} +2 -2
  154. package/dist/v2/embedding-worker.js +3 -3
  155. package/dist/v2/hidden-completion.d.ts.map +1 -1
  156. package/dist/v2/hooks/context.d.ts.map +1 -1
  157. package/dist/v2/{message-time-backfill-r6qkem1x.js → message-time-backfill-1f9tzvcs.js} +1 -1
  158. package/dist/v2/migration-worker.js +7 -5
  159. package/dist/v2/{read-session-chunk-8vjp6581.js → read-session-chunk-gyyv7w2r.js} +4 -4
  160. package/dist/v2/{recomp-orchestrator-8ejxvrzz.js → recomp-orchestrator-42vjba7p.js} +17 -14
  161. package/dist/v2/{send-session-notification-qs0j78v6.js → send-session-notification-c0wtscea.js} +5 -4
  162. package/dist/v2/server-3trjk1hx.js +6360 -0
  163. package/dist/v2/{server-e29d7p7c.js → server-50hvkg3v.js} +7 -3
  164. package/dist/v2/{server-y8maxeft.js → server-cpepxy2q.js} +2 -1
  165. package/dist/v2/server-d0sraw7m.js +3403 -0
  166. package/dist/v2/server-e0c1b7a7.js +2063 -0
  167. package/dist/v2/{server-mcfawjgc.js → server-efhwzx05.js} +5 -24
  168. package/dist/v2/{server-3n4q9xme.js → server-fcdehk1s.js} +1 -1
  169. package/dist/{index-q4yp4b52.js → v2/server-fq7kq32k.js} +42 -27
  170. package/dist/v2/{server-jc7y02q0.js → server-nf82q6pt.js} +1117 -7290
  171. package/dist/v2/{server-frbm99q5.js → server-njt3np1k.js} +99 -3465
  172. package/dist/v2/{server-qp5hx1ad.js → server-nm9n3aqq.js} +107 -12
  173. package/dist/v2/{server-c5gz8wph.js → server-rz514f14.js} +7 -2
  174. package/dist/v2/{server-wvjvfhck.js → server-vd4e7174.js} +1 -1
  175. package/dist/{index-fg34g6a5.js → v2/server-wx8a6bw3.js} +48 -8
  176. package/dist/v2/{server-xa4xe3rv.js → server-y10vbq6y.js} +1 -1
  177. package/dist/v2/server-yr3zj7e7.js +23 -0
  178. package/dist/v2/server.js +5 -5
  179. package/dist/v2/{wrapup-orchestrator-0snhvwcv.js → wrapup-orchestrator-nbz854vc.js} +16 -13
  180. package/dist/wrapup-orchestrator-b2dztrar.js +29 -0
  181. package/package.json +3 -3
  182. package/src/config/index.ts +6 -0
  183. package/src/config/live-run-config.test.ts +37 -1
  184. package/src/config/live-run-config.ts +28 -0
  185. package/src/config/migrate-config-location.ts +10 -3
  186. package/src/config/schema/magic-context.test.ts +4 -3
  187. package/src/config/schema/magic-context.ts +18 -6
  188. package/src/shared/auto-search-lifecycle.ts +27 -0
  189. package/src/shared/historian-tool-defaults.json +6 -6
  190. package/src/shared/historian-tool-expansions.ts +65 -8
  191. package/src/shared/historian-tool-preview-defaults.json +13 -0
  192. package/src/shared/historian-tool-template.ts +48 -14
  193. package/src/shared/sqlite-640-review-r2.test.ts +136 -0
  194. package/src/shared/sqlite-640-review.test.ts +183 -0
  195. package/src/shared/sqlite-acquisition-routing.test.ts +18 -0
  196. package/src/shared/sqlite.ts +84 -14
  197. package/src/shared/status-view.test.ts +6 -1
  198. package/src/shared/status-view.ts +15 -1
  199. package/src/shared/tag-transcript.ts +6 -0
  200. package/src/shared/window-geometry.ts +10 -0
  201. package/dist/wrapup-orchestrator-39amhv9s.js +0 -26
  202. /package/dist/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
  203. /package/dist/v2/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
@@ -1,314 +1,31 @@
1
1
  import {
2
- sanitizeDiagnosticText,
3
- importPluginModule,
4
- writeForwardedLogLine,
2
+ getMagicContextStorageDir
3
+ } from "./index-q6hef7fh.js";
4
+ import {
5
5
  log
6
6
  } from "./index-hybnrwq0.js";
7
- import {
8
- harnessOwnsOpenCodeStore,
9
- getMagicContextStorageDir,
10
- getLegacyOpenCodeMagicContextStorageDir
11
- } from "./index-q6hef7fh.js";
12
7
  import {
13
8
  ensureColumn,
14
9
  healAllNullColumns,
15
- installV95PerfSchema,
16
- setDatabase,
17
- loadToolDefinitionMeasurements
10
+ installV95PerfSchema
18
11
  } from "./index-5gq3b2ya.js";
19
12
  import {
20
13
  resolveOpenCodeDbPath,
21
- hasV1MessageTables,
22
14
  openCodeDbPathExists,
23
- getOpenCodeDbProbeDescriptions,
24
- closeQuietly
15
+ getOpenCodeDbProbeDescriptions
25
16
  } from "./index-b35pxds2.js";
26
17
  import {
27
- registerSlowWriteReporter,
28
- registerSqliteDiagnosticSink,
29
- detectSqliteRuntime,
30
18
  Database,
31
19
  withSqliteBackgroundWriter,
32
- withoutSqliteTransformPass,
33
- isTransientSqliteError,
34
- withPrivilegedWriter
35
- } from "./index-fg34g6a5.js";
20
+ withoutSqliteTransformPass
21
+ } from "./index-wx8a6bw3.js";
36
22
  import {
37
- getErrorMessage
38
- } from "./index-y8v6yerm.js";
39
- import {
40
- shouldEnforcePrivateStoragePermissions,
41
- ensureStorageDirectorySync,
42
- writeStorageFileSync,
43
- writeStorageFileAtomicSync,
44
- copyStorageFileSync,
45
- copyStorageTreeSync,
46
- tightenStorageTreeSync
23
+ writeStorageFileAtomicSync
47
24
  } from "./index-1bc61ans.js";
48
25
  import {
49
26
  logSlowWriteTransaction
50
27
  } from "./index-sj29b3z2.js";
51
28
 
52
- // src/features/magic-context/fail-closed-block.ts
53
- var FAIL_CLOSED_DOCTOR_COMMAND = "npx @cortexkit/magic-context@latest doctor";
54
- var FAIL_CLOSED_REPROBE_EVERY_N = 5;
55
- function attachFailClosedBlockingProcessEvidence(process2, evidence) {
56
- Object.defineProperties(process2, {
57
- startTime: { configurable: true, value: evidence.startTime },
58
- commandLine: { configurable: true, value: evidence.commandLine }
59
- });
60
- return process2;
61
- }
62
-
63
- class FailClosedBlockingError extends Error {
64
- code = "FAIL_CLOSED_BLOCKING";
65
- reason;
66
- constructor(message, reason, options) {
67
- super(message, options);
68
- this.name = "FailClosedBlockingError";
69
- this.reason = reason;
70
- }
71
- }
72
- var OPENCODE_INTERNAL_AGENT_NAMES = new Set(["title", "summary", "compaction"]);
73
- function isMagicContextHiddenAgentName(agent) {
74
- if (agent === "smart-note-compiler" || agent.startsWith("smart-note-")) {
75
- return true;
76
- }
77
- if (agent === "historian" || agent.startsWith("historian-"))
78
- return true;
79
- if (agent === "dreamer" || agent.startsWith("dreamer-"))
80
- return true;
81
- return false;
82
- }
83
- var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
84
- var MAX_FORMATTED_COMMAND_LINE_LENGTH = 80;
85
- function normalizeFailClosedProcessKind(process2) {
86
- switch (process2.kind) {
87
- case "OpenCode server":
88
- case "OpenCode instance (TUI/CLI)":
89
- case "Pi":
90
- case "process":
91
- return process2.kind;
92
- }
93
- switch (process2.harness?.trim().toLowerCase()) {
94
- case "opencode server":
95
- return "OpenCode server";
96
- case "opencode instance (tui/cli)":
97
- case "opencode instance":
98
- return "OpenCode instance (TUI/CLI)";
99
- case "pi":
100
- case "pi harness":
101
- case "omp":
102
- return "Pi";
103
- default:
104
- return "process";
105
- }
106
- }
107
- function formatFailClosedProcessStartTime(startTime) {
108
- if (!Number.isFinite(startTime) || startTime <= 0)
109
- return "unverified";
110
- try {
111
- const parts = new Intl.DateTimeFormat("en-US", {
112
- day: "numeric",
113
- hour: "2-digit",
114
- minute: "2-digit",
115
- month: "short",
116
- hour12: false
117
- }).formatToParts(new Date(startTime));
118
- const values = Object.fromEntries(parts.map(({ type, value }) => [type, value]));
119
- if (!values.month || !values.day || !values.hour || !values.minute)
120
- return "unverified";
121
- return `${values.month} ${values.day} ${values.hour}:${values.minute}`;
122
- } catch {
123
- return "unverified";
124
- }
125
- }
126
- function formatFailClosedCommandLine(commandLine) {
127
- if (typeof commandLine !== "string" || commandLine.trim().length === 0)
128
- return "unverified";
129
- try {
130
- const normalized = commandLine.replaceAll("\x00", " ").replace(/\s+/g, " ").trim();
131
- if (normalized.length === 0)
132
- return "unverified";
133
- const sanitized = sanitizeDiagnosticText(normalized).trim();
134
- if (sanitized.length === 0)
135
- return "unverified";
136
- return sanitized.length > MAX_FORMATTED_COMMAND_LINE_LENGTH ? `${sanitized.slice(0, MAX_FORMATTED_COMMAND_LINE_LENGTH - 3)}...` : sanitized;
137
- } catch {
138
- return "unverified";
139
- }
140
- }
141
- function formatFailClosedBlockingProcesses(processes) {
142
- const uniqueProcesses = new Map;
143
- for (const process2 of processes) {
144
- if (!Number.isInteger(process2.pid) || process2.pid <= 0)
145
- continue;
146
- const candidate = {
147
- kind: normalizeFailClosedProcessKind(process2),
148
- pid: process2.pid,
149
- startTime: process2.startTime,
150
- commandLine: process2.commandLine
151
- };
152
- const previous = uniqueProcesses.get(process2.pid);
153
- if (!previous || previous.kind === "process" && candidate.kind !== "process") {
154
- uniqueProcesses.set(process2.pid, candidate);
155
- } else {
156
- uniqueProcesses.set(process2.pid, {
157
- ...previous,
158
- startTime: previous.startTime ?? candidate.startTime,
159
- commandLine: previous.commandLine ?? candidate.commandLine
160
- });
161
- }
162
- }
163
- const entries = [...uniqueProcesses.values()];
164
- const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
165
- const rendered = visible.map(({ kind, pid }) => `${kind} (PID ${pid})`);
166
- const omitted = entries.length - visible.length;
167
- if (omitted > 0)
168
- rendered.push(`${omitted} more blocking process(es)`);
169
- if (rendered.length === 0)
170
- return "a live process";
171
- const summary = rendered.length === 1 ? rendered[0] : `${rendered.slice(0, -1).join(", ")}, and ${rendered.at(-1)}`;
172
- const evidence = entries.map(({ kind, pid, startTime, commandLine }) => `- PID ${pid}: ${kind}, started ${formatFailClosedProcessStartTime(startTime)}, cmd: ${formatFailClosedCommandLine(commandLine)}`).join(`
173
- `);
174
- return `${summary}
175
- Blocking process evidence:
176
- ${evidence}`;
177
- }
178
- function formatFailClosedBlockingMessage(reason) {
179
- if (reason.kind === "migration_guard") {
180
- if (reason.unreadableFile) {
181
- const arm = reason.unreadableArm ?? "io";
182
- const recovery = arm === "io" ? `If none of these processes are running, it is safe to delete ${reason.unreadableFile} and retry.` : `The file may be a recent incomplete write; retry after the file is older than the ten-minute grace window, or stop the relevant process before deleting it.`;
183
- return [
184
- `Magic Context cannot migrate the shared database because RPC discovery file ${reason.unreadableFile} is uncertain (${arm} arm), so the absence of a live process cannot be proven.`,
185
- recovery,
186
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
187
- ].join(" ");
188
- }
189
- return [
190
- `Magic Context cannot migrate the shared database (one database serves every project on this machine) because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
191
- `The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
192
- "Restart the blocking process — even one from a different project — and it will pick up the new build and migrate on start; or shut it down and retry.",
193
- "If that host loads a pinned or cached older Magic Context build, a restart alone is not enough: stop it or update its Magic Context build, then start this host once so it migrates the database with nothing older attached.",
194
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
195
- ].join(" ");
196
- }
197
- if (reason.kind === "schema_fence") {
198
- return [
199
- `Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (upstream migration lane v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
200
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
201
- ].join(" ");
202
- }
203
- const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
204
- return [
205
- `Magic Context cannot operate: persistent storage failed (${cause}).`,
206
- "The plugin will not silently degrade to native compaction while enabled.",
207
- `Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
208
- ].join(" ");
209
- }
210
- function formatFailClosedBlockingSummary(reason) {
211
- if (reason.kind === "migration_guard") {
212
- if (reason.unreadableFile) {
213
- return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion}: RPC discovery file ${reason.unreadableFile} could not be read, so an older host may still be attached.`;
214
- }
215
- const blockers = reason.blockingProcesses.filter((process2) => Number.isInteger(process2.pid) && process2.pid > 0).map((process2) => `${normalizeFailClosedProcessKind(process2)} (PID ${process2.pid})`);
216
- const named = blockers.length > 0 ? blockers.join(", ") : "a live process";
217
- return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion} while ${named} may still run an older Magic Context build. Stop or update that host, then start this one once.`;
218
- }
219
- if (reason.kind === "schema_fence") {
220
- return `Magic Context cannot operate: the shared database is at v${reason.persistedVersion}, newer than this build supports (v${reason.supportedVersion}). Update and restart this host.`;
221
- }
222
- const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
223
- return `Magic Context cannot operate: persistent storage failed (${cause}).`;
224
- }
225
- function createFailClosedBlockingError(reason, options) {
226
- return new FailClosedBlockingError(formatFailClosedBlockingMessage(reason), reason, options);
227
- }
228
- function isFailClosedBlockingError(error) {
229
- return error instanceof FailClosedBlockingError || typeof error === "object" && error !== null && error.name === "FailClosedBlockingError" && error.code === "FAIL_CLOSED_BLOCKING";
230
- }
231
- function shouldBypassFailClosedBlock(input) {
232
- if (input.isPiSubagentEnv === true)
233
- return true;
234
- if (input.isInternalChildSession === true)
235
- return true;
236
- const agent = typeof input.agent === "string" ? input.agent.trim() : "";
237
- if (agent.length === 0)
238
- return false;
239
- if (OPENCODE_INTERNAL_AGENT_NAMES.has(agent))
240
- return true;
241
- if (isMagicContextHiddenAgentName(agent))
242
- return true;
243
- return false;
244
- }
245
- function resolveAgentNameFromMessages(messages) {
246
- for (let i = messages.length - 1;i >= 0; i -= 1) {
247
- const info = messages[i]?.info;
248
- if (!info || typeof info !== "object")
249
- continue;
250
- const agent = info.agent;
251
- if (typeof agent === "string" && agent.length > 0)
252
- return agent;
253
- }
254
- return;
255
- }
256
- function createFailClosedController(options) {
257
- const reprobeEveryN = Math.max(1, options?.reprobeEveryN ?? FAIL_CLOSED_REPROBE_EVERY_N);
258
- let reason = null;
259
- let blockedPassCount = 0;
260
- return {
261
- arm(next) {
262
- reason = next;
263
- blockedPassCount = 0;
264
- },
265
- clear() {
266
- reason = null;
267
- blockedPassCount = 0;
268
- },
269
- isArmed() {
270
- return reason !== null;
271
- },
272
- getReason() {
273
- return reason;
274
- },
275
- async enforce(input) {
276
- if (!reason)
277
- return;
278
- if (!input.blockingEnabled)
279
- return;
280
- if (input.exempt)
281
- return;
282
- blockedPassCount += 1;
283
- const shouldReprobe = typeof input.tryReopen === "function" && (blockedPassCount === 1 || blockedPassCount % reprobeEveryN === 0);
284
- if (shouldReprobe) {
285
- try {
286
- const healed = await input.tryReopen?.();
287
- if (healed) {
288
- reason = null;
289
- blockedPassCount = 0;
290
- return;
291
- }
292
- } catch {}
293
- }
294
- const blockedReason = reason;
295
- if (!blockedReason)
296
- return;
297
- throw createFailClosedBlockingError(blockedReason);
298
- }
299
- };
300
- }
301
- var lastHookInitFailure = null;
302
- function recordHookInitFailure(failure) {
303
- lastHookInitFailure = failure;
304
- }
305
- function clearHookInitFailure() {
306
- lastHookInitFailure = null;
307
- }
308
- function getLastHookInitFailure() {
309
- return lastHookInitFailure;
310
- }
311
-
312
29
  // src/features/magic-context/memory/project-identity.ts
313
30
  import { execFileSync } from "node:child_process";
314
31
  import { createHash as createHash2 } from "node:crypto";
@@ -4466,3308 +4183,166 @@ async function runMigrationsWithRetry(db, options = {}) {
4466
4183
  }
4467
4184
  }
4468
4185
 
4469
- // src/features/magic-context/storage-db.ts
4470
- import {
4471
- existsSync as existsSync3,
4472
- readdirSync,
4473
- readFileSync as readFileSync3,
4474
- rmdirSync,
4475
- statSync as statSync2,
4476
- unlinkSync
4477
- } from "node:fs";
4478
- import { basename, dirname, join as join2, resolve } from "node:path";
4479
-
4480
- // src/shared/rpc-utils.ts
4481
- import { execFile, execFileSync as execFileSync2 } from "node:child_process";
4186
+ // src/features/magic-context/message-fts-rowid-map.ts
4482
4187
  import { createHash as createHash5 } from "node:crypto";
4483
- import { readFileSync as readFileSync2 } from "node:fs";
4484
- import { join } from "node:path";
4485
-
4486
- // src/shared/pi-executable.ts
4487
- var PI_IMAGE_NAMES = new Set(["pi", "pi.cmd", "omp", "oh-my-pi"]);
4488
- function piHarnessKindFromExecutable(value) {
4489
- const executable = (value ?? "").trim().replace(/^['"]|['"]$/g, "").replaceAll("\\", "/").split("/").at(-1)?.toLowerCase().replace(/\.(?:exe|cmd)$/, "");
4490
- if (!executable || !PI_IMAGE_NAMES.has(executable))
4491
- return;
4492
- return executable === "pi" ? "pi" : "omp";
4188
+ var MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE = 100;
4189
+ var BACKFILL_STATE_ID = 1;
4190
+ var EMPTY_INDEX_CONTENT_HASH = createHash5("sha256").update("").digest("hex");
4191
+ var upsertMapStatements = new WeakMap;
4192
+ var rangeReadyStatements = new WeakMap;
4193
+ var updateTimeStatements = new WeakMap;
4194
+ var activeBackfills = new WeakMap;
4195
+ function getUpsertMapStatement(db) {
4196
+ let statement = upsertMapStatements.get(db);
4197
+ if (!statement) {
4198
+ statement = db.prepare(`INSERT INTO message_fts_rowid_map
4199
+ (session_id, message_ordinal, fts_rowid, message_time_ms)
4200
+ VALUES (?, ?, ?, ?)
4201
+ ON CONFLICT(session_id, message_ordinal) DO UPDATE SET
4202
+ fts_rowid = excluded.fts_rowid,
4203
+ message_time_ms = COALESCE(
4204
+ excluded.message_time_ms,
4205
+ message_fts_rowid_map.message_time_ms
4206
+ )`);
4207
+ upsertMapStatements.set(db, statement);
4208
+ }
4209
+ return statement;
4493
4210
  }
4494
-
4495
- // src/shared/rpc-utils.ts
4496
- var ownRpcServerInstanceIds = new Set;
4497
- function registerOwnRpcServerInstance(instanceId) {
4498
- ownRpcServerInstanceIds.add(instanceId);
4499
- return () => {
4500
- ownRpcServerInstanceIds.delete(instanceId);
4211
+ function getBackfillState(db) {
4212
+ const row = db.prepare(`SELECT watermark_rowid AS watermarkRowid, completed
4213
+ FROM message_fts_rowid_map_backfill_state
4214
+ WHERE id = ?`).get(BACKFILL_STATE_ID);
4215
+ return {
4216
+ processed: 0,
4217
+ watermarkRowid: typeof row?.watermarkRowid === "number" && Number.isSafeInteger(row.watermarkRowid) ? row.watermarkRowid : 0,
4218
+ completed: row?.completed === 1
4501
4219
  };
4502
4220
  }
4503
- function isOwnRpcServerRecord(record) {
4504
- return record.pid === process.pid && record.instance_id !== undefined && ownRpcServerInstanceIds.has(record.instance_id);
4505
- }
4506
- function projectHash(directory) {
4507
- return createHash5("sha256").update(canonicalProjectDirectory(directory)).digest("hex").slice(0, 16);
4508
- }
4509
- function rpcPortDir(storageDir, directory) {
4510
- return join(storageDir, "rpc", projectHash(directory));
4511
- }
4512
- function rpcPortFilePath(storageDir, directory, pid = process.pid, instanceId) {
4513
- const suffix = instanceId ? `-${instanceId}` : "";
4514
- return join(rpcPortDir(storageDir, directory), `port-${pid}${suffix}.json`);
4515
- }
4516
- function isPidAlive(pid) {
4517
- if (!Number.isInteger(pid) || pid <= 0)
4518
- return "dead";
4519
- if (rpcIdentityPlatform === "win32")
4520
- return readWindowsProcess(pid).state;
4521
- try {
4522
- rpcIdentityProcessKill(pid, 0);
4523
- return "alive";
4524
- } catch (error) {
4525
- return error.code === "ESRCH" ? "dead" : "inconclusive";
4526
- }
4527
- }
4528
- var RPC_IDENTITY_SKEW_TOLERANCE_MS = 120000;
4529
- var LINUX_CLOCK_TICKS_PER_SECOND = 100;
4530
- var PS_PROBE_TIMEOUT_MS = 1000;
4531
- var WINDOWS_CIM_PROBE_TIMEOUT_MS = 5000;
4532
- var WINDOWS_PROCESS_SNAPSHOT_TTL_MS = 2000;
4533
- var MAX_ANCESTOR_WALK_DEPTH = 16;
4534
- var OPEN_CODE_COMMAND_MARKERS = ["opencode", "node", "bun", "electron"];
4535
- var WINDOWS_CIM_COMMAND = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine,@{Name='CreationDate';Expression={if ($_.CreationDate) { $_.CreationDate.ToUniversalTime().ToString('o') } else { $null }}} | ConvertTo-Json -Compress";
4536
- var PROCESS_LIST_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
4537
- var PI_HARNESS_ARC_MARKERS = [
4538
- "pi-coding-agent",
4539
- "oh-my-pi",
4540
- "@oh-my-pi",
4541
- "cljs/dist",
4542
- "dist/bundle/cli"
4543
- ];
4544
- var rpcIdentityReadFileSync = readFileSync2;
4545
- var rpcIdentityExecFileSync = execFileSync2;
4546
- var rpcIdentityProcessKill = process.kill;
4547
- var rpcProcessListExecFileSync = execFileSync2;
4548
- var rpcIdentityPlatform = process.platform;
4549
- var rpcIdentityNowMs = () => Date.now();
4550
- function parseLinuxProcessStartTime(statContent, uptimeContent) {
4551
- const closingCommandName = statContent.lastIndexOf(")");
4552
- if (closingCommandName < 0)
4553
- return null;
4554
- const statFields = statContent.slice(closingCommandName + 1).trim().split(/\s+/);
4555
- const startTimeTicks = Number(statFields[19]);
4556
- const uptimeSeconds = Number(uptimeContent.trim().split(/\s+/)[0]);
4557
- if (!Number.isFinite(startTimeTicks) || startTimeTicks < 0 || !Number.isFinite(uptimeSeconds) || uptimeSeconds < 0) {
4558
- return null;
4221
+ function recordMessageFtsRowid(db, sessionId, messageOrdinal, ftsRowid, messageTimeMs = null) {
4222
+ const numericRowid = Number(ftsRowid);
4223
+ if (!Number.isSafeInteger(numericRowid) || numericRowid <= 0) {
4224
+ throw new Error(`invalid message FTS rowid: ${String(ftsRowid)}`);
4559
4225
  }
4560
- const processStartTime = rpcIdentityNowMs() - uptimeSeconds * 1000 + startTimeTicks / LINUX_CLOCK_TICKS_PER_SECOND * 1000;
4561
- return Number.isFinite(processStartTime) ? processStartTime : null;
4226
+ const normalizedTime = typeof messageTimeMs === "number" && Number.isSafeInteger(messageTimeMs) && messageTimeMs >= 0 ? messageTimeMs : null;
4227
+ getUpsertMapStatement(db).run(sessionId, messageOrdinal, numericRowid, normalizedTime);
4562
4228
  }
4563
- function readLinuxProcessStartTime(pid) {
4564
- try {
4565
- const statContent = String(rpcIdentityReadFileSync(`/proc/${pid}/stat`, "utf8"));
4566
- const uptimeContent = String(rpcIdentityReadFileSync("/proc/uptime", "utf8"));
4567
- return parseLinuxProcessStartTime(statContent, uptimeContent);
4568
- } catch {
4569
- return null;
4229
+ function recordIndexedMessageTime(db, sessionId, messageOrdinal, messageTimeMs) {
4230
+ if (typeof messageTimeMs !== "number" || !Number.isSafeInteger(messageTimeMs) || messageTimeMs < 0) {
4231
+ return;
4570
4232
  }
4571
- }
4572
- function readPsProcessStartTime(pid) {
4573
- try {
4574
- const output = rpcIdentityExecFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
4575
- encoding: "utf8",
4576
- timeout: PS_PROBE_TIMEOUT_MS,
4577
- stdio: ["ignore", "pipe", "pipe"],
4578
- windowsHide: true
4579
- });
4580
- const processStartTime = Date.parse(String(output).trim());
4581
- return Number.isFinite(processStartTime) ? processStartTime : null;
4582
- } catch {
4583
- return null;
4233
+ let statement = updateTimeStatements.get(db);
4234
+ if (!statement) {
4235
+ statement = db.prepare(`UPDATE message_fts_rowid_map
4236
+ SET message_time_ms = ?
4237
+ WHERE session_id = ? AND message_ordinal = ?
4238
+ AND message_time_ms IS NULL`);
4239
+ updateTimeStatements.set(db, statement);
4584
4240
  }
4241
+ statement.run(messageTimeMs, sessionId, messageOrdinal);
4585
4242
  }
4586
- function readProcessStartTime(pid) {
4587
- if (!Number.isInteger(pid) || pid <= 0)
4588
- return null;
4589
- return rpcIdentityPlatform === "linux" ? readLinuxProcessStartTime(pid) : rpcIdentityPlatform === "win32" ? readWindowsProcessStartTime(pid) : readPsProcessStartTime(pid);
4590
- }
4591
- function readProcessProbeEvidence(pid) {
4592
- return {
4593
- startTime: readProcessStartTime(pid),
4594
- commandLine: readProcessCommand(pid)
4243
+ function backfillMessageFtsRowidMapBatch(db, batchSize = MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE) {
4244
+ const boundedBatchSize = Number.isFinite(batchSize) ? Math.min(MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE, Math.max(1, Math.floor(batchSize))) : MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE;
4245
+ let progress = {
4246
+ processed: 0,
4247
+ watermarkRowid: 0,
4248
+ completed: false
4595
4249
  };
4596
- }
4597
- function readCachedWindowsImageName(pid) {
4598
- return rpcIdentityPlatform === "win32" ? windowsProcessFactsCache?.get(pid)?.imageName ?? null : null;
4599
- }
4600
- var windowsProcessFactsCache = null;
4601
- var windowsProcessSnapshotCache = null;
4602
- function rememberWindowsProcessFacts(facts) {
4603
- windowsProcessFactsCache = new Map(facts.map((fact) => [fact.pid, fact]));
4604
- }
4605
- function parseCsvLine(line) {
4606
- const fields = [];
4607
- let field = "";
4608
- let quoted = false;
4609
- for (let index = 0;index < line.length; index += 1) {
4610
- const character = line[index];
4611
- if (character === '"') {
4612
- if (quoted && line[index + 1] === '"') {
4613
- field += '"';
4614
- index += 1;
4615
- } else {
4616
- quoted = !quoted;
4617
- }
4618
- } else if (character === "," && !quoted) {
4619
- fields.push(field);
4620
- field = "";
4621
- } else {
4622
- field += character;
4250
+ let transactionStartedAt = 0;
4251
+ db.transaction(() => {
4252
+ transactionStartedAt = performance.now();
4253
+ db.prepare(`INSERT OR IGNORE INTO message_fts_rowid_map_backfill_state
4254
+ (id, watermark_rowid, completed) VALUES (?, 0, 0)`).run(BACKFILL_STATE_ID);
4255
+ const state = getBackfillState(db);
4256
+ if (state.completed) {
4257
+ progress = state;
4258
+ return;
4623
4259
  }
4624
- }
4625
- if (quoted)
4626
- return null;
4627
- fields.push(field);
4628
- return fields;
4629
- }
4630
- function parseTasklistOutput(output) {
4631
- const entries = [];
4632
- for (const rawLine of output.split(/\r?\n/)) {
4633
- const line = rawLine.trim();
4634
- if (!line)
4635
- continue;
4636
- const fields = parseCsvLine(line);
4637
- if (!fields)
4638
- continue;
4639
- if (fields[1]?.trim().toLowerCase() === "pid") {
4640
- continue;
4260
+ const rows = db.prepare(`SELECT rowid AS ftsRowid,
4261
+ session_id AS sessionId, message_ordinal AS messageOrdinal
4262
+ FROM message_history_fts WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`).all(state.watermarkRowid, boundedBatchSize);
4263
+ let watermarkRowid = state.watermarkRowid;
4264
+ for (const row of rows) {
4265
+ const ftsRowid = Number(row.ftsRowid);
4266
+ const messageOrdinal = Number(row.messageOrdinal);
4267
+ if (Number.isSafeInteger(ftsRowid) && ftsRowid > watermarkRowid) {
4268
+ watermarkRowid = ftsRowid;
4269
+ }
4270
+ if (typeof row.sessionId === "string" && Number.isSafeInteger(messageOrdinal) && messageOrdinal >= 0 && Number.isSafeInteger(ftsRowid) && ftsRowid > 0) {
4271
+ recordMessageFtsRowid(db, row.sessionId, messageOrdinal, ftsRowid);
4272
+ }
4641
4273
  }
4642
- const pid = Number(fields[1]);
4643
- if (!Number.isInteger(pid) || pid <= 0 || !fields[0])
4644
- continue;
4645
- entries.push({ pid, command: fields[0] });
4646
- }
4647
- return entries;
4274
+ const completed = rows.length < boundedBatchSize;
4275
+ db.prepare(`UPDATE message_fts_rowid_map_backfill_state
4276
+ SET watermark_rowid = ?, completed = ?, updated_at = ?
4277
+ WHERE id = ?`).run(watermarkRowid, completed ? 1 : 0, Date.now(), BACKFILL_STATE_ID);
4278
+ progress = {
4279
+ processed: rows.length,
4280
+ watermarkRowid,
4281
+ completed
4282
+ };
4283
+ }).immediate();
4284
+ logSlowWriteTransaction("message_fts_rowid_backfill", transactionStartedAt);
4285
+ return progress;
4648
4286
  }
4649
- function readWindowsProcess(pid) {
4650
- try {
4651
- const output = rpcIdentityExecFileSync("tasklist", ["/FO", "CSV", "/NH", "/FI", `PID eq ${pid}`], {
4652
- encoding: "utf8",
4653
- timeout: PS_PROBE_TIMEOUT_MS,
4654
- stdio: ["ignore", "pipe", "pipe"],
4655
- windowsHide: true
4656
- });
4657
- const entries = parseTasklistOutput(String(output));
4658
- if (entries === null)
4659
- return { state: "inconclusive" };
4660
- const process2 = entries.find((entry) => entry.pid === pid);
4661
- return process2 ? { state: "alive", command: process2.command } : { state: "dead" };
4662
- } catch {
4663
- return { state: "inconclusive" };
4287
+ async function runMessageFtsRowidMapBackfill(db) {
4288
+ await new Promise((resolve) => setTimeout(resolve, 0));
4289
+ for (;; ) {
4290
+ const progress = backfillMessageFtsRowidMapBatch(db);
4291
+ if (progress.completed)
4292
+ return;
4293
+ await new Promise((resolve) => setTimeout(resolve, 0));
4664
4294
  }
4665
4295
  }
4666
- function readWindowsProcessStartTime(pid) {
4667
- const cached = windowsProcessFactsCache?.get(pid);
4668
- if (cached)
4669
- return cached.startTime;
4670
- const snapshot = tryReadWindowsCimSnapshot(rpcIdentityExecFileSync);
4671
- if (!snapshot)
4672
- return null;
4673
- rememberWindowsProcessFacts(snapshot.facts);
4674
- return windowsProcessFactsCache?.get(pid)?.startTime ?? null;
4296
+ function startMessageFtsRowidMapBackfill(db) {
4297
+ const active = activeBackfills.get(db);
4298
+ if (active)
4299
+ return active;
4300
+ const run = withoutSqliteTransformPass(() => withSqliteBackgroundWriter(() => runMessageFtsRowidMapBackfill(db))).finally(() => {
4301
+ activeBackfills.delete(db);
4302
+ });
4303
+ activeBackfills.set(db, run);
4304
+ return run;
4675
4305
  }
4676
- function readLinuxProcessCommand(pid) {
4677
- try {
4678
- return String(rpcIdentityReadFileSync(`/proc/${pid}/cmdline`, "utf8"));
4679
- } catch {
4680
- return null;
4306
+ function messageFtsOrdinalRangeIsMapped(db, sessionId, startOrdinal, endOrdinal) {
4307
+ if (endOrdinal < startOrdinal)
4308
+ return true;
4309
+ if (getBackfillState(db).completed)
4310
+ return true;
4311
+ let statement = rangeReadyStatements.get(db);
4312
+ if (!statement) {
4313
+ statement = db.prepare(`SELECT COUNT(DISTINCT source.message_ordinal) AS sourceOrdinalCount,
4314
+ COUNT(DISTINCT CASE
4315
+ WHEN source.role IN ('user', 'assistant')
4316
+ AND source.normalized_content_hash != ?
4317
+ THEN source.message_ordinal
4318
+ END) AS expectedMapCount,
4319
+ COUNT(DISTINCT CASE
4320
+ WHEN source.role IN ('user', 'assistant')
4321
+ AND source.normalized_content_hash != ?
4322
+ AND map.fts_rowid IS NOT NULL
4323
+ THEN source.message_ordinal
4324
+ END) AS mappedCount
4325
+ FROM message_history_source AS source
4326
+ LEFT JOIN message_fts_rowid_map AS map
4327
+ ON map.session_id = source.session_id
4328
+ AND map.message_ordinal = source.message_ordinal
4329
+ WHERE source.session_id = ?
4330
+ AND source.message_ordinal BETWEEN ? AND ?`);
4331
+ rangeReadyStatements.set(db, statement);
4681
4332
  }
4333
+ const row = statement.get(EMPTY_INDEX_CONTENT_HASH, EMPTY_INDEX_CONTENT_HASH, sessionId, startOrdinal, endOrdinal);
4334
+ const ordinalCount = endOrdinal - startOrdinal + 1;
4335
+ return row?.sourceOrdinalCount === ordinalCount && row.expectedMapCount === row.mappedCount;
4682
4336
  }
4683
- function readPsProcessCommand(pid) {
4684
- try {
4685
- const output = rpcIdentityExecFileSync("ps", ["-p", String(pid), "-o", "command="], {
4686
- encoding: "utf8",
4687
- timeout: PS_PROBE_TIMEOUT_MS,
4688
- stdio: ["ignore", "pipe", "pipe"],
4689
- windowsHide: true
4690
- });
4691
- return String(output);
4692
- } catch {
4693
- return null;
4694
- }
4695
- }
4696
- function readProcessCommand(pid) {
4697
- if (!Number.isInteger(pid) || pid <= 0)
4698
- return null;
4699
- if (rpcIdentityPlatform === "linux")
4700
- return readLinuxProcessCommand(pid);
4701
- if (rpcIdentityPlatform === "win32") {
4702
- const cached = windowsProcessFactsCache?.get(pid);
4703
- if (cached?.commandLine)
4704
- return cached.commandLine;
4705
- return readWindowsProcess(pid).command ?? null;
4706
- }
4707
- return readPsProcessCommand(pid);
4708
- }
4709
- function executableName(token) {
4710
- return (token ?? "").replace(/^['"]|['"]$/g, "").split("/").at(-1) ?? "";
4711
- }
4712
- function commandTokens(command) {
4713
- return command.toLowerCase().replaceAll("\\", "/").replaceAll("\x00", " ").split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, "")).filter(Boolean);
4714
- }
4715
- function commandHasOpenCodeExecutable(tokens) {
4716
- return tokens.findIndex((token) => {
4717
- const executable = executableName(token).replace(/\.(?:exe|cmd)$/, "");
4718
- return executable === "opencode" || executable.endsWith("/opencode");
4719
- });
4720
- }
4721
- function commandHasPiExecutable(tokens) {
4722
- for (let index = 0;index < tokens.length; index += 1) {
4723
- const executable = executableName(tokens[index]).replace(/\.(?:exe|cmd)$/, "");
4724
- if (piHarnessKindFromExecutable(executable) !== undefined)
4725
- return true;
4726
- if (["node", "bun", "deno"].includes(executable)) {
4727
- const script = executableName(tokens[index + 1]).replace(/\.(?:exe|cmd)$/, "");
4728
- if (["pi", "pi.js", "pi.mjs", "pi.cjs"].includes(script) || tokens[index + 1]?.includes("pi-coding-agent")) {
4729
- return true;
4730
- }
4731
- }
4732
- }
4733
- return false;
4734
- }
4735
- function classifyProcessKind(command) {
4736
- if (!command)
4737
- return "process";
4738
- const tokens = commandTokens(command);
4739
- const openCodeIndex = commandHasOpenCodeExecutable(tokens);
4740
- if (openCodeIndex >= 0) {
4741
- const args = tokens.slice(openCodeIndex + 1);
4742
- if (args.some((token) => token === "serve" || token === "--serve" || token.startsWith("--serve="))) {
4743
- return "OpenCode server";
4744
- }
4745
- return "OpenCode instance (TUI/CLI)";
4746
- }
4747
- return commandHasPiExecutable(tokens) ? "Pi" : "process";
4748
- }
4749
- function commandLooksLikeOpenCode(command) {
4750
- const normalized = command.toLowerCase();
4751
- return OPEN_CODE_COMMAND_MARKERS.some((marker) => normalized.includes(marker));
4752
- }
4753
- var RPC_HOST_PROCESS_MARKERS = [
4754
- "opencode",
4755
- "openchamber",
4756
- "ck-mc",
4757
- "magic-context",
4758
- "cortexkit",
4759
- "electron",
4760
- "node",
4761
- "bun",
4762
- "deno",
4763
- "pi-coding-agent",
4764
- "oh-my-pi"
4765
- ];
4766
- function processCannotBeRpcHost(evidence) {
4767
- const texts = [evidence.imageName, evidence.commandLine].filter((text) => typeof text === "string" && text.trim().length > 0);
4768
- if (texts.length === 0)
4769
- return false;
4770
- for (const text of texts) {
4771
- const normalized = text.toLowerCase();
4772
- if (RPC_HOST_PROCESS_MARKERS.some((marker) => normalized.includes(marker)))
4773
- return false;
4774
- const tokens = commandTokens(text);
4775
- if (commandHasPiExecutable(tokens))
4776
- return false;
4777
- if (tokens.some((token) => piHarnessKindFromExecutable(token) !== undefined))
4778
- return false;
4779
- }
4780
- return true;
4781
- }
4782
- function isPidIdentityPlausible(record, evidence) {
4783
- if (!Number.isInteger(record.pid) || record.pid <= 0)
4784
- return "implausible";
4785
- if (Number.isFinite(record.started_at) && record.started_at > 0) {
4786
- const processStartTime = evidence ? evidence.startTime : readProcessStartTime(record.pid);
4787
- if (processStartTime !== null) {
4788
- return processStartTime <= record.started_at + RPC_IDENTITY_SKEW_TOLERANCE_MS ? "plausible" : "implausible";
4789
- }
4790
- const identity = evidence ?? {
4791
- commandLine: readProcessCommand(record.pid),
4792
- imageName: readCachedWindowsImageName(record.pid)
4793
- };
4794
- return processCannotBeRpcHost(identity) ? "implausible" : "inconclusive";
4795
- }
4796
- const command = evidence ? evidence.commandLine : rpcIdentityPlatform === "linux" ? readLinuxProcessCommand(record.pid) : rpcIdentityPlatform === "win32" ? readWindowsProcess(record.pid).command ?? null : readPsProcessCommand(record.pid);
4797
- if (command === null)
4798
- return "inconclusive";
4799
- if (commandLooksLikeOpenCode(command))
4800
- return "plausible";
4801
- return processCannotBeRpcHost({ commandLine: command, imageName: evidence?.imageName }) ? "implausible" : "inconclusive";
4802
- }
4803
- function commandLooksLikePiImage(command) {
4804
- const tokens = commandTokens(command);
4805
- const first = executableName(tokens[0]).replace(/\.(?:exe|cmd)$/, "");
4806
- return PI_IMAGE_NAMES.has(first);
4807
- }
4808
- function commandHasPiHarnessArc(command) {
4809
- const normalized = command.trim().toLowerCase().replaceAll("\\", "/").replaceAll("\x00", " ");
4810
- if (!normalized)
4811
- return false;
4812
- const tokens = commandTokens(command);
4813
- if (tokens.length === 0)
4814
- return false;
4815
- const hasArc = PI_HARNESS_ARC_MARKERS.some((marker) => normalized.includes(marker));
4816
- const first = executableName(tokens[0]).replace(/\.(?:exe|cmd)$/, "");
4817
- if (hasArc && ["pi", "omp", "oh-my-pi", "node", "bun", "deno", "cmd"].includes(first)) {
4818
- return true;
4819
- }
4820
- if (hasArc && PI_HARNESS_ARC_MARKERS.some((marker) => tokens[0].includes(marker))) {
4821
- return true;
4822
- }
4823
- if (["node", "bun", "deno"].includes(first)) {
4824
- const script = executableName(tokens[1]).replace(/\.(?:exe|cmd)$/, "");
4825
- if (["pi", "pi.js", "pi.mjs", "pi.cjs"].includes(script))
4826
- return true;
4827
- if (hasArc)
4828
- return true;
4829
- }
4830
- return false;
4831
- }
4832
- function execProcessList(exec, file, args, timeout = PS_PROBE_TIMEOUT_MS) {
4833
- return String(exec(file, [...args], {
4834
- encoding: "utf8",
4835
- timeout,
4836
- maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES,
4837
- stdio: ["ignore", "pipe", "pipe"],
4838
- windowsHide: true
4839
- }));
4840
- }
4841
- function parseWindowsCreationDate(value) {
4842
- if (value !== null && typeof value === "object" && "value" in value) {
4843
- return parseWindowsCreationDate(value.value);
4844
- }
4845
- if (typeof value === "number" && Number.isFinite(value)) {
4846
- return value > 1000000000000 ? value : value * 1000;
4847
- }
4848
- if (typeof value !== "string")
4849
- return null;
4850
- const trimmed = value.trim();
4851
- if (!trimmed)
4852
- return null;
4853
- const wmi = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-])(\d{3})$/.exec(trimmed);
4854
- if (wmi) {
4855
- const utcMs = Date.UTC(Number(wmi[1]), Number(wmi[2]) - 1, Number(wmi[3]), Number(wmi[4]), Number(wmi[5]), Number(wmi[6]), Number(wmi[7]) / 1000);
4856
- if (!Number.isFinite(utcMs))
4857
- return null;
4858
- const offsetMinutes = Number(wmi[9]);
4859
- const sign = wmi[8] === "+" ? 1 : -1;
4860
- return utcMs - sign * offsetMinutes * 60000;
4861
- }
4862
- const dotNet = /^\/Date\((-?\d+)\)\/$/.exec(trimmed);
4863
- if (dotNet) {
4864
- const milliseconds = Number(dotNet[1]);
4865
- return Number.isFinite(milliseconds) ? milliseconds : null;
4866
- }
4867
- const parsed = Date.parse(trimmed);
4868
- return Number.isFinite(parsed) ? parsed : null;
4869
- }
4870
- function parseWindowsCimOutput(output) {
4871
- const trimmed = output.trim();
4872
- if (!trimmed)
4873
- return null;
4874
- const bracket = trimmed.indexOf("[");
4875
- const brace = trimmed.indexOf("{");
4876
- const start = Math.min(bracket === -1 ? Number.POSITIVE_INFINITY : bracket, brace === -1 ? Number.POSITIVE_INFINITY : brace);
4877
- if (!Number.isFinite(start))
4878
- return null;
4879
- let parsed;
4880
- try {
4881
- parsed = JSON.parse(trimmed.slice(start));
4882
- } catch {
4883
- return null;
4884
- }
4885
- if (parsed == null)
4886
- return null;
4887
- const rows = Array.isArray(parsed) ? parsed : [parsed];
4888
- const facts = [];
4889
- for (const row of rows) {
4890
- if (!row || typeof row !== "object")
4891
- continue;
4892
- const record = row;
4893
- const pid = Number(record.ProcessId);
4894
- if (!Number.isInteger(pid) || pid <= 0)
4895
- continue;
4896
- const parentRaw = record.ParentProcessId;
4897
- const parentPid = parentRaw == null || parentRaw === "" ? Number.NaN : Number(parentRaw);
4898
- const commandLine = typeof record.CommandLine === "string" ? record.CommandLine : null;
4899
- facts.push({
4900
- pid,
4901
- parentPid: Number.isInteger(parentPid) && parentPid > 0 ? parentPid : null,
4902
- commandLine,
4903
- imageName: typeof record.Name === "string" ? record.Name : commandLine ? executableName(commandTokens(commandLine)[0]) : null,
4904
- startTime: parseWindowsCreationDate(record.CreationDate)
4905
- });
4906
- }
4907
- return facts.length > 0 ? facts : null;
4908
- }
4909
- function snapshotFromFacts(facts, source) {
4910
- const parentByPid = new Map;
4911
- for (const fact of facts) {
4912
- if (fact.parentPid != null)
4913
- parentByPid.set(fact.pid, fact.parentPid);
4914
- }
4915
- return { facts, parentByPid, source };
4916
- }
4917
- function tryReadWindowsCimSnapshot(exec) {
4918
- try {
4919
- const output = execProcessList(exec, "powershell", ["-NoProfile", "-Command", WINDOWS_CIM_COMMAND], WINDOWS_CIM_PROBE_TIMEOUT_MS);
4920
- const facts = parseWindowsCimOutput(output);
4921
- return facts ? snapshotFromFacts(facts, "cim") : null;
4922
- } catch {
4923
- return null;
4924
- }
4925
- }
4926
- function tryReadWindowsTasklistSnapshot() {
4927
- try {
4928
- const output = execProcessList(rpcProcessListExecFileSync, "tasklist", [
4929
- "/FO",
4930
- "CSV",
4931
- "/NH"
4932
- ]);
4933
- const entries = parseTasklistOutput(output);
4934
- if (entries === null)
4935
- return null;
4936
- const facts = entries.map((entry) => ({
4937
- pid: entry.pid,
4938
- parentPid: null,
4939
- commandLine: null,
4940
- imageName: entry.command,
4941
- startTime: null
4942
- }));
4943
- return snapshotFromFacts(facts, "tasklist");
4944
- } catch {
4945
- return null;
4946
- }
4947
- }
4948
- function readPosixProcessSnapshot() {
4949
- const output = execProcessList(rpcProcessListExecFileSync, "ps", ["-axo", "pid=,command="]);
4950
- const facts = [];
4951
- for (const line of output.split(/\r?\n/)) {
4952
- const match = /^\s*(\d+)\s+(.+)$/.exec(line);
4953
- if (!match)
4954
- continue;
4955
- const pid = Number(match[1]);
4956
- if (!Number.isInteger(pid) || pid <= 0)
4957
- continue;
4958
- facts.push({
4959
- pid,
4960
- parentPid: null,
4961
- commandLine: match[2],
4962
- imageName: executableName(commandTokens(match[2])[0]),
4963
- startTime: null
4964
- });
4965
- }
4966
- return snapshotFromFacts(facts, "ps");
4967
- }
4968
- function readPosixParentPid(pid) {
4969
- try {
4970
- const output = execProcessList(rpcProcessListExecFileSync, "ps", [
4971
- "-o",
4972
- "ppid=",
4973
- "-p",
4974
- String(pid)
4975
- ]);
4976
- const match = /^\s*(\d+)\s*$/.exec(output);
4977
- if (!match)
4978
- return null;
4979
- const ppid = Number(match[1]);
4980
- return Number.isInteger(ppid) && ppid > 0 ? ppid : null;
4981
- } catch {
4982
- return null;
4983
- }
4984
- }
4985
- function collectAncestorPids(selfPid, parentByPid, probeParents = true) {
4986
- const ancestors = new Set;
4987
- let current = selfPid;
4988
- for (let depth = 0;depth < MAX_ANCESTOR_WALK_DEPTH; depth += 1) {
4989
- let ppid = null;
4990
- if (parentByPid.has(current)) {
4991
- ppid = parentByPid.get(current) ?? null;
4992
- } else if (rpcIdentityPlatform !== "win32" && probeParents) {
4993
- ppid = readPosixParentPid(current);
4994
- if (ppid == null && current === process.pid && process.ppid > 0) {
4995
- ppid = process.ppid;
4996
- }
4997
- } else if (current === process.pid && process.ppid > 0) {
4998
- ppid = process.ppid;
4999
- } else {
5000
- break;
5001
- }
5002
- if (ppid == null || ppid <= 0 || ppid === current || ancestors.has(ppid))
5003
- break;
5004
- ancestors.add(ppid);
5005
- current = ppid;
5006
- }
5007
- return ancestors;
5008
- }
5009
- function classifyLivePiSnapshot(snapshot, probeParents = true) {
5010
- const ancestors = collectAncestorPids(process.pid, snapshot.parentByPid, probeParents);
5011
- const processIds = new Set;
5012
- const inconclusivePids = new Set;
5013
- const skippedAncestorPids = [];
5014
- for (const fact of snapshot.facts) {
5015
- if (fact.pid === process.pid)
5016
- continue;
5017
- const command = fact.commandLine ?? fact.imageName ?? "";
5018
- const looksLikeHarness = commandHasPiHarnessArc(command) || commandLooksLikePiImage(command);
5019
- if (!looksLikeHarness)
5020
- continue;
5021
- if (ancestors.has(fact.pid)) {
5022
- skippedAncestorPids.push(fact.pid);
5023
- log(`[magic-context] Pi process scan: skipping ancestor PID ${fact.pid} (session launcher shim)`);
5024
- continue;
5025
- }
5026
- if (commandHasPiHarnessArc(command)) {
5027
- processIds.add(fact.pid);
5028
- continue;
5029
- }
5030
- inconclusivePids.add(fact.pid);
5031
- log(`[magic-context] Pi process scan: PID ${fact.pid} command line is ambiguous (image-name or missing Pi/OMP arc); treating as inconclusive`);
5032
- }
5033
- skippedAncestorPids.sort((left, right) => left - right);
5034
- const verified = [...processIds].sort((left, right) => left - right);
5035
- const inconclusive = [...inconclusivePids].sort((left, right) => left - right);
5036
- if (verified.length === 0 && inconclusive.length > 0) {
5037
- return {
5038
- state: "inconclusive",
5039
- processIds: [],
5040
- inconclusivePids: inconclusive,
5041
- ...skippedAncestorPids.length > 0 ? { skippedAncestorPids } : {}
5042
- };
5043
- }
5044
- return {
5045
- state: "known",
5046
- processIds: verified,
5047
- ...inconclusive.length > 0 ? { inconclusivePids: inconclusive } : {},
5048
- ...skippedAncestorPids.length > 0 ? { skippedAncestorPids } : {}
5049
- };
5050
- }
5051
- function inspectLivePiProcesses() {
5052
- if (false) {}
5053
- try {
5054
- if (rpcIdentityPlatform === "win32") {
5055
- const now = rpcIdentityNowMs();
5056
- const cached = windowsProcessSnapshotCache;
5057
- const snapshot = cached && now >= cached.at && now - cached.at < WINDOWS_PROCESS_SNAPSHOT_TTL_MS ? cached.snapshot : tryReadWindowsCimSnapshot(rpcProcessListExecFileSync) ?? tryReadWindowsTasklistSnapshot();
5058
- if (!snapshot) {
5059
- return {
5060
- state: "unreadable",
5061
- processIds: [],
5062
- error: "process list unavailable"
5063
- };
5064
- }
5065
- if (snapshot !== cached?.snapshot)
5066
- windowsProcessSnapshotCache = { snapshot, at: now };
5067
- rememberWindowsProcessFacts(snapshot.facts);
5068
- return classifyLivePiSnapshot(snapshot);
5069
- }
5070
- return classifyLivePiSnapshot(readPosixProcessSnapshot());
5071
- } catch (error) {
5072
- return {
5073
- state: "unreadable",
5074
- processIds: [],
5075
- error: error instanceof Error ? error.message : String(error)
5076
- };
5077
- }
5078
- }
5079
- function parseRpcPortFile(content, fallbackPid = 0) {
5080
- const trimmed = content.trim();
5081
- if (!trimmed)
5082
- return null;
5083
- if (trimmed.startsWith("{")) {
5084
- try {
5085
- const parsed = JSON.parse(trimmed);
5086
- const port = Number(parsed.port);
5087
- const pid = Number(parsed.pid);
5088
- const startedAt = Number(parsed.started_at);
5089
- if (!isValidPort(port) || !Number.isInteger(pid) || pid <= 0)
5090
- return null;
5091
- return {
5092
- port,
5093
- pid,
5094
- started_at: Number.isFinite(startedAt) ? startedAt : 0,
5095
- kind: typeof parsed.kind === "string" ? parsed.kind : undefined,
5096
- harness: typeof parsed.harness === "string" ? parsed.harness : undefined,
5097
- token: typeof parsed.token === "string" ? parsed.token : undefined,
5098
- instance_id: typeof parsed.instance_id === "string" ? parsed.instance_id : undefined
5099
- };
5100
- } catch {
5101
- return null;
5102
- }
5103
- }
5104
- const port = Number.parseInt(trimmed, 10);
5105
- if (!isValidPort(port))
5106
- return null;
5107
- return { port, pid: fallbackPid, started_at: 0 };
5108
- }
5109
- function isValidPort(port) {
5110
- return Number.isInteger(port) && port > 0 && port <= 65535;
5111
- }
5112
- function snapshotEvidence(fact) {
5113
- return {
5114
- startTime: fact?.startTime ?? null,
5115
- commandLine: fact?.commandLine ?? fact?.imageName ?? null,
5116
- ...fact?.imageName ? { imageName: fact.imageName } : {}
5117
- };
5118
- }
5119
- function execProcessListAsync(file, args, timeout) {
5120
- return new Promise((resolve, reject) => {
5121
- execFile(file, args, { encoding: "utf8", timeout, windowsHide: true, maxBuffer: 8 * 1024 * 1024 }, (error, stdout) => {
5122
- if (error)
5123
- reject(error);
5124
- else
5125
- resolve(stdout);
5126
- });
5127
- });
5128
- }
5129
- var asyncProcessExec = execProcessListAsync;
5130
- var asyncInspection;
5131
- var asyncInspectionExpires = 0;
5132
- var lastAsyncSnapshot = null;
5133
- function inspectProcessesAsync(forceFresh = false) {
5134
- if (!forceFresh && asyncInspection && rpcIdentityNowMs() < asyncInspectionExpires)
5135
- return asyncInspection;
5136
- asyncInspectionExpires = Number.POSITIVE_INFINITY;
5137
- asyncInspection = (async () => {
5138
- let snapshot = null;
5139
- try {
5140
- if (rpcIdentityPlatform === "win32") {
5141
- try {
5142
- const output = await asyncProcessExec("powershell", ["-NoProfile", "-Command", WINDOWS_CIM_COMMAND], WINDOWS_CIM_PROBE_TIMEOUT_MS);
5143
- const facts = parseWindowsCimOutput(output);
5144
- if (facts)
5145
- snapshot = snapshotFromFacts(facts, "cim");
5146
- } catch {}
5147
- if (!snapshot) {
5148
- const entries = parseTasklistOutput(await asyncProcessExec("tasklist", ["/FO", "CSV", "/NH"], PS_PROBE_TIMEOUT_MS));
5149
- if (entries)
5150
- snapshot = snapshotFromFacts(entries.map(({ pid, command }) => ({
5151
- pid,
5152
- parentPid: null,
5153
- commandLine: null,
5154
- imageName: command,
5155
- startTime: null
5156
- })), "tasklist");
5157
- }
5158
- } else {
5159
- const output = await asyncProcessExec("ps", ["-axo", "pid=,ppid=,lstart=,command="], PS_PROBE_TIMEOUT_MS);
5160
- const facts = [];
5161
- for (const line of output.split(/\r?\n/)) {
5162
- const match = /^\s*(\d+)\s+(\d+)\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+(.+)$/.exec(line);
5163
- if (!match)
5164
- continue;
5165
- const startTime = Date.parse(match[3]);
5166
- facts.push({
5167
- pid: Number(match[1]),
5168
- parentPid: Number(match[2]),
5169
- commandLine: match[4],
5170
- imageName: null,
5171
- startTime: Number.isFinite(startTime) ? startTime : null
5172
- });
5173
- }
5174
- snapshot = facts.length > 0 ? snapshotFromFacts(facts, "ps") : null;
5175
- }
5176
- } catch {}
5177
- const fresh = snapshot !== null;
5178
- if (snapshot)
5179
- lastAsyncSnapshot = snapshot;
5180
- else
5181
- snapshot = lastAsyncSnapshot;
5182
- const byPid = new Map(snapshot?.facts.map((fact) => [fact.pid, fact]));
5183
- return {
5184
- pi: snapshot ? classifyLivePiSnapshot(snapshot, false) : { state: "unreadable", processIds: [] },
5185
- ...fresh && snapshot ? { processSnapshot: { source: snapshot.source, facts: snapshot.facts } } : {},
5186
- evidence: (pid) => snapshotEvidence(byPid.get(pid)),
5187
- liveness: (pid) => byPid.has(pid) ? "alive" : fresh ? "dead" : "inconclusive"
5188
- };
5189
- })().finally(() => {
5190
- asyncInspectionExpires = rpcIdentityNowMs() + WINDOWS_PROCESS_SNAPSHOT_TTL_MS;
5191
- });
5192
- return asyncInspection;
5193
- }
5194
- function inspectWindowsProcessesSync() {
5195
- const snapshot = tryReadWindowsCimSnapshot(rpcProcessListExecFileSync) ?? tryReadWindowsTasklistSnapshot();
5196
- const byPid = new Map(snapshot?.facts.map((fact) => [fact.pid, fact]));
5197
- return {
5198
- pi: snapshot ? classifyLivePiSnapshot(snapshot) : { state: "unreadable", processIds: [] },
5199
- ...snapshot ? { processSnapshot: { source: snapshot.source, facts: snapshot.facts } } : {},
5200
- evidence: (pid) => snapshotEvidence(byPid.get(pid)),
5201
- liveness: (pid) => byPid.has(pid) ? "alive" : snapshot ? "dead" : "inconclusive"
5202
- };
5203
- }
5204
-
5205
- // src/shared/sqlite-context-pragmas.ts
5206
- function configureContextDatabasePragmas(db, readonly = false) {
5207
- const [row] = db.prepare(readonly ? "PRAGMA journal_mode" : "PRAGMA journal_mode=WAL").all();
5208
- const mode = row?.journal_mode?.toLowerCase();
5209
- if (mode === "memory" || readonly && mode !== "wal")
4337
+ function deleteUnmappedMessageFtsRows(db, sessionIds) {
4338
+ if (sessionIds.length === 0)
5210
4339
  return;
5211
- if (mode !== "wal") {
5212
- throw new Error(`context.db requires WAL before synchronous=NORMAL (got ${mode ?? "unknown"})`);
5213
- }
5214
- db.exec("PRAGMA synchronous=NORMAL");
5215
- }
5216
-
5217
- // src/features/magic-context/context-store-uuid.ts
5218
- import { randomUUID } from "node:crypto";
5219
- function getContextStoreUuid(db) {
5220
- const row = db.prepare("SELECT value FROM context_store_meta WHERE key = 'store_uuid'").get();
5221
- return typeof row?.value === "string" && row.value.length > 0 ? row.value : null;
5222
- }
5223
- function ensureContextStoreUuid(db) {
5224
- const existing = getContextStoreUuid(db);
5225
- if (existing)
5226
- return existing;
5227
- const minted = randomUUID();
5228
- withPrivilegedWriter(db, () => {
5229
- db.transaction(() => {
5230
- db.prepare("INSERT INTO context_store_meta(key, value) VALUES ('store_uuid', ?) ON CONFLICT(key) DO NOTHING").run(minted);
5231
- }).immediate();
5232
- });
5233
- return getContextStoreUuid(db) ?? minted;
5234
- }
5235
-
5236
- // src/features/magic-context/message-fts-rowid-map.ts
5237
- import { createHash as createHash6 } from "node:crypto";
5238
- var MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE = 100;
5239
- var BACKFILL_STATE_ID = 1;
5240
- var EMPTY_INDEX_CONTENT_HASH = createHash6("sha256").update("").digest("hex");
5241
- var upsertMapStatements = new WeakMap;
5242
- var rangeReadyStatements = new WeakMap;
5243
- var updateTimeStatements = new WeakMap;
5244
- var activeBackfills = new WeakMap;
5245
- function getUpsertMapStatement(db) {
5246
- let statement = upsertMapStatements.get(db);
5247
- if (!statement) {
5248
- statement = db.prepare(`INSERT INTO message_fts_rowid_map
5249
- (session_id, message_ordinal, fts_rowid, message_time_ms)
5250
- VALUES (?, ?, ?, ?)
5251
- ON CONFLICT(session_id, message_ordinal) DO UPDATE SET
5252
- fts_rowid = excluded.fts_rowid,
5253
- message_time_ms = COALESCE(
5254
- excluded.message_time_ms,
5255
- message_fts_rowid_map.message_time_ms
5256
- )`);
5257
- upsertMapStatements.set(db, statement);
5258
- }
5259
- return statement;
5260
- }
5261
- function getBackfillState(db) {
5262
- const row = db.prepare(`SELECT watermark_rowid AS watermarkRowid, completed
5263
- FROM message_fts_rowid_map_backfill_state
5264
- WHERE id = ?`).get(BACKFILL_STATE_ID);
5265
- return {
5266
- processed: 0,
5267
- watermarkRowid: typeof row?.watermarkRowid === "number" && Number.isSafeInteger(row.watermarkRowid) ? row.watermarkRowid : 0,
5268
- completed: row?.completed === 1
5269
- };
5270
- }
5271
- function recordMessageFtsRowid(db, sessionId, messageOrdinal, ftsRowid, messageTimeMs = null) {
5272
- const numericRowid = Number(ftsRowid);
5273
- if (!Number.isSafeInteger(numericRowid) || numericRowid <= 0) {
5274
- throw new Error(`invalid message FTS rowid: ${String(ftsRowid)}`);
5275
- }
5276
- const normalizedTime = typeof messageTimeMs === "number" && Number.isSafeInteger(messageTimeMs) && messageTimeMs >= 0 ? messageTimeMs : null;
5277
- getUpsertMapStatement(db).run(sessionId, messageOrdinal, numericRowid, normalizedTime);
5278
- }
5279
- function recordIndexedMessageTime(db, sessionId, messageOrdinal, messageTimeMs) {
5280
- if (typeof messageTimeMs !== "number" || !Number.isSafeInteger(messageTimeMs) || messageTimeMs < 0) {
5281
- return;
5282
- }
5283
- let statement = updateTimeStatements.get(db);
5284
- if (!statement) {
5285
- statement = db.prepare(`UPDATE message_fts_rowid_map
5286
- SET message_time_ms = ?
5287
- WHERE session_id = ? AND message_ordinal = ?
5288
- AND message_time_ms IS NULL`);
5289
- updateTimeStatements.set(db, statement);
5290
- }
5291
- statement.run(messageTimeMs, sessionId, messageOrdinal);
5292
- }
5293
- function backfillMessageFtsRowidMapBatch(db, batchSize = MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE) {
5294
- const boundedBatchSize = Number.isFinite(batchSize) ? Math.min(MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE, Math.max(1, Math.floor(batchSize))) : MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE;
5295
- let progress = {
5296
- processed: 0,
5297
- watermarkRowid: 0,
5298
- completed: false
5299
- };
5300
- let transactionStartedAt = 0;
5301
- db.transaction(() => {
5302
- transactionStartedAt = performance.now();
5303
- db.prepare(`INSERT OR IGNORE INTO message_fts_rowid_map_backfill_state
5304
- (id, watermark_rowid, completed) VALUES (?, 0, 0)`).run(BACKFILL_STATE_ID);
5305
- const state = getBackfillState(db);
5306
- if (state.completed) {
5307
- progress = state;
5308
- return;
5309
- }
5310
- const rows = db.prepare(`SELECT rowid AS ftsRowid,
5311
- session_id AS sessionId, message_ordinal AS messageOrdinal
5312
- FROM message_history_fts WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`).all(state.watermarkRowid, boundedBatchSize);
5313
- let watermarkRowid = state.watermarkRowid;
5314
- for (const row of rows) {
5315
- const ftsRowid = Number(row.ftsRowid);
5316
- const messageOrdinal = Number(row.messageOrdinal);
5317
- if (Number.isSafeInteger(ftsRowid) && ftsRowid > watermarkRowid) {
5318
- watermarkRowid = ftsRowid;
5319
- }
5320
- if (typeof row.sessionId === "string" && Number.isSafeInteger(messageOrdinal) && messageOrdinal >= 0 && Number.isSafeInteger(ftsRowid) && ftsRowid > 0) {
5321
- recordMessageFtsRowid(db, row.sessionId, messageOrdinal, ftsRowid);
5322
- }
5323
- }
5324
- const completed = rows.length < boundedBatchSize;
5325
- db.prepare(`UPDATE message_fts_rowid_map_backfill_state
5326
- SET watermark_rowid = ?, completed = ?, updated_at = ?
5327
- WHERE id = ?`).run(watermarkRowid, completed ? 1 : 0, Date.now(), BACKFILL_STATE_ID);
5328
- progress = {
5329
- processed: rows.length,
5330
- watermarkRowid,
5331
- completed
5332
- };
5333
- }).immediate();
5334
- logSlowWriteTransaction("message_fts_rowid_backfill", transactionStartedAt);
5335
- return progress;
5336
- }
5337
- async function runMessageFtsRowidMapBackfill(db) {
5338
- await new Promise((resolve) => setTimeout(resolve, 0));
5339
- for (;; ) {
5340
- const progress = backfillMessageFtsRowidMapBatch(db);
5341
- if (progress.completed)
5342
- return;
5343
- await new Promise((resolve) => setTimeout(resolve, 0));
5344
- }
5345
- }
5346
- function startMessageFtsRowidMapBackfill(db) {
5347
- const active = activeBackfills.get(db);
5348
- if (active)
5349
- return active;
5350
- const run = withoutSqliteTransformPass(() => withSqliteBackgroundWriter(() => runMessageFtsRowidMapBackfill(db))).finally(() => {
5351
- activeBackfills.delete(db);
5352
- });
5353
- activeBackfills.set(db, run);
5354
- return run;
5355
- }
5356
- function messageFtsOrdinalRangeIsMapped(db, sessionId, startOrdinal, endOrdinal) {
5357
- if (endOrdinal < startOrdinal)
5358
- return true;
5359
- if (getBackfillState(db).completed)
5360
- return true;
5361
- let statement = rangeReadyStatements.get(db);
5362
- if (!statement) {
5363
- statement = db.prepare(`SELECT COUNT(DISTINCT source.message_ordinal) AS sourceOrdinalCount,
5364
- COUNT(DISTINCT CASE
5365
- WHEN source.role IN ('user', 'assistant')
5366
- AND source.normalized_content_hash != ?
5367
- THEN source.message_ordinal
5368
- END) AS expectedMapCount,
5369
- COUNT(DISTINCT CASE
5370
- WHEN source.role IN ('user', 'assistant')
5371
- AND source.normalized_content_hash != ?
5372
- AND map.fts_rowid IS NOT NULL
5373
- THEN source.message_ordinal
5374
- END) AS mappedCount
5375
- FROM message_history_source AS source
5376
- LEFT JOIN message_fts_rowid_map AS map
5377
- ON map.session_id = source.session_id
5378
- AND map.message_ordinal = source.message_ordinal
5379
- WHERE source.session_id = ?
5380
- AND source.message_ordinal BETWEEN ? AND ?`);
5381
- rangeReadyStatements.set(db, statement);
5382
- }
5383
- const row = statement.get(EMPTY_INDEX_CONTENT_HASH, EMPTY_INDEX_CONTENT_HASH, sessionId, startOrdinal, endOrdinal);
5384
- const ordinalCount = endOrdinal - startOrdinal + 1;
5385
- return row?.sourceOrdinalCount === ordinalCount && row.expectedMapCount === row.mappedCount;
5386
- }
5387
- function deleteUnmappedMessageFtsRows(db, sessionIds) {
5388
- if (sessionIds.length === 0)
5389
- return;
5390
- const state = getBackfillState(db);
5391
- if (state.completed)
4340
+ const state = getBackfillState(db);
4341
+ if (state.completed)
5392
4342
  return;
5393
4343
  const placeholders = sessionIds.map(() => "?").join(", ");
5394
4344
  db.prepare(`DELETE FROM message_history_fts
5395
4345
  WHERE rowid > ? AND session_id IN (${placeholders})`).run(state.watermarkRowid, ...sessionIds);
5396
4346
  }
5397
4347
 
5398
- // src/features/magic-context/migration-worker-client.ts
5399
- import { Worker } from "node:worker_threads";
5400
-
5401
- // src/shared/off-thread-migration-clock.ts
5402
- var activeMigrations = 0;
5403
- var activeSince = 0;
5404
- var accumulatedMs = 0;
5405
- var idleWaiters = [];
5406
- function offThreadMigrationMs(now = performance.now()) {
5407
- return accumulatedMs + (activeMigrations > 0 ? Math.max(0, now - activeSince) : 0);
5408
- }
5409
- function isOffThreadMigrationRunning() {
5410
- return activeMigrations > 0;
5411
- }
5412
- function beginOffThreadMigration() {
5413
- if (activeMigrations === 0)
5414
- activeSince = performance.now();
5415
- activeMigrations += 1;
5416
- let ended = false;
5417
- return () => {
5418
- if (ended)
5419
- return;
5420
- ended = true;
5421
- activeMigrations -= 1;
5422
- if (activeMigrations > 0)
5423
- return;
5424
- accumulatedMs += Math.max(0, performance.now() - activeSince);
5425
- const waiters = idleWaiters;
5426
- idleWaiters = [];
5427
- for (const wake of waiters)
5428
- wake();
5429
- };
5430
- }
5431
- function whenOffThreadMigrationIdle() {
5432
- if (activeMigrations === 0)
5433
- return Promise.resolve();
5434
- return new Promise((resolve) => idleWaiters.push(resolve));
5435
- }
5436
- function startBootDeadline(timeoutMs, onDeferred) {
5437
- const startedAt = performance.now();
5438
- const migratedAtStart = offThreadMigrationMs(startedAt);
5439
- const budgetMs = Math.max(0, timeoutMs);
5440
- let timer;
5441
- let cancelled = false;
5442
- let deferredReported = false;
5443
- let resolveExpired;
5444
- const expired = new Promise((resolve) => {
5445
- resolveExpired = resolve;
5446
- });
5447
- const remainingMs = () => {
5448
- const now = performance.now();
5449
- const countedMs = now - startedAt - (offThreadMigrationMs(now) - migratedAtStart);
5450
- return budgetMs - countedMs;
5451
- };
5452
- const check = () => {
5453
- timer = undefined;
5454
- if (cancelled)
5455
- return;
5456
- if (isOffThreadMigrationRunning()) {
5457
- if (!deferredReported) {
5458
- deferredReported = true;
5459
- onDeferred?.();
5460
- }
5461
- whenOffThreadMigrationIdle().then(check);
5462
- return;
5463
- }
5464
- const left = remainingMs();
5465
- if (left > 0) {
5466
- timer = setTimeout(check, left);
5467
- return;
5468
- }
5469
- resolveExpired();
5470
- };
5471
- timer = setTimeout(check, budgetMs);
5472
- return {
5473
- expired,
5474
- cancel: () => {
5475
- cancelled = true;
5476
- if (timer)
5477
- clearTimeout(timer);
5478
- timer = undefined;
5479
- }
5480
- };
5481
- }
5482
-
5483
- // src/features/magic-context/migration-worker-client.ts
5484
- function workerStartError(detail) {
5485
- return new Error(`the migration worker could not start (${detail}); reinstall or rebuild the plugin`);
5486
- }
5487
- function defaultWorkerEntry() {
5488
- return new URL(new URL(import.meta.url).pathname.endsWith(".ts") ? "./migration-worker.ts" : "./migration-worker.js", import.meta.url);
5489
- }
5490
- var workerEntryOverride = null;
5491
- function runMigrationsOffThread(data) {
5492
- return new Promise((resolve, reject) => {
5493
- let worker;
5494
- try {
5495
- worker = new Worker(workerEntryOverride ?? defaultWorkerEntry(), { workerData: data });
5496
- } catch (error) {
5497
- reject(workerStartError(error instanceof Error ? error.message : String(error)));
5498
- return;
5499
- }
5500
- log(`[migrations] applying pending migrations on a worker thread: ${data.dbPath}`);
5501
- let ready = false;
5502
- let settled = false;
5503
- let endBusy = beginOffThreadMigration();
5504
- const setBusy = (busy) => {
5505
- if (busy && !endBusy)
5506
- endBusy = beginOffThreadMigration();
5507
- if (!busy && endBusy) {
5508
- endBusy();
5509
- endBusy = null;
5510
- }
5511
- };
5512
- const settle = (finish) => {
5513
- if (settled)
5514
- return;
5515
- settled = true;
5516
- setBusy(false);
5517
- finish();
5518
- };
5519
- const unavailable = (detail) => {
5520
- settle(() => {
5521
- reject(workerStartError(detail));
5522
- });
5523
- };
5524
- worker.on("message", (message) => {
5525
- switch (message.type) {
5526
- case "ready":
5527
- ready = true;
5528
- log(`[migrations] migration worker ready: ${data.dbPath}`);
5529
- return;
5530
- case "log":
5531
- writeForwardedLogLine(message.line);
5532
- return;
5533
- case "lock-wait":
5534
- if (!settled)
5535
- setBusy(!message.waiting);
5536
- return;
5537
- case "done":
5538
- if (!ready) {
5539
- unavailable("reported completion before ready");
5540
- return;
5541
- }
5542
- settle(() => resolve("migrated"));
5543
- return;
5544
- case "failed":
5545
- if (!ready) {
5546
- unavailable(message.message);
5547
- return;
5548
- }
5549
- settle(() => reject(new Error(message.message)));
5550
- return;
5551
- }
5552
- });
5553
- worker.on("error", (error) => {
5554
- if (!ready) {
5555
- unavailable(error.message);
5556
- worker.terminate();
5557
- return;
5558
- }
5559
- settle(() => reject(new Error(`migration worker failed: ${error.message}`)));
5560
- });
5561
- worker.on("exit", (code) => {
5562
- if (!ready) {
5563
- unavailable(`exited with code ${code} before loading`);
5564
- return;
5565
- }
5566
- settle(() => reject(new Error(`migration worker exited (code ${code}) before reporting a result`)));
5567
- });
5568
- });
5569
- }
5570
-
5571
- // src/features/magic-context/tool-owner-backfill.ts
5572
- import { existsSync as existsSync2 } from "node:fs";
5573
- var LEASE_DURATION_MS = 5 * 60 * 1000;
5574
- var LEASE_RENEWAL_MS = 60 * 1000;
5575
- function resolveOpencodeDbPath() {
5576
- return resolveOpenCodeDbPath().path;
5577
- }
5578
- function ensureBackfillStateTable(db) {
5579
- db.exec(`
5580
- CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
5581
- session_id TEXT PRIMARY KEY,
5582
- status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'skipped')),
5583
- started_at INTEGER,
5584
- lease_expires_at INTEGER,
5585
- completed_at INTEGER,
5586
- last_error TEXT
5587
- );
5588
- CREATE INDEX IF NOT EXISTS idx_tool_owner_backfill_state_status
5589
- ON tool_owner_backfill_state(status);
5590
- `);
5591
- }
5592
- function runToolOwnerBackfill(db) {
5593
- const startedAt = performance.now();
5594
- ensureBackfillStateTable(db);
5595
- const result = {
5596
- sessionsProcessed: 0,
5597
- sessionsSkippedNoOcDb: 0,
5598
- sessionsSkippedNoMatches: 0,
5599
- sessionsCompleted: 0,
5600
- sessionsBlockedByLease: 0,
5601
- sessionsErrored: 0,
5602
- rowsUpdated: 0,
5603
- rowsLeftNull: 0,
5604
- durationMs: 0
5605
- };
5606
- if (!isToolOwnerBackfillNeeded(db)) {
5607
- result.durationMs = performance.now() - startedAt;
5608
- return result;
5609
- }
5610
- const opencodeDbPath = resolveOpencodeDbPath();
5611
- if (!existsSync2(opencodeDbPath)) {
5612
- log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
5613
- markAllUnbackfilledSessionsSkipped(db);
5614
- result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
5615
- result.durationMs = performance.now() - startedAt;
5616
- return result;
5617
- }
5618
- const escapedDbPath = opencodeDbPath.replaceAll("'", "''");
5619
- db.exec(`ATTACH '${escapedDbPath}' AS oc_backfill`);
5620
- try {
5621
- if (!hasV1MessageTables(db, "oc_backfill")) {
5622
- throw new Error(`OpenCode store at ${opencodeDbPath} has no v1 message tables; nothing to backfill`);
5623
- }
5624
- backfillToolOwnersInChunks(db, result);
5625
- } finally {
5626
- try {
5627
- db.exec("DETACH DATABASE oc_backfill");
5628
- } catch (error) {
5629
- log(`[backfill] failed to detach oc_backfill database: ${error instanceof Error ? error.message : String(error)}`);
5630
- }
5631
- }
5632
- result.durationMs = performance.now() - startedAt;
5633
- log(`[backfill] sessions=${result.sessionsProcessed} completed=${result.sessionsCompleted} skipped_no_oc=${result.sessionsSkippedNoOcDb} skipped_no_matches=${result.sessionsSkippedNoMatches} blocked_by_lease=${result.sessionsBlockedByLease} errored=${result.sessionsErrored} rows_updated=${result.rowsUpdated} rows_left_null=${result.rowsLeftNull} duration_ms=${Math.round(result.durationMs)}`);
5634
- return result;
5635
- }
5636
- function isToolOwnerBackfillNeeded(db) {
5637
- ensureBackfillStateTable(db);
5638
- const row = db.prepare(`SELECT 1 AS hit
5639
- FROM tags
5640
- WHERE type = 'tool' AND tool_owner_message_id IS NULL
5641
- AND NOT EXISTS (
5642
- SELECT 1 FROM tool_owner_backfill_state s
5643
- WHERE s.session_id = tags.session_id
5644
- AND s.status IN ('completed', 'skipped')
5645
- )
5646
- LIMIT 1`).get();
5647
- return row !== null && row !== undefined;
5648
- }
5649
- function markAllUnbackfilledSessionsSkipped(db) {
5650
- const now = Date.now();
5651
- db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, completed_at, last_error)
5652
- SELECT DISTINCT session_id, 'skipped', NULL, ?, NULL
5653
- FROM tags
5654
- WHERE type = 'tool' AND tool_owner_message_id IS NULL
5655
- ON CONFLICT(session_id) DO UPDATE SET
5656
- status = 'skipped',
5657
- completed_at = excluded.completed_at,
5658
- last_error = NULL
5659
- WHERE tool_owner_backfill_state.status NOT IN ('completed', 'running')`).run(now);
5660
- }
5661
- function countSessionsByStatus(db, status) {
5662
- const row = db.prepare("SELECT COUNT(*) AS c FROM tool_owner_backfill_state WHERE status = ?").get(status);
5663
- return row.c;
5664
- }
5665
- function acquireSessionLease(db, sessionId, now) {
5666
- const expiresAt = now + LEASE_DURATION_MS;
5667
- const result = db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, lease_expires_at)
5668
- SELECT ?, 'running', ?, ?
5669
- WHERE EXISTS (SELECT 1 FROM tags WHERE session_id = ?)
5670
- ON CONFLICT(session_id) DO UPDATE SET
5671
- status = 'running',
5672
- started_at = excluded.started_at,
5673
- lease_expires_at = excluded.lease_expires_at,
5674
- last_error = NULL
5675
- WHERE tool_owner_backfill_state.status IN ('pending', 'skipped')
5676
- OR (tool_owner_backfill_state.status = 'running'
5677
- AND tool_owner_backfill_state.lease_expires_at < ?)`).run(sessionId, now, expiresAt, sessionId, now);
5678
- return (result.changes ?? 0) === 1;
5679
- }
5680
- function renewSessionLease(db, sessionId, now) {
5681
- const expiresAt = now + LEASE_DURATION_MS;
5682
- db.prepare(`UPDATE tool_owner_backfill_state
5683
- SET lease_expires_at = ?
5684
- WHERE session_id = ? AND status = 'running'`).run(expiresAt, sessionId);
5685
- }
5686
- function markSessionCompleted(db, sessionId, now) {
5687
- db.prepare(`UPDATE tool_owner_backfill_state
5688
- SET status = 'completed', completed_at = ?, lease_expires_at = NULL, last_error = NULL
5689
- WHERE session_id = ?`).run(now, sessionId);
5690
- }
5691
- function markSessionPendingRetry(db, sessionId) {
5692
- db.prepare(`UPDATE tool_owner_backfill_state
5693
- SET status = 'pending', completed_at = NULL, lease_expires_at = NULL, last_error = NULL
5694
- WHERE session_id = ?`).run(sessionId);
5695
- }
5696
- function markSessionSkipped(db, sessionId, now, reason) {
5697
- db.prepare(`UPDATE tool_owner_backfill_state
5698
- SET status = 'skipped', completed_at = ?, last_error = ?, lease_expires_at = NULL
5699
- WHERE session_id = ? AND status = 'running'`).run(now, reason, sessionId);
5700
- }
5701
- function markSessionErrored(db, sessionId, error) {
5702
- const message = error instanceof Error ? error.message : String(error);
5703
- db.prepare(`UPDATE tool_owner_backfill_state
5704
- SET last_error = ?, lease_expires_at = NULL
5705
- WHERE session_id = ?`).run(message, sessionId);
5706
- }
5707
- function getSessionsNeedingBackfill(db) {
5708
- const rows = db.prepare(`SELECT DISTINCT t.session_id
5709
- FROM tags t
5710
- LEFT JOIN tool_owner_backfill_state s ON s.session_id = t.session_id
5711
- WHERE t.type = 'tool' AND t.tool_owner_message_id IS NULL
5712
- AND (s.status IS NULL OR s.status NOT IN ('completed', 'skipped'))
5713
- ORDER BY t.session_id ASC`).all();
5714
- return rows.map((r) => r.session_id);
5715
- }
5716
- function buildSessionOwnerMap(db, sessionId) {
5717
- const rows = db.prepare(`SELECT
5718
- COALESCE(
5719
- CASE WHEN json_extract(p.data, '$.type') = 'tool_use'
5720
- THEN json_extract(p.data, '$.id')
5721
- END,
5722
- json_extract(p.data, '$.callID')
5723
- ) AS callid,
5724
- m.id AS owner_id,
5725
- m.time_created AS owner_t_created,
5726
- p.id AS part_id,
5727
- p.time_created AS part_t_created
5728
- FROM oc_backfill.message m
5729
- INNER JOIN oc_backfill.part p ON p.message_id = m.id
5730
- WHERE m.session_id = ?
5731
- AND json_extract(m.data, '$.role') = 'assistant'
5732
- AND (
5733
- (json_extract(p.data, '$.type') IN ('tool', 'tool-invocation')
5734
- AND json_extract(p.data, '$.callID') IS NOT NULL)
5735
- OR (json_extract(p.data, '$.type') = 'tool_use'
5736
- AND json_extract(p.data, '$.id') IS NOT NULL)
5737
- )
5738
- ORDER BY
5739
- m.time_created ASC,
5740
- m.id ASC,
5741
- p.time_created ASC,
5742
- p.id ASC`).all(sessionId);
5743
- const oldestByCallId = new Map;
5744
- for (const r of rows) {
5745
- if (typeof r.callid !== "string" || r.callid.length === 0)
5746
- continue;
5747
- if (!oldestByCallId.has(r.callid)) {
5748
- oldestByCallId.set(r.callid, r.owner_id);
5749
- }
5750
- }
5751
- return oldestByCallId;
5752
- }
5753
- function applyOwnersForSession(db, sessionId, ownersByCallId) {
5754
- if (ownersByCallId.size === 0) {
5755
- const leftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
5756
- WHERE session_id = ? AND type = 'tool'
5757
- AND tool_owner_message_id IS NULL`).get(sessionId).c;
5758
- return { rowsUpdated: 0, rowsLeftNull: leftNull };
5759
- }
5760
- const findOrphanStmt = db.prepare(`SELECT id FROM tags
5761
- WHERE session_id = ? AND message_id = ? AND type = 'tool'
5762
- AND tool_owner_message_id IS NULL
5763
- ORDER BY tag_number ASC
5764
- LIMIT 1`);
5765
- const updateRowStmt = db.prepare(`UPDATE tags
5766
- SET tool_owner_message_id = ?
5767
- WHERE id = ? AND tool_owner_message_id IS NULL`);
5768
- const existingOwnerStmt = db.prepare(`SELECT 1 AS hit FROM tags
5769
- WHERE session_id = ? AND message_id = ? AND type = 'tool'
5770
- AND tool_owner_message_id = ?
5771
- LIMIT 1`);
5772
- const ownerForRowStmt = db.prepare("SELECT tool_owner_message_id FROM tags WHERE id = ?");
5773
- let rowsUpdated = 0;
5774
- db.transaction(() => {
5775
- for (const [callId, ownerId] of ownersByCallId) {
5776
- const orphan = findOrphanStmt.get(sessionId, callId);
5777
- if (!orphan)
5778
- continue;
5779
- if (existingOwnerStmt.get(sessionId, callId, ownerId))
5780
- continue;
5781
- updateRowStmt.run(ownerId, orphan.id);
5782
- const updated = ownerForRowStmt.get(orphan.id);
5783
- if (updated?.tool_owner_message_id === ownerId)
5784
- rowsUpdated += 1;
5785
- }
5786
- }).immediate();
5787
- const rowsLeftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
5788
- WHERE session_id = ? AND type = 'tool'
5789
- AND tool_owner_message_id IS NULL`).get(sessionId).c;
5790
- return { rowsUpdated, rowsLeftNull };
5791
- }
5792
- function backfillToolOwnersInChunks(db, result) {
5793
- const sessionIds = getSessionsNeedingBackfill(db);
5794
- let lastRenewedAt = Date.now();
5795
- for (const sessionId of sessionIds) {
5796
- const now = Date.now();
5797
- result.sessionsProcessed += 1;
5798
- const acquired = acquireSessionLease(db, sessionId, now);
5799
- if (!acquired) {
5800
- result.sessionsBlockedByLease += 1;
5801
- continue;
5802
- }
5803
- try {
5804
- const owners = buildSessionOwnerMap(db, sessionId);
5805
- const { rowsUpdated, rowsLeftNull } = applyOwnersForSession(db, sessionId, owners);
5806
- result.rowsUpdated += rowsUpdated;
5807
- result.rowsLeftNull += rowsLeftNull;
5808
- if (owners.size === 0) {
5809
- markSessionSkipped(db, sessionId, Date.now(), "no_oc_matches");
5810
- result.sessionsSkippedNoMatches += 1;
5811
- } else if (rowsLeftNull > 0) {
5812
- markSessionPendingRetry(db, sessionId);
5813
- } else {
5814
- markSessionCompleted(db, sessionId, Date.now());
5815
- result.sessionsCompleted += 1;
5816
- }
5817
- } catch (error) {
5818
- log(`[backfill] session=${sessionId} errored: ${error instanceof Error ? error.message : String(error)}`);
5819
- markSessionErrored(db, sessionId, error);
5820
- result.sessionsErrored += 1;
5821
- }
5822
- const sinceRenew = Date.now() - lastRenewedAt;
5823
- if (sinceRenew > LEASE_RENEWAL_MS) {
5824
- renewSessionLease(db, sessionId, Date.now());
5825
- lastRenewedAt = Date.now();
5826
- }
5827
- }
5828
- }
5829
-
5830
- // src/features/magic-context/storage-db.ts
5831
- registerSlowWriteReporter(logSlowWriteTransaction);
5832
- registerSqliteDiagnosticSink((message) => log(message));
5833
- var databases = new Map;
5834
- var pendingAsyncOpens = new Map;
5835
- var persistenceByDatabase = new WeakMap;
5836
- var persistenceErrorByDatabase = new WeakMap;
5837
- var pathByDatabase = new WeakMap;
5838
- var lastSchemaFenceRejection = null;
5839
- var lastMigrationOnOpenRefusal = null;
5840
- var lastUnconfirmedMigrationHolders = null;
5841
- function getUnconfirmedMigrationHolders() {
5842
- return lastUnconfirmedMigrationHolders;
5843
- }
5844
- function getSchemaFenceRejection() {
5845
- return lastSchemaFenceRejection;
5846
- }
5847
- function getMigrationOnOpenRefusal() {
5848
- return lastMigrationOnOpenRefusal;
5849
- }
5850
- var LATEST_SUPPORTED_VERSION = 95;
5851
- var BOOT_SQLITE_BUSY_TIMEOUT_MS = 5000;
5852
- function ensureSecureStorageDir(dir) {
5853
- ensureStorageDirectorySync(dir);
5854
- }
5855
- function tightenStorageTree(dir) {
5856
- const { failures } = tightenStorageTreeSync(dir);
5857
- log(`[magic-context] storage permission tightening failures=${failures}`);
5858
- }
5859
- function resolveBootBusyTimeoutMs(value) {
5860
- if (value === undefined)
5861
- return BOOT_SQLITE_BUSY_TIMEOUT_MS;
5862
- if (!Number.isFinite(value))
5863
- return BOOT_SQLITE_BUSY_TIMEOUT_MS;
5864
- return Math.max(0, Math.min(BOOT_SQLITE_BUSY_TIMEOUT_MS, Math.floor(value)));
5865
- }
5866
- function installBootBusyTimeout(db, dbPath, timeoutMs, report = log) {
5867
- db.exec(`PRAGMA busy_timeout=${timeoutMs}`);
5868
- report(`[magic-context] SQLite boot busy timeout: backend=${detectSqliteRuntime()} timeout=${timeoutMs}ms path=${dbPath}`);
5869
- }
5870
- function resolveDatabasePath(dbPathOverride) {
5871
- if (dbPathOverride) {
5872
- return { dbDir: dirname(dbPathOverride), dbPath: dbPathOverride };
5873
- }
5874
- const dbDir = getMagicContextStorageDir();
5875
- return { dbDir, dbPath: join2(dbDir, "context.db") };
5876
- }
5877
- function isFileBackedPath(dbPath) {
5878
- const trimmed = dbPath.trim();
5879
- return trimmed !== "" && trimmed !== ":memory:" && !trimmed.startsWith("file:");
5880
- }
5881
- function getDatabasePath(db) {
5882
- return pathByDatabase.get(db) ?? null;
5883
- }
5884
- function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
5885
- if (existsSync3(targetDbPath))
5886
- return;
5887
- const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
5888
- const legacyDbPath = join2(legacyDir, "context.db");
5889
- if (!existsSync3(legacyDbPath))
5890
- return;
5891
- log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
5892
- ensureSecureStorageDir(targetDbDir);
5893
- try {
5894
- const legacyDb = new Database(legacyDbPath);
5895
- try {
5896
- configureContextDatabasePragmas(legacyDb, true);
5897
- legacyDb.exec("PRAGMA wal_checkpoint(TRUNCATE)");
5898
- } finally {
5899
- closeQuietly(legacyDb);
5900
- }
5901
- } catch (error) {
5902
- log(`[magic-context] legacy WAL checkpoint before copy failed (continuing with sidecar copy): ${getErrorMessage(error)}`);
5903
- }
5904
- for (const suffix of ["", "-wal", "-shm"]) {
5905
- const src = `${legacyDbPath}${suffix}`;
5906
- const dst = join2(targetDbDir, `context.db${suffix}`);
5907
- if (existsSync3(src)) {
5908
- try {
5909
- copyStorageFileSync(src, dst);
5910
- } catch (error) {
5911
- log(`[magic-context] failed to copy ${src}:`, getErrorMessage(error));
5912
- }
5913
- }
5914
- }
5915
- const legacyModelsDir = join2(legacyDir, "models");
5916
- const targetModelsDir = join2(targetDbDir, "models");
5917
- if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
5918
- try {
5919
- copyStorageTreeSync(legacyModelsDir, targetModelsDir);
5920
- } catch (error) {
5921
- log("[magic-context] failed to copy embedding model cache:", getErrorMessage(error));
5922
- }
5923
- }
5924
- }
5925
- function getPersistedSchemaVersion(db) {
5926
- const hasMigrationsTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get();
5927
- if (!hasMigrationsTable) {
5928
- return 0;
5929
- }
5930
- const row = db.prepare("SELECT COALESCE(MAX(version), 0) AS version FROM schema_migrations WHERE version < ?").get(FORK_MIGRATION_VERSION_FLOOR);
5931
- return row?.version ?? 0;
5932
- }
5933
- function formatSchemaFenceBootLog(persistedVersion, supportedVersion) {
5934
- return `[magic-context] upstream migration lane at boot: database=v${persistedVersion}, supported_fence=v${supportedVersion}`;
5935
- }
5936
- function getRuntimeLatestSupportedVersion(options) {
5937
- if (options?.latestSupportedVersion !== undefined) {
5938
- return options.latestSupportedVersion;
5939
- }
5940
- const override = process.env.MAGIC_CONTEXT_LATEST_SUPPORTED_VERSION;
5941
- if (override) {
5942
- const parsed = Number.parseInt(override, 10);
5943
- if (Number.isFinite(parsed)) {
5944
- return parsed;
5945
- }
5946
- }
5947
- return LATEST_SUPPORTED_VERSION;
5948
- }
5949
- function enforceSchemaFence(db, dbPath, latestSupportedVersion) {
5950
- const persistedVersion = getPersistedSchemaVersion(db);
5951
- if (persistedVersion <= latestSupportedVersion) {
5952
- lastSchemaFenceRejection = null;
5953
- return true;
5954
- }
5955
- lastSchemaFenceRejection = { persistedVersion, supportedVersion: latestSupportedVersion };
5956
- log(`[magic-context] storage fatal: refusing to open ${dbPath}; upstream migration lane v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}). A pinned or stale plugin is likely sharing this database with a newer instance; update or unpin Magic Context with 'npx @cortexkit/magic-context@latest doctor --force', then restart.`);
5957
- return false;
5958
- }
5959
- function unreadableDiscovery(path, arm) {
5960
- return {
5961
- state: "unreadable",
5962
- serverPids: [],
5963
- staleFiles: [],
5964
- unreadableFile: path,
5965
- unreadableArm: arm
5966
- };
5967
- }
5968
- var RPC_DISCOVERY_PARSE_GRACE_MS = 10 * 60 * 1000;
5969
- var defaultRpcDiscoveryFs = {
5970
- readdirSync: (path, options) => options?.withFileTypes ? readdirSync(path, { withFileTypes: true }) : readdirSync(path),
5971
- readFileSync: (path, encoding) => String(readFileSync3(path, encoding)),
5972
- statSync: (path) => ({ mtimeMs: statSync2(path).mtimeMs }),
5973
- unlinkSync: (path) => unlinkSync(path),
5974
- rmdirSync: (path) => rmdirSync(path)
5975
- };
5976
- var rpcDiscoveryFs = defaultRpcDiscoveryFs;
5977
- function invalidDiscoveryReason(raw) {
5978
- const trimmed = raw.trim();
5979
- if (trimmed.startsWith("{")) {
5980
- try {
5981
- const parsed = JSON.parse(trimmed);
5982
- if ("pid" in parsed) {
5983
- const pid = Number(parsed.pid);
5984
- if (!Number.isInteger(pid) || pid <= 0)
5985
- return "invalid-pid";
5986
- }
5987
- } catch {}
5988
- }
5989
- return "parse-invalid";
5990
- }
5991
- function classifyDiscoveryRecordKind(record) {
5992
- for (const value of [record.kind, record.harness]) {
5993
- const normalized = value?.trim().toLowerCase();
5994
- if (!normalized)
5995
- continue;
5996
- if (normalized === "process")
5997
- return "process";
5998
- if (normalized === "opencode server" || normalized === "server") {
5999
- return "OpenCode server";
6000
- }
6001
- if (normalized === "opencode instance" || normalized === "opencode instance (tui/cli)" || normalized === "opencode" || normalized === "tui" || normalized === "cli") {
6002
- return "OpenCode instance (TUI/CLI)";
6003
- }
6004
- if (normalized === "pi" || normalized === "pi harness" || normalized === "omp" || normalized === "oh-my-pi") {
6005
- return "Pi";
6006
- }
6007
- }
6008
- return null;
6009
- }
6010
- function classifyRpcProcess(record, commandLine) {
6011
- return classifyDiscoveryRecordKind(record) ?? classifyProcessKind(commandLine === undefined ? readProcessProbeEvidence(record.pid).commandLine : commandLine);
6012
- }
6013
- function classifyJunkDiscovery(portFile, raw, staleFiles) {
6014
- let mtimeMs;
6015
- try {
6016
- mtimeMs = rpcDiscoveryFs.statSync(portFile).mtimeMs;
6017
- } catch (error) {
6018
- if (error.code === "ENOENT")
6019
- return null;
6020
- return unreadableDiscovery(portFile, "io");
6021
- }
6022
- const ageMs = Date.now() - mtimeMs;
6023
- if (!Number.isFinite(ageMs) || ageMs < RPC_DISCOVERY_PARSE_GRACE_MS) {
6024
- return unreadableDiscovery(portFile, "parse");
6025
- }
6026
- staleFiles.push(portFile);
6027
- const reason = invalidDiscoveryReason(raw);
6028
- log(`[magic-context] removing stale RPC discovery file ${portFile}: ${reason} record older than 10 minutes`);
6029
- return null;
6030
- }
6031
- function inspectRpcServerDiscovery(storageDir, processes, options) {
6032
- const deadline = options?.deadlineMs === undefined ? Number.POSITIVE_INFINITY : Date.now() + options.deadlineMs;
6033
- let progressAt = Date.now() + 3000;
6034
- const rpcRoot = join2(storageDir, "rpc");
6035
- let projectEntries;
6036
- try {
6037
- projectEntries = rpcDiscoveryFs.readdirSync(rpcRoot, { withFileTypes: true });
6038
- } catch (error) {
6039
- if (error.code === "ENOENT") {
6040
- return { state: "absent", serverPids: [], staleFiles: [] };
6041
- }
6042
- return unreadableDiscovery(rpcRoot, "io");
6043
- }
6044
- const portFiles = [];
6045
- for (const projectEntry of projectEntries) {
6046
- if (!projectEntry.isDirectory())
6047
- continue;
6048
- const projectDir = join2(rpcRoot, projectEntry.name);
6049
- let entries;
6050
- try {
6051
- entries = rpcDiscoveryFs.readdirSync(projectDir);
6052
- } catch (error) {
6053
- if (error.code === "ENOENT")
6054
- continue;
6055
- return unreadableDiscovery(projectDir, "io");
6056
- }
6057
- for (const entry of entries) {
6058
- if (entry === "port" || entry.startsWith("port-") && entry.endsWith(".json")) {
6059
- portFiles.push(join2(projectDir, entry));
6060
- }
6061
- }
6062
- }
6063
- if (portFiles.length === 0) {
6064
- return { state: "absent", serverPids: [], staleFiles: [] };
6065
- }
6066
- if (!processes && process.platform === "win32")
6067
- processes = inspectWindowsProcessesSync();
6068
- const pids = new Set;
6069
- const processByPid = new Map;
6070
- const staleFiles = [];
6071
- const inconclusivePids = new Set;
6072
- const inconclusiveRecords = [];
6073
- for (const [index, portFile] of portFiles.entries()) {
6074
- if (Date.now() >= deadline)
6075
- throw new Error(`RPC holder inspection timed out after ${Math.round((options?.deadlineMs ?? 0) / 1000)} seconds (${index}/${portFiles.length} records checked). Close OpenCode and Pi, then retry.`);
6076
- if (options?.onProgress && Date.now() >= progressAt) {
6077
- options.onProgress(index, portFiles.length);
6078
- progressAt = Date.now() + 3000;
6079
- }
6080
- let raw;
6081
- try {
6082
- raw = rpcDiscoveryFs.readFileSync(portFile, "utf8");
6083
- } catch (error) {
6084
- if (error.code === "ENOENT")
6085
- continue;
6086
- return unreadableDiscovery(portFile, "io");
6087
- }
6088
- const filename = basename(portFile);
6089
- const pidFromName = /^port-(\d+)/.exec(filename)?.[1];
6090
- const fallbackPid = pidFromName ? Number(pidFromName) : 0;
6091
- const record = parseRpcPortFile(raw, fallbackPid);
6092
- if (!record || !Number.isInteger(record.pid) || record.pid <= 0) {
6093
- const junk = classifyJunkDiscovery(portFile, raw, staleFiles);
6094
- if (junk)
6095
- return junk;
6096
- continue;
6097
- }
6098
- if (isOwnRpcServerRecord(record))
6099
- continue;
6100
- const liveness = processes ? processes.liveness(record.pid) : isPidAlive(record.pid);
6101
- if (liveness === "dead") {
6102
- staleFiles.push(portFile);
6103
- continue;
6104
- }
6105
- const evidence = processes ? processes.evidence(record.pid) : readProcessProbeEvidence(record.pid);
6106
- const identity = isPidIdentityPlausible(record, evidence);
6107
- if (identity === "plausible") {
6108
- pids.add(record.pid);
6109
- const detected = attachFailClosedBlockingProcessEvidence({
6110
- kind: classifyRpcProcess(record, evidence.commandLine),
6111
- pid: record.pid
6112
- }, evidence);
6113
- const previous = processByPid.get(record.pid);
6114
- if (!previous || previous.kind === "process" && detected.kind !== "process") {
6115
- processByPid.set(record.pid, detected);
6116
- }
6117
- } else if (identity === "implausible") {
6118
- staleFiles.push(portFile);
6119
- } else {
6120
- inconclusivePids.add(record.pid);
6121
- inconclusiveRecords.push({
6122
- file: portFile,
6123
- pid: record.pid,
6124
- recordedStartedAt: record.started_at > 0 ? record.started_at : null,
6125
- processStartTime: evidence.startTime,
6126
- imageName: evidence.imageName ?? null,
6127
- commandLine: evidence.commandLine,
6128
- liveness
6129
- });
6130
- }
6131
- }
6132
- for (const staleFile of staleFiles) {
6133
- try {
6134
- rpcDiscoveryFs.unlinkSync(staleFile);
6135
- } catch {
6136
- return unreadableDiscovery(staleFile, "io");
6137
- }
6138
- }
6139
- const serverPids = [...pids].sort((a, b) => a - b);
6140
- if (serverPids.length > 0) {
6141
- return {
6142
- state: "live",
6143
- serverPids,
6144
- serverProcesses: serverPids.map((pid) => processByPid.get(pid) ?? { kind: "process", pid }),
6145
- staleFiles,
6146
- ...inconclusiveRecords.length > 0 ? { inconclusiveRecords } : {}
6147
- };
6148
- }
6149
- const uncertainPids = [...inconclusivePids].sort((a, b) => a - b);
6150
- if (uncertainPids.length > 0) {
6151
- return {
6152
- state: "inconclusive",
6153
- serverPids: [],
6154
- staleFiles,
6155
- inconclusivePids: uncertainPids,
6156
- inconclusiveRecords
6157
- };
6158
- }
6159
- return { state: "stale", serverPids: [], staleFiles };
6160
- }
6161
- function createPiBlockingProcess(pid) {
6162
- return attachFailClosedBlockingProcessEvidence({ kind: "Pi", pid }, readProcessProbeEvidence(pid));
6163
- }
6164
- function formatInconclusiveOpenCodeMigrationWarning(dbPath, pids) {
6165
- return `[magic-context] storage warning: continuing migration for ${dbPath}; OpenCode server PID ${pids.join(", ")} was not confirmed because its liveness or identity check could not run. This commonly means an OS sandbox denied kill(0) or ps. No live OpenCode server was confirmed.`;
6166
- }
6167
- function formatInconclusivePiMigrationWarning(dbPath, pids) {
6168
- return `[magic-context] storage warning: continuing migration for ${dbPath}; Pi/OMP PID ${pids.join(", ")} was not confirmed as a live harness because the process image or command line was ambiguous. No live Pi harness was confirmed.`;
6169
- }
6170
- function logInconclusiveMigrationProbes(dbPath, discovery, piDiscovery) {
6171
- const uncertainPids = discovery.inconclusivePids ?? [];
6172
- if (uncertainPids.length > 0) {
6173
- log(formatInconclusiveOpenCodeMigrationWarning(dbPath, uncertainPids));
6174
- }
6175
- if (piDiscovery.state === "unreadable") {
6176
- log(`[magic-context] storage warning: continuing migration for ${dbPath}; the Pi/OMP process-list probe could not run, which commonly means an OS sandbox denied ps. No live Pi harness was confirmed.`);
6177
- } else if ((piDiscovery.inconclusivePids?.length ?? 0) > 0) {
6178
- log(formatInconclusivePiMigrationWarning(dbPath, piDiscovery.inconclusivePids ?? []));
6179
- }
6180
- }
6181
- function isDefaultSharedDatabasePath(dbPath) {
6182
- if (!process.env.XDG_DATA_HOME && (process.env.MAGIC_CONTEXT_TEST_DATA_DIR || false)) {
6183
- return false;
6184
- }
6185
- return resolve(dbPath) === resolve(join2(getMagicContextStorageDir(), "context.db"));
6186
- }
6187
- function migrationBlockingPiPids(dbPath, discovery, discoveredPiPids) {
6188
- if (isDefaultSharedDatabasePath(dbPath))
6189
- return [...discoveredPiPids];
6190
- const sameDataDirPids = new Set(discovery.serverPids);
6191
- return discoveredPiPids.filter((pid) => sameDataDirPids.has(pid));
6192
- }
6193
- function formatLiveProcessMigrationRefusal(dbPath, persistedVersion, latestSupportedVersion, serverPids, piPids) {
6194
- const blockers = [
6195
- ...serverPids.map((pid) => `confirmed OpenCode server PID ${pid}`),
6196
- ...piPids.map((pid) => `confirmed Pi harness PID ${pid}`)
6197
- ];
6198
- return `[magic-context] storage fatal: refusing to migrate ${dbPath} from upstream migration v${persistedVersion} to v${latestSupportedVersion} while ${blockers.join(", ")} still use the old plugin build. Restart the blocking harness, then retry this process.`;
6199
- }
6200
- function enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion, processes) {
6201
- const persistedVersion = getPersistedSchemaVersion(db);
6202
- if (persistedVersion >= latestSupportedVersion) {
6203
- lastMigrationOnOpenRefusal = null;
6204
- return true;
6205
- }
6206
- const discovery = inspectRpcServerDiscovery(dbDir, processes);
6207
- const piDiscovery = processes?.pi ?? inspectLivePiProcesses();
6208
- const piPids = migrationBlockingPiPids(dbPath, discovery, piDiscovery.processIds);
6209
- const serverProcesses = discovery.serverProcesses ?? (discovery.state === "live" ? discovery.serverPids.map((pid) => ({ kind: "process", pid })) : []);
6210
- const blockingProcesses = [
6211
- ...serverProcesses,
6212
- ...piPids.map((pid) => processes ? attachFailClosedBlockingProcessEvidence({ kind: "Pi", pid }, processes.evidence(pid)) : createPiBlockingProcess(pid))
6213
- ];
6214
- if ((discovery.state === "absent" || discovery.state === "stale" || discovery.state === "inconclusive") && piPids.length === 0) {
6215
- lastMigrationOnOpenRefusal = null;
6216
- logInconclusiveMigrationProbes(dbPath, discovery, piDiscovery);
6217
- const uncertainPids = discovery.inconclusivePids ?? [];
6218
- if (uncertainPids.length > 0) {
6219
- lastUnconfirmedMigrationHolders = {
6220
- pids: [...uncertainPids],
6221
- fromVersion: persistedVersion,
6222
- toVersion: latestSupportedVersion
6223
- };
6224
- }
6225
- return true;
6226
- }
6227
- const blockingPids = [...new Set([...discovery.serverPids, ...piPids])].sort((left, right) => left - right);
6228
- lastMigrationOnOpenRefusal = {
6229
- persistedVersion,
6230
- supportedVersion: latestSupportedVersion,
6231
- serverPids: blockingPids,
6232
- blockingProcesses,
6233
- ...discovery.unreadableFile ? { unreadableFile: discovery.unreadableFile } : {},
6234
- ...discovery.unreadableArm ? { unreadableArm: discovery.unreadableArm } : {}
6235
- };
6236
- if (discovery.state === "unreadable") {
6237
- const unreadableFile = discovery.unreadableFile ?? "<unknown>";
6238
- const arm = discovery.unreadableArm ?? "io";
6239
- const recovery = arm === "io" ? `If no OpenCode server is running, it is safe to delete ${unreadableFile} and retry.` : `Retry after the file is older than the ten-minute grace window, or stop OpenCode before deleting it.`;
6240
- log(`[magic-context] storage fatal: refusing to migrate ${dbPath} from upstream migration v${persistedVersion} to v${latestSupportedVersion} because RPC discovery file ${unreadableFile} is uncertain (${arm} arm), so the absence of a live OpenCode server cannot be proven. ${recovery}`);
6241
- } else {
6242
- log(formatLiveProcessMigrationRefusal(dbPath, persistedVersion, latestSupportedVersion, discovery.serverPids, piPids));
6243
- }
6244
- return false;
6245
- }
6246
- var sqlitePragmaConfig = {
6247
- cacheSizeMb: 64,
6248
- mmapSizeMb: 0
6249
- };
6250
- function setSqlitePragmaConfig(config) {
6251
- sqlitePragmaConfig = config;
6252
- }
6253
- function applySqliteTuningPragmas(db) {
6254
- db.exec(`PRAGMA cache_size=-${Math.round(sqlitePragmaConfig.cacheSizeMb * 1024)}`);
6255
- db.exec(`PRAGMA mmap_size=${Math.round(sqlitePragmaConfig.mmapSizeMb * 1024 * 1024)}`);
6256
- db.exec("PRAGMA analysis_limit=400");
6257
- }
6258
- function runSqliteOptimize(db) {
6259
- try {
6260
- db.exec("PRAGMA analysis_limit=400");
6261
- db.exec("PRAGMA optimize");
6262
- } catch {}
6263
- }
6264
- function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion) {
6265
- if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
6266
- closeQuietly(db);
6267
- return null;
6268
- }
6269
- healWedgedChannel2Claims(db);
6270
- if (!explicitDbPath) {
6271
- const readsOpenCodeStore = harnessOwnsOpenCodeStore();
6272
- let busyRetryMs = 100;
6273
- const runBackfills = () => withoutSqliteTransformPass(() => withSqliteBackgroundWriter(() => {
6274
- if (readsOpenCodeStore) {
6275
- try {
6276
- runToolOwnerBackfill(db);
6277
- } catch (error) {
6278
- log(`[magic-context] tool-owner backfill failed (continuing with lazy adoption fallback): ${getErrorMessage(error)}`);
6279
- }
6280
- }
6281
- startMessageFtsRowidMapBackfill(db).then(async () => {
6282
- if (!readsOpenCodeStore)
6283
- return;
6284
- const { readRawSessionMessagePage, readRawSessionMessages } = await importPluginModule(() => import("./read-session-chunk-egz7r68d.js"));
6285
- const { startMessageTimeBackfill } = await importPluginModule(() => import("./message-time-backfill-68gj6t3e.js"));
6286
- await startMessageTimeBackfill(db, Object.assign(readRawSessionMessages, {
6287
- readPage: readRawSessionMessagePage
6288
- }));
6289
- }).catch((error) => {
6290
- if (isTransientSqliteError(error)) {
6291
- withoutSqliteTransformPass(() => {
6292
- const timer = setTimeout(runBackfills, busyRetryMs);
6293
- timer.unref();
6294
- });
6295
- busyRetryMs = Math.min(30000, busyRetryMs * 2);
6296
- return;
6297
- }
6298
- log(`[magic-context] message-index backfill failed (will resume next startup): ${getErrorMessage(error)}`);
6299
- });
6300
- }));
6301
- if (bootQuietRemainingMs() > 0)
6302
- scheduleAfterBootQuiet(runBackfills);
6303
- else
6304
- runBackfills();
6305
- }
6306
- setDatabase(db);
6307
- loadToolDefinitionMeasurements(db);
6308
- tightenStorageTree(dirname(dbPath));
6309
- databases.set(dbPath, db);
6310
- pathByDatabase.set(db, dbPath);
6311
- persistenceByDatabase.set(db, true);
6312
- persistenceErrorByDatabase.delete(db);
6313
- if (!explicitDbPath) {
6314
- log(formatSchemaFenceBootLog(getPersistedSchemaVersion(db), latestSupportedVersion));
6315
- }
6316
- return db;
6317
- }
6318
- function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
6319
- db.exec(`PRAGMA busy_timeout=${resolveBootBusyTimeoutMs(busyTimeoutMs)}`);
6320
- db.exec("PRAGMA foreign_keys=ON");
6321
- configureContextDatabasePragmas(db);
6322
- applySqliteTuningPragmas(db);
6323
- db.exec(`
6324
- CREATE TABLE IF NOT EXISTS tags (
6325
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6326
- session_id TEXT,
6327
- message_id TEXT,
6328
- type TEXT,
6329
- status TEXT DEFAULT 'active',
6330
- byte_size INTEGER,
6331
- tag_number INTEGER,
6332
- harness TEXT NOT NULL DEFAULT 'opencode',
6333
- entry_fingerprint TEXT,
6334
- token_count INTEGER,
6335
- input_token_count INTEGER,
6336
- reasoning_token_count INTEGER,
6337
- UNIQUE(session_id, tag_number)
6338
- );
6339
-
6340
- CREATE TABLE IF NOT EXISTS pending_ops (
6341
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6342
- session_id TEXT,
6343
- tag_id INTEGER,
6344
- operation TEXT,
6345
- queued_at INTEGER,
6346
- harness TEXT NOT NULL DEFAULT 'opencode'
6347
- );
6348
-
6349
- CREATE TABLE IF NOT EXISTS source_contents (
6350
- tag_id INTEGER,
6351
- session_id TEXT,
6352
- content TEXT,
6353
- created_at INTEGER, -- epoch ms; Date.now() on source writes, preserved on session clones
6354
- harness TEXT NOT NULL DEFAULT 'opencode',
6355
- PRIMARY KEY(session_id, tag_id)
6356
- );
6357
-
6358
- CREATE TABLE IF NOT EXISTS compartments (
6359
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6360
- session_id TEXT NOT NULL,
6361
- sequence INTEGER NOT NULL,
6362
- start_message INTEGER NOT NULL,
6363
- end_message INTEGER NOT NULL,
6364
- start_message_id TEXT DEFAULT '',
6365
- end_message_id TEXT DEFAULT '',
6366
- start_block_index INTEGER,
6367
- end_block_index INTEGER,
6368
- title TEXT NOT NULL,
6369
- content TEXT NOT NULL,
6370
- p1 TEXT,
6371
- p2 TEXT,
6372
- p3 TEXT,
6373
- p4 TEXT,
6374
- importance INTEGER NOT NULL DEFAULT 50,
6375
- episode_type TEXT,
6376
- p1_embedding BLOB,
6377
- p1_embedding_model_id TEXT,
6378
- legacy INTEGER NOT NULL DEFAULT 0,
6379
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
6380
- harness TEXT NOT NULL DEFAULT 'opencode',
6381
- UNIQUE(session_id, sequence)
6382
- );
6383
-
6384
- CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
6385
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6386
- compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
6387
- session_id TEXT NOT NULL,
6388
- project_path TEXT NOT NULL,
6389
- harness TEXT NOT NULL DEFAULT 'opencode',
6390
- window_index INTEGER NOT NULL DEFAULT 0,
6391
- start_ordinal INTEGER NOT NULL,
6392
- end_ordinal INTEGER NOT NULL,
6393
- chunk_hash TEXT NOT NULL,
6394
- model_id TEXT NOT NULL,
6395
- dims INTEGER NOT NULL,
6396
- vector BLOB NOT NULL,
6397
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
6398
- UNIQUE(compartment_id, model_id, window_index)
6399
- );
6400
- CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
6401
- CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
6402
-
6403
- CREATE TABLE IF NOT EXISTS session_projects (
6404
- session_id TEXT NOT NULL,
6405
- harness TEXT NOT NULL DEFAULT 'opencode',
6406
- project_path TEXT NOT NULL,
6407
- updated_at INTEGER NOT NULL,
6408
- PRIMARY KEY(session_id, harness)
6409
- );
6410
- CREATE INDEX IF NOT EXISTS idx_session_projects_project
6411
- ON session_projects(project_path);
6412
-
6413
- CREATE TABLE IF NOT EXISTS compartment_events (
6414
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6415
- session_id TEXT NOT NULL,
6416
- compartment_id INTEGER,
6417
- kind TEXT NOT NULL,
6418
- at_compartment INTEGER,
6419
- fields_json TEXT NOT NULL DEFAULT '{}',
6420
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
6421
- harness TEXT NOT NULL DEFAULT 'opencode'
6422
- );
6423
- CREATE INDEX IF NOT EXISTS idx_compartment_events_session
6424
- ON compartment_events(session_id);
6425
-
6426
- CREATE TABLE IF NOT EXISTS compartment_state_lease (
6427
- session_id TEXT PRIMARY KEY NOT NULL,
6428
- holder_id TEXT NOT NULL,
6429
- owner_pid INTEGER,
6430
- acquired_at INTEGER NOT NULL,
6431
- expires_at INTEGER NOT NULL
6432
- );
6433
- CREATE INDEX IF NOT EXISTS idx_compartment_state_lease_expires
6434
- ON compartment_state_lease(expires_at);
6435
-
6436
- CREATE TABLE IF NOT EXISTS compression_depth (
6437
- session_id TEXT NOT NULL,
6438
- message_ordinal INTEGER NOT NULL,
6439
- depth INTEGER NOT NULL DEFAULT 0,
6440
- harness TEXT NOT NULL DEFAULT 'opencode',
6441
- PRIMARY KEY(session_id, message_ordinal)
6442
- );
6443
-
6444
- CREATE TABLE IF NOT EXISTS session_facts (
6445
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6446
- session_id TEXT NOT NULL,
6447
- category TEXT NOT NULL,
6448
- content TEXT NOT NULL,
6449
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
6450
- updated_at INTEGER NOT NULL,
6451
- harness TEXT NOT NULL DEFAULT 'opencode'
6452
- );
6453
-
6454
- CREATE TABLE IF NOT EXISTS primer_candidates (
6455
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6456
- project_path TEXT NOT NULL,
6457
- harness TEXT NOT NULL DEFAULT 'opencode',
6458
- session_id TEXT NOT NULL,
6459
- question TEXT NOT NULL,
6460
- normalized_question TEXT NOT NULL,
6461
- source_compartment_start INTEGER,
6462
- source_compartment_end INTEGER,
6463
- source_start_message_id TEXT NOT NULL DEFAULT '',
6464
- source_end_message_id TEXT NOT NULL DEFAULT '',
6465
- source_message_time INTEGER NOT NULL,
6466
- question_embedding BLOB,
6467
- question_embedding_model_id TEXT,
6468
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
6469
- UNIQUE(project_path, harness, session_id, source_start_message_id, source_end_message_id)
6470
- );
6471
- CREATE INDEX IF NOT EXISTS idx_primer_candidates_project_time
6472
- ON primer_candidates(project_path, source_message_time);
6473
- CREATE INDEX IF NOT EXISTS idx_primer_candidates_session
6474
- ON primer_candidates(session_id, harness);
6475
- CREATE INDEX IF NOT EXISTS idx_primer_candidates_embedding_model
6476
- ON primer_candidates(project_path, question_embedding_model_id);
6477
-
6478
- CREATE TABLE IF NOT EXISTS primers (
6479
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6480
- project_path TEXT NOT NULL,
6481
- question TEXT NOT NULL,
6482
- question_embedding BLOB,
6483
- question_embedding_model_id TEXT,
6484
- answer TEXT NOT NULL DEFAULT '',
6485
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived')),
6486
- total_support INTEGER NOT NULL DEFAULT 0,
6487
- last_observed_at INTEGER,
6488
- answer_refreshed_at INTEGER,
6489
- source_candidate_ids TEXT NOT NULL DEFAULT '[]',
6490
- source_candidate_provenance TEXT,
6491
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
6492
- updated_at INTEGER NOT NULL
6493
- );
6494
- CREATE INDEX IF NOT EXISTS idx_primers_project_status_observed
6495
- ON primers(project_path, status, last_observed_at DESC);
6496
- CREATE INDEX IF NOT EXISTS idx_primers_embedding_model
6497
- ON primers(project_path, question_embedding_model_id);
6498
-
6499
- CREATE VIRTUAL TABLE IF NOT EXISTS primers_fts USING fts5(
6500
- question,
6501
- answer,
6502
- project_path UNINDEXED,
6503
- content='primers',
6504
- content_rowid='id',
6505
- tokenize='porter unicode61'
6506
- );
6507
-
6508
- CREATE TRIGGER IF NOT EXISTS primers_ai AFTER INSERT ON primers BEGIN
6509
- INSERT INTO primers_fts(rowid, question, answer, project_path)
6510
- VALUES (new.id, new.question, new.answer, new.project_path);
6511
- END;
6512
-
6513
- CREATE TRIGGER IF NOT EXISTS primers_ad AFTER DELETE ON primers BEGIN
6514
- INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
6515
- VALUES ('delete', old.id, old.question, old.answer, old.project_path);
6516
- END;
6517
-
6518
- CREATE TRIGGER IF NOT EXISTS primers_au AFTER UPDATE ON primers BEGIN
6519
- INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
6520
- VALUES ('delete', old.id, old.question, old.answer, old.project_path);
6521
- INSERT INTO primers_fts(rowid, question, answer, project_path)
6522
- VALUES (new.id, new.question, new.answer, new.project_path);
6523
- END;
6524
-
6525
- -- session_notes and smart_notes were merged into the unified notes table
6526
- -- by migration v1 (see features/magic-context/migrations.ts). The old tables
6527
- -- are never recreated; fresh DBs create only notes, upgraded DBs have
6528
- -- their old tables migrated and dropped by the migration runner.
6529
-
6530
- CREATE TABLE IF NOT EXISTS memories (
6531
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6532
- project_path TEXT NOT NULL,
6533
- category TEXT NOT NULL,
6534
- content TEXT NOT NULL,
6535
- normalized_hash TEXT NOT NULL,
6536
- importance INTEGER,
6537
- scope TEXT NOT NULL DEFAULT 'project',
6538
- shareable INTEGER NOT NULL DEFAULT 0,
6539
- source_session_id TEXT,
6540
- source_type TEXT DEFAULT 'historian',
6541
- seen_count INTEGER DEFAULT 1,
6542
- retrieval_count INTEGER DEFAULT 0,
6543
- first_seen_at INTEGER NOT NULL,
6544
- created_at INTEGER NOT NULL,
6545
- updated_at INTEGER NOT NULL,
6546
- last_seen_at INTEGER NOT NULL,
6547
- last_retrieved_at INTEGER,
6548
- status TEXT DEFAULT 'active',
6549
- expires_at INTEGER,
6550
- verification_status TEXT DEFAULT 'unverified',
6551
- verified_at INTEGER,
6552
- classified_at INTEGER,
6553
- superseded_by_memory_id INTEGER,
6554
- merged_from TEXT,
6555
- metadata_json TEXT,
6556
- mural_cue TEXT,
6557
- mural_cue_hash TEXT,
6558
- mural_cue_at INTEGER,
6559
- mural_cue_rejection_count INTEGER NOT NULL DEFAULT 0,
6560
- UNIQUE(project_path, category, normalized_hash)
6561
- );
6562
-
6563
- CREATE TABLE IF NOT EXISTS memory_embeddings (
6564
- -- FK-cascade audit (v12): memory_embeddings.memory_id -> memories.id
6565
- -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
6566
- -- every connection and v12 cleans historical orphan rows.
6567
- memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
6568
- embedding BLOB NOT NULL,
6569
- model_id TEXT NOT NULL,
6570
- PRIMARY KEY(memory_id, model_id)
6571
- );
6572
-
6573
- CREATE TABLE IF NOT EXISTS embedding_identity_active (
6574
- project_path TEXT NOT NULL,
6575
- scope TEXT NOT NULL CHECK(scope IN ('memory', 'commit', 'chunk')),
6576
- model_id TEXT NOT NULL,
6577
- last_active_at INTEGER NOT NULL,
6578
- PRIMARY KEY(project_path, scope, model_id)
6579
- );
6580
-
6581
- CREATE TABLE IF NOT EXISTS embedding_registrations (
6582
- project_path TEXT PRIMARY KEY,
6583
- provider_identity TEXT NOT NULL DEFAULT '',
6584
- model_id TEXT NOT NULL DEFAULT '',
6585
- chunk_model_id TEXT NOT NULL DEFAULT '',
6586
- fingerprint TEXT NOT NULL DEFAULT '',
6587
- table_epoch INTEGER NOT NULL DEFAULT 0,
6588
- dims INTEGER NOT NULL DEFAULT 0,
6589
- provenance_json TEXT NOT NULL DEFAULT '{}',
6590
- generation INTEGER NOT NULL DEFAULT 0,
6591
- updated_at INTEGER NOT NULL DEFAULT 0
6592
- );
6593
-
6594
- CREATE TABLE IF NOT EXISTS synapse_batch_ledger (
6595
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6596
- session_id TEXT NOT NULL,
6597
- project_path TEXT NOT NULL DEFAULT '',
6598
- scope TEXT NOT NULL DEFAULT '',
6599
- manifest_json TEXT NOT NULL DEFAULT '{}',
6600
- request_key TEXT NOT NULL DEFAULT '',
6601
- job_id TEXT,
6602
- cursor TEXT,
6603
- status TEXT NOT NULL DEFAULT 'pending',
6604
- created_at INTEGER NOT NULL DEFAULT 0, -- epoch ms (Date.now())
6605
- updated_at INTEGER NOT NULL DEFAULT 0,
6606
- UNIQUE(session_id, request_key)
6607
- );
6608
- CREATE INDEX IF NOT EXISTS idx_synapse_batch_ledger_session
6609
- ON synapse_batch_ledger(session_id, updated_at);
6610
-
6611
- CREATE TABLE IF NOT EXISTS shadow_embedding_registrations (
6612
- project_path TEXT NOT NULL,
6613
- scope TEXT NOT NULL CHECK(scope IN ('memory', 'commit', 'chunk')),
6614
- model_id TEXT NOT NULL,
6615
- generation INTEGER NOT NULL DEFAULT 0,
6616
- fingerprint TEXT NOT NULL DEFAULT '',
6617
- table_epoch INTEGER NOT NULL DEFAULT 0,
6618
- dims INTEGER NOT NULL DEFAULT 0,
6619
- provenance_json TEXT NOT NULL DEFAULT '{}',
6620
- updated_at INTEGER NOT NULL DEFAULT 0,
6621
- PRIMARY KEY(project_path, scope, model_id)
6622
- );
6623
-
6624
- CREATE TABLE IF NOT EXISTS embedding_measurement_corpus (
6625
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6626
- session_id TEXT NOT NULL,
6627
- project_path TEXT NOT NULL DEFAULT '',
6628
- dedup_key TEXT NOT NULL DEFAULT '',
6629
- cohort_key TEXT NOT NULL DEFAULT '',
6630
- query_text_hash TEXT NOT NULL DEFAULT '',
6631
- primary_result_ids_json TEXT NOT NULL DEFAULT '[]',
6632
- shadow_result_ids_json TEXT NOT NULL DEFAULT '[]',
6633
- primary_latency_ms INTEGER,
6634
- shadow_latency_ms INTEGER,
6635
- primary_failed INTEGER NOT NULL DEFAULT 0,
6636
- shadow_failed INTEGER NOT NULL DEFAULT 0,
6637
- primary_model_id TEXT NOT NULL DEFAULT '',
6638
- shadow_model_id TEXT NOT NULL DEFAULT '',
6639
- primary_fingerprint TEXT NOT NULL DEFAULT '',
6640
- shadow_fingerprint TEXT NOT NULL DEFAULT '',
6641
- primary_epoch INTEGER NOT NULL DEFAULT 0,
6642
- shadow_epoch INTEGER NOT NULL DEFAULT 0,
6643
- corpus_hash TEXT NOT NULL DEFAULT '',
6644
- coverage_json TEXT NOT NULL DEFAULT '{}',
6645
- created_at INTEGER NOT NULL DEFAULT 0, -- epoch ms (Date.now())
6646
- UNIQUE(dedup_key, cohort_key)
6647
- );
6648
- CREATE INDEX IF NOT EXISTS idx_embedding_measurement_session
6649
- ON embedding_measurement_corpus(session_id, created_at);
6650
-
6651
- CREATE TABLE IF NOT EXISTS memory_verifications (
6652
- memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
6653
- file_path TEXT NOT NULL,
6654
- -- verified_at=0 means "mapped (files known) but not yet content-verified".
6655
- -- map-memories sets mapped_at + verified_at=0; verify sets verified_at=now.
6656
- verified_at INTEGER NOT NULL,
6657
- mapped_at INTEGER NOT NULL DEFAULT 0,
6658
- -- Distinguishes mapper-authored independence from a host rejection fallback.
6659
- mapping_origin TEXT NOT NULL DEFAULT 'mapper',
6660
- PRIMARY KEY (memory_id, file_path)
6661
- );
6662
- CREATE INDEX IF NOT EXISTS idx_memory_verifications_memory ON memory_verifications(memory_id);
6663
-
6664
- CREATE TABLE IF NOT EXISTS memory_mutation_log (
6665
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6666
- project_path TEXT NOT NULL,
6667
- mutation_type TEXT NOT NULL CHECK (mutation_type IN ('archive', 'delete', 'update', 'superseded')),
6668
- target_memory_id INTEGER NOT NULL,
6669
- superseded_by_id INTEGER,
6670
- category TEXT,
6671
- new_content TEXT,
6672
- queued_at INTEGER NOT NULL
6673
- );
6674
- CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_project
6675
- ON memory_mutation_log(project_path, id);
6676
- CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_visibility
6677
- ON memory_mutation_log(project_path, category, id, target_memory_id);
6678
- CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_target
6679
- ON memory_mutation_log(project_path, target_memory_id, id);
6680
-
6681
- CREATE TABLE IF NOT EXISTS dream_state (
6682
- key TEXT PRIMARY KEY,
6683
- value TEXT NOT NULL
6684
- );
6685
-
6686
- CREATE TABLE IF NOT EXISTS dream_queue (
6687
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6688
- project_path TEXT NOT NULL,
6689
- reason TEXT NOT NULL,
6690
- enqueued_at INTEGER NOT NULL,
6691
- started_at INTEGER,
6692
- retry_count INTEGER DEFAULT 0
6693
- );
6694
- CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
6695
- CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
6696
-
6697
- CREATE TABLE IF NOT EXISTS dream_runs (
6698
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6699
- project_path TEXT NOT NULL,
6700
- started_at INTEGER NOT NULL,
6701
- finished_at INTEGER NOT NULL,
6702
- holder_id TEXT NOT NULL,
6703
- tasks_json TEXT NOT NULL,
6704
- tasks_succeeded INTEGER NOT NULL DEFAULT 0,
6705
- tasks_failed INTEGER NOT NULL DEFAULT 0,
6706
- smart_notes_surfaced INTEGER NOT NULL DEFAULT 0,
6707
- smart_notes_pending INTEGER NOT NULL DEFAULT 0,
6708
- memory_changes_json TEXT,
6709
- parent_session_id TEXT
6710
- );
6711
- CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
6712
-
6713
- CREATE TABLE IF NOT EXISTS task_schedule_state (
6714
- project_path TEXT NOT NULL,
6715
- task TEXT NOT NULL,
6716
- last_run_at INTEGER,
6717
- next_due_at INTEGER,
6718
- schedule TEXT,
6719
- last_status TEXT,
6720
- last_error TEXT,
6721
- last_checked_commit TEXT,
6722
- last_broad_run_at INTEGER,
6723
- retrospective_watermark_ms INTEGER,
6724
- retry_count INTEGER NOT NULL DEFAULT 0,
6725
- PRIMARY KEY (project_path, task)
6726
- );
6727
- CREATE INDEX IF NOT EXISTS idx_task_schedule_due ON task_schedule_state(next_due_at);
6728
-
6729
- CREATE TABLE IF NOT EXISTS retrospective_processed_windows (
6730
- project_path TEXT NOT NULL,
6731
- window_key TEXT NOT NULL,
6732
- processed_at INTEGER NOT NULL,
6733
- PRIMARY KEY (project_path, window_key)
6734
- );
6735
-
6736
- CREATE TABLE IF NOT EXISTS project_key_files (
6737
- project_path TEXT NOT NULL,
6738
- path TEXT NOT NULL,
6739
- content TEXT NOT NULL,
6740
- content_hash TEXT NOT NULL,
6741
- local_token_estimate INTEGER NOT NULL,
6742
- generated_at INTEGER NOT NULL,
6743
- generated_by_model TEXT,
6744
- generation_config_hash TEXT NOT NULL,
6745
- stale_reason TEXT,
6746
- PRIMARY KEY (project_path, path)
6747
- );
6748
- CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
6749
-
6750
- CREATE TABLE IF NOT EXISTS project_key_files_version (
6751
- project_path TEXT PRIMARY KEY,
6752
- version INTEGER NOT NULL DEFAULT 0
6753
- );
6754
-
6755
- CREATE TABLE IF NOT EXISTS schema_migrations_meta (
6756
- key TEXT PRIMARY KEY,
6757
- value TEXT NOT NULL
6758
- );
6759
-
6760
- CREATE TABLE IF NOT EXISTS project_state (
6761
- project_path TEXT PRIMARY KEY,
6762
- project_memory_epoch INTEGER NOT NULL DEFAULT 0,
6763
- project_user_profile_version INTEGER NOT NULL DEFAULT 0,
6764
- updated_at INTEGER NOT NULL DEFAULT 0
6765
- );
6766
-
6767
- CREATE TABLE IF NOT EXISTS git_sweep_coordinator (
6768
- project_path TEXT PRIMARY KEY,
6769
- lease_holder TEXT,
6770
- lease_expires_at INTEGER,
6771
- last_swept_at INTEGER
6772
- );
6773
- CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_lease_expires
6774
- ON git_sweep_coordinator(lease_expires_at);
6775
- CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_last_swept
6776
- ON git_sweep_coordinator(last_swept_at);
6777
-
6778
- CREATE TABLE IF NOT EXISTS m0_mutation_log (
6779
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6780
- session_id TEXT NOT NULL,
6781
- mutation_type TEXT NOT NULL CHECK (mutation_type IN (
6782
- 'compartment_delete', 'compartment_merge', 'recomp_boundary_change', 'compartment_upgrade'
6783
- )),
6784
- target_id INTEGER,
6785
- queued_at INTEGER NOT NULL
6786
- );
6787
- CREATE INDEX IF NOT EXISTS idx_m0_mutation_log_session ON m0_mutation_log(session_id);
6788
-
6789
- CREATE TABLE IF NOT EXISTS v22_identity_rekey_map (
6790
- old_project_path TEXT PRIMARY KEY,
6791
- new_project_path TEXT NOT NULL,
6792
- rekeyed_at INTEGER NOT NULL
6793
- );
6794
-
6795
- CREATE TABLE IF NOT EXISTS workspaces (
6796
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6797
- name TEXT NOT NULL UNIQUE,
6798
- created_at INTEGER NOT NULL,
6799
- updated_at INTEGER NOT NULL,
6800
- share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
6801
- );
6802
-
6803
- CREATE TABLE IF NOT EXISTS workspace_members (
6804
- workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
6805
- project_path TEXT NOT NULL,
6806
- display_name TEXT NOT NULL,
6807
- display_path TEXT NOT NULL,
6808
- added_at INTEGER NOT NULL,
6809
- PRIMARY KEY (workspace_id, project_path)
6810
- );
6811
- CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
6812
- CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
6813
-
6814
- CREATE TABLE IF NOT EXISTS v22_backfill_failures (
6815
- id INTEGER PRIMARY KEY AUTOINCREMENT,
6816
- table_name TEXT NOT NULL,
6817
- row_id INTEGER NOT NULL,
6818
- raw_project_path TEXT NOT NULL,
6819
- error_class TEXT NOT NULL CHECK (error_class IN ('not_git_repo', 'git_missing', 'git_timeout', 'permission_denied', 'unknown')),
6820
- error_message TEXT,
6821
- failed_at INTEGER NOT NULL,
6822
- UNIQUE(table_name, row_id)
6823
- );
6824
-
6825
- -- (smart_notes: see note above; merged into unified notes table by migration v1)
6826
-
6827
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
6828
- content,
6829
- category,
6830
- content='memories',
6831
- content_rowid='id',
6832
- tokenize='porter unicode61'
6833
- );
6834
-
6835
- CREATE VIRTUAL TABLE IF NOT EXISTS message_history_fts USING fts5(
6836
- session_id UNINDEXED,
6837
- message_ordinal UNINDEXED,
6838
- message_id UNINDEXED,
6839
- role,
6840
- content,
6841
- tokenize='porter unicode61'
6842
- );
6843
-
6844
- CREATE TABLE IF NOT EXISTS message_fts_rowid_map (
6845
- session_id TEXT NOT NULL,
6846
- message_ordinal INTEGER NOT NULL,
6847
- fts_rowid INTEGER NOT NULL,
6848
- message_time_ms INTEGER,
6849
- PRIMARY KEY(session_id, message_ordinal)
6850
- );
6851
-
6852
- CREATE TABLE IF NOT EXISTS message_fts_rowid_map_backfill_state (
6853
- id INTEGER PRIMARY KEY CHECK(id = 1),
6854
- watermark_rowid INTEGER NOT NULL DEFAULT 0,
6855
- completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0, 1)),
6856
- updated_at INTEGER NOT NULL DEFAULT 0
6857
- );
6858
- INSERT OR IGNORE INTO message_fts_rowid_map_backfill_state
6859
- (id, watermark_rowid, completed, updated_at)
6860
- VALUES (1, 0, 0, 0);
6861
-
6862
- CREATE TABLE IF NOT EXISTS message_time_backfill_state (
6863
- id INTEGER PRIMARY KEY CHECK(id = 1),
6864
- cursor_session_id TEXT NOT NULL DEFAULT '',
6865
- cursor_ordinal INTEGER NOT NULL DEFAULT 0,
6866
- completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0, 1)),
6867
- updated_at INTEGER NOT NULL DEFAULT 0
6868
- );
6869
- INSERT OR IGNORE INTO message_time_backfill_state
6870
- (id, cursor_session_id, cursor_ordinal, completed, updated_at)
6871
- VALUES (1, '', 0, 0, 0);
6872
-
6873
- CREATE TABLE IF NOT EXISTS single_store_state (
6874
- id INTEGER PRIMARY KEY CHECK (id = 1),
6875
- state TEXT NOT NULL CHECK (state IN ('required', 'migrated')),
6876
- migrated_at INTEGER,
6877
- migrated_by TEXT,
6878
- backup_dir TEXT,
6879
- report_json TEXT
6880
- );
6881
- INSERT OR IGNORE INTO single_store_state(id, state) VALUES (1, 'required');
6882
-
6883
- -- Highest memory id another writer (the Rust module in single-store mode) put
6884
- -- into memories for a project, and how far this host has embedded. Migration v91.
6885
- CREATE TABLE IF NOT EXISTS memory_embedding_watermarks (
6886
- project_path TEXT PRIMARY KEY,
6887
- written_memory_id INTEGER NOT NULL DEFAULT 0,
6888
- embedded_memory_id INTEGER NOT NULL DEFAULT 0,
6889
- updated_at INTEGER NOT NULL DEFAULT 0
6890
- );
6891
-
6892
- -- Last-known-good replay slots, their prefix slices, and trailing-blank replay
6893
- -- decisions, in the layout of migration v94.
6894
- ${LKG_SLOTS_DDL}
6895
- ${LKG_SLOT_CHUNKS_DDL}
6896
- ${SESSION_REPLAY_DECISIONS_DDL}
6897
-
6898
- CREATE TABLE IF NOT EXISTS message_history_index (
6899
- session_id TEXT PRIMARY KEY,
6900
- last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
6901
- dirty_floor_ordinal INTEGER NOT NULL DEFAULT 0,
6902
- updated_at INTEGER NOT NULL,
6903
- harness TEXT NOT NULL DEFAULT 'opencode'
6904
- );
6905
-
6906
- CREATE TABLE IF NOT EXISTS message_history_source (
6907
- session_id TEXT NOT NULL,
6908
- message_id TEXT NOT NULL,
6909
- message_ordinal INTEGER NOT NULL,
6910
- source_version TEXT NOT NULL,
6911
- normalized_content_hash TEXT NOT NULL,
6912
- role TEXT NOT NULL,
6913
- harness TEXT NOT NULL DEFAULT 'opencode',
6914
- updated_at INTEGER NOT NULL,
6915
- PRIMARY KEY(session_id, message_id)
6916
- );
6917
- CREATE INDEX IF NOT EXISTS idx_message_history_source_session_ordinal
6918
- ON message_history_source(session_id, message_ordinal);
6919
-
6920
- CREATE TABLE IF NOT EXISTS pending_session_cleanup (
6921
- session_id TEXT PRIMARY KEY,
6922
- harness TEXT NOT NULL DEFAULT 'opencode',
6923
- requested_at INTEGER NOT NULL,
6924
- last_attempt_at INTEGER
6925
- );
6926
-
6927
- CREATE TABLE IF NOT EXISTS message_history_orphan_sweep (
6928
- harness TEXT PRIMARY KEY,
6929
- cursor_session_id TEXT NOT NULL DEFAULT '',
6930
- last_swept_at INTEGER
6931
- );
6932
-
6933
- CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
6934
- INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
6935
- END;
6936
-
6937
- CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
6938
- INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
6939
- END;
6940
-
6941
- CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
6942
- INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
6943
- INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
6944
- END;
6945
-
6946
- CREATE TABLE IF NOT EXISTS session_meta (
6947
- session_id TEXT PRIMARY KEY,
6948
- harness TEXT NOT NULL DEFAULT 'opencode',
6949
- last_response_time INTEGER,
6950
- cache_ttl TEXT,
6951
- counter INTEGER DEFAULT 0,
6952
- tags_version INTEGER NOT NULL DEFAULT 0,
6953
- last_nudge_tokens INTEGER DEFAULT 0,
6954
- last_nudge_band TEXT DEFAULT '',
6955
- last_nudge_undropped INTEGER DEFAULT 0,
6956
- last_nudge_level TEXT DEFAULT '',
6957
- channel2_nudge_state TEXT DEFAULT '',
6958
- channel2_nudge_claimed_at INTEGER DEFAULT 0,
6959
- channel2_nudge_claim_token TEXT DEFAULT '',
6960
- last_emergency_input_sample INTEGER DEFAULT 0,
6961
- last_transform_error TEXT DEFAULT '',
6962
- nudge_anchor_message_id TEXT DEFAULT '',
6963
- nudge_anchor_text TEXT DEFAULT '',
6964
- sticky_turn_reminder_text TEXT DEFAULT '',
6965
- sticky_turn_reminder_message_id TEXT DEFAULT '',
6966
- note_nudge_trigger_pending INTEGER DEFAULT 0,
6967
- note_nudge_trigger_message_id TEXT DEFAULT '',
6968
- note_nudge_sticky_text TEXT DEFAULT '',
6969
- note_nudge_sticky_message_id TEXT DEFAULT '',
6970
- note_nudge_anchors TEXT NOT NULL DEFAULT '[]',
6971
- auto_search_hint_decisions TEXT NOT NULL DEFAULT '[]',
6972
- last_todo_state TEXT DEFAULT '',
6973
- todo_permission_denied INTEGER NOT NULL DEFAULT 2,
6974
- todo_synthetic_call_id TEXT DEFAULT '',
6975
- todo_synthetic_anchor_message_id TEXT DEFAULT '',
6976
- todo_synthetic_state_json TEXT DEFAULT '',
6977
- is_subagent INTEGER DEFAULT 0,
6978
- last_context_percentage REAL DEFAULT 0,
6979
- last_input_tokens INTEGER DEFAULT 0,
6980
- detected_context_limit_provenance TEXT NOT NULL DEFAULT 'unknown',
6981
- observed_safe_input_tokens INTEGER NOT NULL DEFAULT 0,
6982
- cache_alert_sent INTEGER NOT NULL DEFAULT 0,
6983
- times_execute_threshold_reached INTEGER DEFAULT 0,
6984
- compartment_in_progress INTEGER DEFAULT 0,
6985
- historian_failure_count INTEGER DEFAULT 0,
6986
- historian_last_error TEXT DEFAULT NULL,
6987
- historian_last_failure_at INTEGER DEFAULT NULL,
6988
- system_prompt_hash TEXT DEFAULT '',
6989
- memory_block_cache TEXT DEFAULT '',
6990
- memory_block_count INTEGER DEFAULT 0,
6991
- memory_block_ids TEXT DEFAULT '',
6992
- -- pending_compaction_marker_state: intentionally NULLABLE without a
6993
- -- default. Absence of a deferred marker is SQL NULL; presence is a
6994
- -- valid JSON blob written via setPendingCompactionMarkerState.
6995
- -- Excluded from the healAllNullColumns fallback list. Readers filter
6996
- -- IS NOT NULL AND != empty-string defensively. Plan v6 section 3.
6997
- pending_compaction_marker_state TEXT,
6998
- -- Target OpenCode message id used to inject the current compaction marker.
6999
- -- Nullable for legacy persisted markers; repaired on the next marker move.
7000
- compaction_marker_target_end_message_id TEXT,
7001
- -- pending_pi_compaction_marker_state: intentionally NULLABLE without a
7002
- -- default. Absence of a deferred Pi-native marker is SQL NULL; presence
7003
- -- is a valid JSON blob written via setPendingPiCompactionMarkerState.
7004
- -- Excluded from the healAllNullColumns fallback list.
7005
- pending_pi_compaction_marker_state TEXT,
7006
- new_work_tokens INTEGER NOT NULL DEFAULT 0,
7007
- total_input_tokens INTEGER NOT NULL DEFAULT 0,
7008
- -- Retired columns remain in place so existing databases keep the same schema:
7009
- -- deferred_execute_state was used by the removed turn-boundary execute hold.
7010
- deferred_execute_state TEXT,
7011
- cached_m0_bytes BLOB,
7012
- cached_m0_project_memory_epoch INTEGER,
7013
- cached_m0_workspace_fingerprint TEXT,
7014
- cached_m0_project_user_profile_version INTEGER,
7015
- cached_m0_max_compartment_seq INTEGER,
7016
- cached_m0_max_memory_id INTEGER,
7017
- cached_m0_max_mutation_id INTEGER,
7018
- cached_m0_max_memory_mutation_id INTEGER,
7019
- cached_m0_project_docs_hash TEXT,
7020
- cached_m1_bytes BLOB,
7021
- last_observed_model_key TEXT,
7022
- last_usage_context_limit INTEGER NOT NULL DEFAULT 0,
7023
- prior_boundary_ordinal INTEGER NOT NULL DEFAULT 1,
7024
- protected_tokens_effective INTEGER,
7025
- protected_tokens_pre_snapshot TEXT,
7026
- protected_tail_policy_version INTEGER NOT NULL DEFAULT 0,
7027
- protected_tail_drain_window_started_at INTEGER NOT NULL DEFAULT 0,
7028
- protected_tail_drain_tokens INTEGER NOT NULL DEFAULT 0,
7029
- recovery_no_eligible_head_count INTEGER NOT NULL DEFAULT 0,
7030
- force_emergency_bypass_window_start INTEGER NOT NULL DEFAULT 0,
7031
- force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
7032
- emergency_drain_active INTEGER NOT NULL DEFAULT 0,
7033
- historian_drain_failure_at INTEGER NOT NULL DEFAULT 0,
7034
- wrapup_in_progress_state TEXT,
7035
- compaction_mode_record TEXT,
7036
- cached_m0_materialized_at INTEGER,
7037
- cached_m0_session_facts_version INTEGER,
7038
- cached_m0_upgrade_state TEXT,
7039
- cached_m0_system_hash TEXT,
7040
- cached_m0_tool_set_hash TEXT,
7041
- cached_m0_model_key TEXT,
7042
- cached_m0_project_identity TEXT,
7043
- cached_m0_last_baseline_end_message_id TEXT,
7044
- thinking_binding_recovery_target TEXT NOT NULL DEFAULT '',
7045
- upgrade_reminded_at INTEGER,
7046
- pi_stable_id_scheme INTEGER
7047
- );
7048
-
7049
- CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
7050
- session_id TEXT PRIMARY KEY,
7051
- status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'skipped')),
7052
- started_at INTEGER,
7053
- lease_expires_at INTEGER,
7054
- completed_at INTEGER,
7055
- last_error TEXT
7056
- );
7057
- CREATE INDEX IF NOT EXISTS idx_tool_owner_backfill_state_status
7058
- ON tool_owner_backfill_state(status);
7059
-
7060
- CREATE TABLE IF NOT EXISTS subagent_invocations (
7061
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7062
- session_id TEXT NOT NULL,
7063
- harness TEXT NOT NULL,
7064
- subagent TEXT NOT NULL,
7065
- task TEXT,
7066
- provider_id TEXT,
7067
- model_id TEXT,
7068
- started_at INTEGER NOT NULL,
7069
- ended_at INTEGER,
7070
- status TEXT NOT NULL,
7071
- input_tokens INTEGER NOT NULL DEFAULT 0,
7072
- output_tokens INTEGER NOT NULL DEFAULT 0,
7073
- cache_read_tokens INTEGER NOT NULL DEFAULT 0,
7074
- cache_write_tokens INTEGER NOT NULL DEFAULT 0,
7075
- error TEXT,
7076
- parent_invocation_id INTEGER
7077
- );
7078
- CREATE INDEX IF NOT EXISTS idx_sai_session_started
7079
- ON subagent_invocations(session_id, started_at DESC);
7080
- CREATE INDEX IF NOT EXISTS idx_sai_subagent
7081
- ON subagent_invocations(subagent, started_at DESC);
7082
-
7083
- CREATE TABLE IF NOT EXISTS historian_runs (
7084
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7085
- session_id TEXT NOT NULL,
7086
- harness TEXT NOT NULL DEFAULT 'opencode',
7087
- subagent_invocation_id INTEGER,
7088
- run_kind TEXT NOT NULL,
7089
- status TEXT NOT NULL,
7090
- failure_reason TEXT,
7091
- chunk_start_ordinal INTEGER,
7092
- chunk_end_ordinal INTEGER,
7093
- unprocessed_from INTEGER,
7094
- compartments_produced INTEGER NOT NULL DEFAULT 0,
7095
- compartment_id_min INTEGER,
7096
- compartment_id_max INTEGER,
7097
- facts_emitted INTEGER NOT NULL DEFAULT 0,
7098
- facts_by_category_json TEXT,
7099
- events_emitted INTEGER NOT NULL DEFAULT 0,
7100
- importance_min INTEGER,
7101
- importance_max INTEGER,
7102
- importance_avg REAL,
7103
- discarded_last INTEGER NOT NULL DEFAULT 0,
7104
- legacy INTEGER NOT NULL DEFAULT 0,
7105
- created_at INTEGER NOT NULL -- epoch ms (Date.now())
7106
- );
7107
- CREATE INDEX IF NOT EXISTS idx_historian_runs_session
7108
- ON historian_runs(session_id, created_at DESC);
7109
- CREATE INDEX IF NOT EXISTS idx_historian_runs_status
7110
- ON historian_runs(status, created_at DESC);
7111
-
7112
- CREATE TABLE IF NOT EXISTS transform_decisions (
7113
- session_id TEXT NOT NULL,
7114
- harness TEXT NOT NULL DEFAULT 'opencode',
7115
- message_id TEXT NOT NULL,
7116
- ts_ms INTEGER NOT NULL,
7117
- decision TEXT NOT NULL,
7118
- materialized INTEGER NOT NULL DEFAULT 0,
7119
- materialize_reason TEXT,
7120
- system_hash_prev TEXT,
7121
- system_hash_new TEXT,
7122
- m0_tool_set_hash_prev TEXT,
7123
- m0_tool_set_hash_new TEXT,
7124
- m0_model_key_prev TEXT,
7125
- m0_model_key_new TEXT,
7126
- emergency INTEGER NOT NULL DEFAULT 0,
7127
- dropped_tokens INTEGER NOT NULL DEFAULT 0,
7128
- dropped_count INTEGER NOT NULL DEFAULT 0,
7129
- input_tokens INTEGER NOT NULL DEFAULT 0,
7130
- PRIMARY KEY (session_id, harness, message_id)
7131
- );
7132
-
7133
- CREATE INDEX IF NOT EXISTS idx_tags_session_message_id ON tags(session_id, message_id);
7134
-
7135
- -- Clone/import paths can write tags before session bootstrap. Keep trigger-created
7136
- -- metadata rows aligned with the explicit defaults in ensureSessionMetaRow.
7137
- CREATE TRIGGER IF NOT EXISTS tags_version_ai AFTER INSERT ON tags BEGIN
7138
- INSERT INTO session_meta(
7139
- session_id, harness, last_response_time, cache_ttl, counter, tags_version,
7140
- last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
7141
- last_context_percentage, last_input_tokens, observed_safe_input_tokens,
7142
- cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
7143
- system_prompt_hash, cleared_reasoning_through_tag
7144
- ) VALUES(NEW.session_id, NEW.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0)
7145
- ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
7146
- END;
7147
- CREATE TRIGGER IF NOT EXISTS tags_version_ad AFTER DELETE ON tags BEGIN
7148
- INSERT INTO session_meta(
7149
- session_id, harness, last_response_time, cache_ttl, counter, tags_version,
7150
- last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
7151
- last_context_percentage, last_input_tokens, observed_safe_input_tokens,
7152
- cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
7153
- system_prompt_hash, cleared_reasoning_through_tag
7154
- ) VALUES(OLD.session_id, OLD.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0)
7155
- ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
7156
- END;
7157
- CREATE TRIGGER IF NOT EXISTS tags_version_au
7158
- AFTER UPDATE OF session_id, message_id, tag_number, type, tool_owner_message_id, status ON tags BEGIN
7159
- INSERT INTO session_meta(
7160
- session_id, harness, last_response_time, cache_ttl, counter, tags_version,
7161
- last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
7162
- last_context_percentage, last_input_tokens, observed_safe_input_tokens,
7163
- cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
7164
- system_prompt_hash, cleared_reasoning_through_tag
7165
- ) VALUES(OLD.session_id, OLD.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0)
7166
- ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
7167
- INSERT INTO session_meta(
7168
- session_id, harness, last_response_time, cache_ttl, counter, tags_version,
7169
- last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
7170
- last_context_percentage, last_input_tokens, observed_safe_input_tokens,
7171
- cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
7172
- system_prompt_hash, cleared_reasoning_through_tag
7173
- )
7174
- SELECT NEW.session_id, NEW.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0
7175
- WHERE NEW.session_id != OLD.session_id
7176
- ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
7177
- END;
7178
- CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
7179
-
7180
- CREATE TABLE IF NOT EXISTS recomp_compartments (
7181
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7182
- session_id TEXT NOT NULL,
7183
- sequence INTEGER NOT NULL,
7184
- start_message INTEGER NOT NULL,
7185
- end_message INTEGER NOT NULL,
7186
- start_message_id TEXT DEFAULT '',
7187
- end_message_id TEXT DEFAULT '',
7188
- start_block_index INTEGER,
7189
- end_block_index INTEGER,
7190
- title TEXT NOT NULL,
7191
- content TEXT NOT NULL,
7192
- p1 TEXT,
7193
- p2 TEXT,
7194
- p3 TEXT,
7195
- p4 TEXT,
7196
- importance INTEGER NOT NULL DEFAULT 50,
7197
- episode_type TEXT,
7198
- pass_number INTEGER NOT NULL,
7199
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
7200
- harness TEXT NOT NULL DEFAULT 'opencode',
7201
- UNIQUE(session_id, sequence)
7202
- );
7203
-
7204
- CREATE TABLE IF NOT EXISTS recomp_facts (
7205
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7206
- session_id TEXT NOT NULL,
7207
- category TEXT NOT NULL,
7208
- content TEXT NOT NULL,
7209
- pass_number INTEGER NOT NULL,
7210
- created_at INTEGER NOT NULL, -- epoch ms (Date.now())
7211
- harness TEXT NOT NULL DEFAULT 'opencode'
7212
- );
7213
-
7214
- CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
7215
- CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
7216
- CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
7217
- CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
7218
- CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
7219
- CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
7220
- CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
7221
- `);
7222
- installCompartmentHistoryVersions(db);
7223
- ensureColumn(db, "message_fts_rowid_map", "message_time_ms", "INTEGER");
7224
- db.exec(`
7225
- CREATE INDEX IF NOT EXISTS idx_message_fts_rowid_map_session_time
7226
- ON message_fts_rowid_map(session_id, message_time_ms);
7227
- `);
7228
- ensureColumn(db, "primer_candidates", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7229
- ensureColumn(db, "primer_candidates", "source_start_message_id", "TEXT NOT NULL DEFAULT ''");
7230
- ensureColumn(db, "primer_candidates", "source_end_message_id", "TEXT NOT NULL DEFAULT ''");
7231
- ensureColumn(db, "primer_candidates", "question_embedding", "BLOB");
7232
- ensureColumn(db, "primer_candidates", "question_embedding_model_id", "TEXT");
7233
- ensureColumn(db, "primers", "question_embedding_model_id", "TEXT");
7234
- ensureColumn(db, "primers", "source_candidate_provenance", "TEXT");
7235
- const hasUserMemoriesTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'user_memories'").get();
7236
- if (hasUserMemoriesTable) {
7237
- ensureColumn(db, "user_memories", "source_candidate_provenance", "TEXT");
7238
- }
7239
- db.exec(`
7240
- CREATE UNIQUE INDEX IF NOT EXISTS idx_primer_candidates_occurrence
7241
- ON primer_candidates(project_path, harness, session_id, source_start_message_id, source_end_message_id);
7242
- CREATE INDEX IF NOT EXISTS idx_primer_candidates_project_time
7243
- ON primer_candidates(project_path, source_message_time);
7244
- CREATE INDEX IF NOT EXISTS idx_primer_candidates_session
7245
- ON primer_candidates(session_id, harness);
7246
- CREATE INDEX IF NOT EXISTS idx_primer_candidates_embedding_model
7247
- ON primer_candidates(project_path, question_embedding_model_id);
7248
- CREATE INDEX IF NOT EXISTS idx_primers_project_status_observed
7249
- ON primers(project_path, status, last_observed_at DESC);
7250
- CREATE INDEX IF NOT EXISTS idx_primers_embedding_model
7251
- ON primers(project_path, question_embedding_model_id);
7252
- CREATE VIRTUAL TABLE IF NOT EXISTS primers_fts USING fts5(
7253
- question,
7254
- answer,
7255
- project_path UNINDEXED,
7256
- content='primers',
7257
- content_rowid='id',
7258
- tokenize='porter unicode61'
7259
- );
7260
- CREATE TRIGGER IF NOT EXISTS primers_ai AFTER INSERT ON primers BEGIN
7261
- INSERT INTO primers_fts(rowid, question, answer, project_path)
7262
- VALUES (new.id, new.question, new.answer, new.project_path);
7263
- END;
7264
- CREATE TRIGGER IF NOT EXISTS primers_ad AFTER DELETE ON primers BEGIN
7265
- INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
7266
- VALUES ('delete', old.id, old.question, old.answer, old.project_path);
7267
- END;
7268
- CREATE TRIGGER IF NOT EXISTS primers_au AFTER UPDATE ON primers BEGIN
7269
- INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
7270
- VALUES ('delete', old.id, old.question, old.answer, old.project_path);
7271
- INSERT INTO primers_fts(rowid, question, answer, project_path)
7272
- VALUES (new.id, new.question, new.answer, new.project_path);
7273
- END;
7274
- `);
7275
- ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
7276
- ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
7277
- ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
7278
- ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
7279
- ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
7280
- ensureColumn(db, "session_meta", "channel2_nudge_claim_token", "TEXT DEFAULT ''");
7281
- ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
7282
- ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
7283
- ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
7284
- ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
7285
- ensureColumn(db, "session_meta", "sticky_turn_reminder_text", "TEXT DEFAULT ''");
7286
- ensureColumn(db, "session_meta", "sticky_turn_reminder_message_id", "TEXT DEFAULT ''");
7287
- ensureColumn(db, "session_meta", "note_nudge_trigger_pending", "INTEGER DEFAULT 0");
7288
- ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
7289
- ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
7290
- ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
7291
- ensureColumn(db, "session_meta", "note_nudge_anchors", "TEXT NOT NULL DEFAULT '[]'");
7292
- ensureColumn(db, "session_meta", "auto_search_hint_decisions", "TEXT NOT NULL DEFAULT '[]'");
7293
- ensureColumn(db, "session_meta", "last_todo_state", "TEXT DEFAULT ''");
7294
- ensureColumn(db, "session_meta", "todo_permission_denied", "INTEGER NOT NULL DEFAULT 2");
7295
- ensureColumn(db, "session_meta", "todo_synthetic_call_id", "TEXT DEFAULT ''");
7296
- ensureColumn(db, "session_meta", "todo_synthetic_anchor_message_id", "TEXT DEFAULT ''");
7297
- ensureColumn(db, "session_meta", "todo_synthetic_state_json", "TEXT DEFAULT ''");
7298
- ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
7299
- ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
7300
- ensureColumn(db, "session_meta", "observed_safe_input_tokens", "INTEGER NOT NULL DEFAULT 0");
7301
- ensureColumn(db, "session_meta", "cache_alert_sent", "INTEGER NOT NULL DEFAULT 0");
7302
- ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
7303
- ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
7304
- ensureColumn(db, "session_meta", "historian_last_error", "TEXT DEFAULT NULL");
7305
- ensureColumn(db, "session_meta", "historian_last_failure_at", "INTEGER DEFAULT NULL");
7306
- ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
7307
- ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
7308
- ensureColumn(db, "session_meta", "tags_version", "INTEGER NOT NULL DEFAULT 0");
7309
- ensureColumn(db, "session_meta", "tool_reclaim_watermark", "INTEGER DEFAULT 0");
7310
- ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
7311
- ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
7312
- ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
7313
- ensureColumn(db, "session_meta", "merged_reasoning_stripped_ids", "TEXT DEFAULT ''");
7314
- ensureColumn(db, "session_meta", "thinking_binding_recovery_target", "TEXT DEFAULT ''");
7315
- ensureColumn(db, "session_meta", "trailing_blank_decisions", "TEXT DEFAULT ''");
7316
- ensureColumn(db, "compartment_state_lease", "owner_pid", "INTEGER");
7317
- ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
7318
- ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
7319
- ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
7320
- ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
7321
- ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
7322
- ensureColumn(db, "session_meta", "pi_stable_id_scheme", "INTEGER");
7323
- ensureColumn(db, "session_meta", "memory_block_ids", "TEXT DEFAULT ''");
7324
- ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
7325
- ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
7326
- ensureColumn(db, "tags", "drop_mode", "TEXT DEFAULT 'full'");
7327
- ensureColumn(db, "tags", "tool_name", "TEXT");
7328
- ensureColumn(db, "tags", "input_byte_size", "INTEGER DEFAULT 0");
7329
- ensureColumn(db, "tags", "caveman_depth", "INTEGER DEFAULT 0");
7330
- ensureColumn(db, "tags", "tool_owner_message_id", "TEXT DEFAULT NULL");
7331
- ensureColumn(db, "tags", "entry_fingerprint", "TEXT");
7332
- db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_adopt
7333
- ON tags(session_id, entry_fingerprint)
7334
- WHERE type='message' AND entry_fingerprint IS NOT NULL`);
7335
- db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_fallback_tool_owner
7336
- ON tags(session_id, tool_owner_message_id)
7337
- WHERE type='tool'`);
7338
- ensureColumn(db, "tags", "token_count", "INTEGER");
7339
- ensureColumn(db, "tags", "input_token_count", "INTEGER");
7340
- ensureColumn(db, "tags", "reasoning_token_count", "INTEGER");
7341
- ensureColumn(db, "task_schedule_state", "schedule", "TEXT");
7342
- ensureColumn(db, "task_schedule_state", "last_checked_commit", "TEXT");
7343
- ensureColumn(db, "task_schedule_state", "last_broad_run_at", "INTEGER");
7344
- ensureColumn(db, "task_schedule_state", "retrospective_watermark_ms", "INTEGER");
7345
- ensureColumn(db, "dream_runs", "parent_session_id", "TEXT");
7346
- ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
7347
- ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
7348
- ensureColumn(db, "session_meta", "compaction_marker_target_end_message_id", "TEXT");
7349
- ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
7350
- ensureColumn(db, "session_meta", "conversation_tokens", "INTEGER DEFAULT 0");
7351
- ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
7352
- ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
7353
- ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
7354
- ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
7355
- ensureColumn(db, "session_meta", "detected_context_limit_model_key", "TEXT");
7356
- ensureColumn(db, "session_meta", "detected_context_limit_provenance", "TEXT NOT NULL DEFAULT 'unknown'");
7357
- ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
7358
- ensureColumn(db, "session_meta", "emergency_recovery_origin", "TEXT DEFAULT ''");
7359
- ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
7360
- ensureColumn(db, "session_meta", "pending_pi_compaction_marker_state", "TEXT");
7361
- ensureColumn(db, "session_meta", "new_work_tokens", "INTEGER NOT NULL DEFAULT 0");
7362
- ensureColumn(db, "session_meta", "total_input_tokens", "INTEGER NOT NULL DEFAULT 0");
7363
- ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
7364
- ensureColumn(db, "compartments", "p1", "TEXT");
7365
- ensureColumn(db, "compartments", "p2", "TEXT");
7366
- ensureColumn(db, "compartments", "p3", "TEXT");
7367
- ensureColumn(db, "compartments", "p4", "TEXT");
7368
- ensureColumn(db, "compartments", "importance", "INTEGER NOT NULL DEFAULT 50");
7369
- ensureColumn(db, "compartments", "episode_type", "TEXT");
7370
- ensureColumn(db, "compartments", "p1_embedding", "BLOB");
7371
- ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
7372
- ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
7373
- ensureColumn(db, "recomp_compartments", "p1", "TEXT");
7374
- ensureColumn(db, "recomp_compartments", "p2", "TEXT");
7375
- ensureColumn(db, "recomp_compartments", "p3", "TEXT");
7376
- ensureColumn(db, "recomp_compartments", "p4", "TEXT");
7377
- ensureColumn(db, "recomp_compartments", "importance", "INTEGER NOT NULL DEFAULT 50");
7378
- ensureColumn(db, "recomp_compartments", "episode_type", "TEXT");
7379
- ensureColumn(db, "memories", "importance", "INTEGER");
7380
- ensureColumn(db, "memories", "classified_at", "INTEGER");
7381
- ensureColumn(db, "memories", "mural_cue", "TEXT");
7382
- ensureColumn(db, "memories", "mural_cue_hash", "TEXT");
7383
- ensureColumn(db, "memories", "mural_cue_at", "INTEGER");
7384
- ensureColumn(db, "memory_verifications", "mapped_at", "INTEGER NOT NULL DEFAULT 0");
7385
- ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
7386
- ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
7387
- ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
7388
- ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
7389
- ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
7390
- ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
7391
- ensureColumn(db, "session_meta", "cached_m0_max_mutation_id", "INTEGER");
7392
- ensureColumn(db, "session_meta", "cached_m0_max_memory_mutation_id", "INTEGER");
7393
- ensureColumn(db, "session_meta", "cached_m0_project_docs_hash", "TEXT");
7394
- ensureColumn(db, "session_meta", "cached_m1_bytes", "BLOB");
7395
- ensureColumn(db, "session_meta", "last_observed_model_key", "TEXT");
7396
- ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
7397
- ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
7398
- ensureColumn(db, "session_meta", "protected_tokens_effective", "INTEGER");
7399
- ensureColumn(db, "session_meta", "protected_tokens_pre_snapshot", "TEXT");
7400
- ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
7401
- ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
7402
- ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
7403
- ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
7404
- ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
7405
- ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
7406
- ensureColumn(db, "session_meta", "emergency_drain_active", "INTEGER NOT NULL DEFAULT 0");
7407
- ensureColumn(db, "session_meta", "historian_drain_failure_at", "INTEGER NOT NULL DEFAULT 0");
7408
- ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
7409
- ensureColumn(db, "session_meta", "compaction_mode_record", "TEXT");
7410
- ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
7411
- ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
7412
- ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
7413
- ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
7414
- ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
7415
- ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
7416
- ensureColumn(db, "session_meta", "cached_m0_project_identity", "TEXT");
7417
- ensureColumn(db, "session_meta", "cached_m0_last_baseline_end_message_id", "TEXT");
7418
- ensureColumn(db, "session_meta", "upgrade_reminded_at", "INTEGER");
7419
- ensureColumn(db, "session_meta", "upgrade_reminder_last_sent_at", "INTEGER");
7420
- ensureColumn(db, "session_meta", "upgrade_reminder_count", "INTEGER NOT NULL DEFAULT 0");
7421
- ensureColumn(db, "session_meta", "cached_m0_mural_data_url", "TEXT");
7422
- ensureColumn(db, "session_meta", "cached_m0_mural_hash", "TEXT");
7423
- ensureColumn(db, "session_meta", "coordinate_generation", "TEXT");
7424
- ensureColumn(db, "session_meta", "coordinate_rebase_notice", "TEXT");
7425
- ensureColumn(db, "compartments", "rebase_status", "TEXT NOT NULL DEFAULT 'ok'");
7426
- ensureColumn(db, "recomp_compartments", "rebase_status", "TEXT NOT NULL DEFAULT 'ok'");
7427
- db.exec(`
7428
- CREATE TABLE IF NOT EXISTS project_state (
7429
- project_path TEXT PRIMARY KEY,
7430
- project_memory_epoch INTEGER NOT NULL DEFAULT 0,
7431
- project_user_profile_version INTEGER NOT NULL DEFAULT 0,
7432
- updated_at INTEGER NOT NULL DEFAULT 0
7433
- );
7434
- CREATE TABLE IF NOT EXISTS session_projects (
7435
- session_id TEXT NOT NULL,
7436
- harness TEXT NOT NULL DEFAULT 'opencode',
7437
- project_path TEXT NOT NULL,
7438
- updated_at INTEGER NOT NULL,
7439
- PRIMARY KEY(session_id, harness)
7440
- );
7441
- CREATE INDEX IF NOT EXISTS idx_session_projects_project
7442
- ON session_projects(project_path);
7443
- CREATE TABLE IF NOT EXISTS m0_mutation_log (
7444
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7445
- session_id TEXT NOT NULL,
7446
- mutation_type TEXT NOT NULL CHECK (mutation_type IN (
7447
- 'compartment_delete', 'compartment_merge', 'recomp_boundary_change', 'compartment_upgrade'
7448
- )),
7449
- target_id INTEGER,
7450
- queued_at INTEGER NOT NULL
7451
- );
7452
- CREATE INDEX IF NOT EXISTS idx_m0_mutation_log_session ON m0_mutation_log(session_id);
7453
- CREATE TABLE IF NOT EXISTS memory_mutation_log (
7454
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7455
- project_path TEXT NOT NULL,
7456
- mutation_type TEXT NOT NULL CHECK (mutation_type IN ('archive', 'delete', 'update', 'superseded')),
7457
- target_memory_id INTEGER NOT NULL,
7458
- superseded_by_id INTEGER,
7459
- category TEXT,
7460
- new_content TEXT,
7461
- queued_at INTEGER NOT NULL
7462
- );
7463
- CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_project
7464
- ON memory_mutation_log(project_path, id);
7465
- CREATE TABLE IF NOT EXISTS v22_identity_rekey_map (
7466
- old_project_path TEXT PRIMARY KEY,
7467
- new_project_path TEXT NOT NULL,
7468
- rekeyed_at INTEGER NOT NULL
7469
- );
7470
- CREATE TABLE IF NOT EXISTS identity_merge_log (
7471
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7472
- from_identity TEXT NOT NULL,
7473
- to_identity TEXT NOT NULL,
7474
- table_name TEXT NOT NULL,
7475
- row_id TEXT NOT NULL,
7476
- action TEXT NOT NULL,
7477
- target_row_id TEXT,
7478
- merged_at INTEGER NOT NULL
7479
- );
7480
- CREATE INDEX IF NOT EXISTS idx_identity_merge_log_identities
7481
- ON identity_merge_log(from_identity, to_identity, merged_at);
7482
- CREATE INDEX IF NOT EXISTS idx_identity_merge_log_table_row
7483
- ON identity_merge_log(table_name, row_id);
7484
- CREATE TABLE IF NOT EXISTS workspaces (
7485
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7486
- name TEXT NOT NULL UNIQUE,
7487
- created_at INTEGER NOT NULL,
7488
- updated_at INTEGER NOT NULL,
7489
- share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
7490
- );
7491
- CREATE TABLE IF NOT EXISTS workspace_members (
7492
- workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
7493
- project_path TEXT NOT NULL,
7494
- display_name TEXT NOT NULL,
7495
- display_path TEXT NOT NULL,
7496
- added_at INTEGER NOT NULL,
7497
- PRIMARY KEY (workspace_id, project_path)
7498
- );
7499
- CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
7500
- CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
7501
- CREATE TABLE IF NOT EXISTS v22_backfill_failures (
7502
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7503
- table_name TEXT NOT NULL,
7504
- row_id INTEGER NOT NULL,
7505
- raw_project_path TEXT NOT NULL,
7506
- error_class TEXT NOT NULL CHECK (error_class IN ('not_git_repo', 'git_missing', 'git_timeout', 'permission_denied', 'unknown')),
7507
- error_message TEXT,
7508
- failed_at INTEGER NOT NULL,
7509
- UNIQUE(table_name, row_id)
7510
- );
7511
- CREATE TABLE IF NOT EXISTS transform_decisions (
7512
- session_id TEXT NOT NULL,
7513
- harness TEXT NOT NULL DEFAULT 'opencode',
7514
- message_id TEXT NOT NULL,
7515
- ts_ms INTEGER NOT NULL,
7516
- decision TEXT NOT NULL,
7517
- materialized INTEGER NOT NULL DEFAULT 0,
7518
- materialize_reason TEXT,
7519
- system_hash_prev TEXT,
7520
- system_hash_new TEXT,
7521
- m0_tool_set_hash_prev TEXT,
7522
- m0_tool_set_hash_new TEXT,
7523
- m0_model_key_prev TEXT,
7524
- m0_model_key_new TEXT,
7525
- emergency INTEGER NOT NULL DEFAULT 0,
7526
- dropped_tokens INTEGER NOT NULL DEFAULT 0,
7527
- dropped_count INTEGER NOT NULL DEFAULT 0,
7528
- input_tokens INTEGER NOT NULL DEFAULT 0,
7529
- PRIMARY KEY (session_id, harness, message_id)
7530
- );
7531
- `);
7532
- ensureColumn(db, "transform_decisions", "system_hash_prev", "TEXT");
7533
- ensureColumn(db, "transform_decisions", "system_hash_new", "TEXT");
7534
- ensureColumn(db, "transform_decisions", "m0_model_key_prev", "TEXT");
7535
- ensureColumn(db, "transform_decisions", "m0_model_key_new", "TEXT");
7536
- ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7537
- ensureColumn(db, "message_history_index", "dirty_floor_ordinal", "INTEGER NOT NULL DEFAULT 0");
7538
- ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7539
- ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7540
- ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7541
- ensureColumn(db, "compression_depth", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7542
- ensureColumn(db, "session_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7543
- ensureColumn(db, "session_meta", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7544
- ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7545
- ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7546
- ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
7547
- db.exec(`
7548
- CREATE INDEX IF NOT EXISTS idx_message_history_index_orphan_sweep
7549
- ON message_history_index(harness, session_id, updated_at);
7550
- `);
7551
- ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
7552
- const version = getPersistedSchemaVersion(db);
7553
- if (version === 0 || version >= 95)
7554
- installV95PerfSchema(db, false, version === 0);
7555
- }
7556
- var CHANNEL2_CLAIM_TTL_MS = 10 * 60000;
7557
- var channel2HealStatements = new WeakMap;
7558
- function healWedgedChannel2Claims(db) {
7559
- try {
7560
- const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
7561
- let statements = channel2HealStatements.get(db);
7562
- if (!statements) {
7563
- const predicate = "channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)";
7564
- statements = {
7565
- stale: db.prepare(`SELECT 1 FROM session_meta WHERE ${predicate} LIMIT 1`),
7566
- heal: db.prepare(`UPDATE session_meta SET channel2_nudge_state = '', channel2_nudge_claimed_at = 0, channel2_nudge_claim_token = '' WHERE ${predicate}`)
7567
- };
7568
- channel2HealStatements.set(db, statements);
7569
- }
7570
- if (statements.stale.get(staleBefore))
7571
- statements.heal.run(staleBefore);
7572
- } catch {}
7573
- }
7574
- function openDatabase(dbPathOrOptions) {
7575
- const options = typeof dbPathOrOptions === "string" ? { dbPath: dbPathOrOptions } : dbPathOrOptions;
7576
- const explicitDbPath = options?.dbPath !== undefined;
7577
- const { dbDir, dbPath } = resolveDatabasePath(options?.dbPath);
7578
- const latestSupportedVersion = getRuntimeLatestSupportedVersion(options);
7579
- const busyTimeoutMs = resolveBootBusyTimeoutMs(options?.busyTimeoutMs);
7580
- lastSchemaFenceRejection = null;
7581
- lastMigrationOnOpenRefusal = null;
7582
- const existing = databases.get(dbPath);
7583
- if (!existing && pendingAsyncOpens.has(dbPath)) {
7584
- log(`[magic-context] storage not ready: ${dbPath} is still being opened or migrated; refusing a second synchronous open`);
7585
- return null;
7586
- }
7587
- if (existing) {
7588
- if (!enforceSchemaFence(existing, dbPath, latestSupportedVersion)) {
7589
- return null;
7590
- }
7591
- if (!persistenceByDatabase.has(existing)) {
7592
- persistenceByDatabase.set(existing, true);
7593
- }
7594
- healWedgedChannel2Claims(existing);
7595
- return existing;
7596
- }
7597
- let db;
7598
- try {
7599
- if (!explicitDbPath) {
7600
- migrateLegacyStorageIfNeeded(dbPath, dbDir);
7601
- }
7602
- ensureSecureStorageDir(dbDir);
7603
- tightenStorageTree(dbDir);
7604
- if (!existsSync3(dbPath) && shouldEnforcePrivateStoragePermissions()) {
7605
- try {
7606
- writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
7607
- } catch (error) {
7608
- if (error.code !== "EEXIST")
7609
- throw error;
7610
- }
7611
- }
7612
- db = new Database(dbPath);
7613
- installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
7614
- if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
7615
- closeQuietly(db);
7616
- return null;
7617
- }
7618
- if (options?.allowMigrations === false && hasPendingMigrations(db)) {
7619
- log(`[magic-context] storage not ready: pending migrations at ${dbPath}; host callers must await the async opener`);
7620
- closeQuietly(db);
7621
- return null;
7622
- }
7623
- if (!enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion)) {
7624
- closeQuietly(db);
7625
- return null;
7626
- }
7627
- initializeDatabase(db, busyTimeoutMs);
7628
- if (options?.allowMigrations === false) {
7629
- if (hasPendingMigrations(db)) {
7630
- closeQuietly(db);
7631
- return null;
7632
- }
7633
- } else
7634
- runMigrations(db);
7635
- ensureContextStoreUuid(db);
7636
- return finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
7637
- } catch (error) {
7638
- if (db)
7639
- closeQuietly(db);
7640
- const detail = getErrorMessage(error);
7641
- log(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
7642
- throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
7643
- }
7644
- }
7645
- function openCurrentDatabase(options = {}) {
7646
- return openDatabase({ ...options, allowMigrations: false });
7647
- }
7648
- async function openDatabaseAsync(dbPathOrOptions) {
7649
- const options = typeof dbPathOrOptions === "string" ? { dbPath: dbPathOrOptions } : dbPathOrOptions;
7650
- const explicitDbPath = options?.dbPath !== undefined;
7651
- const { dbDir, dbPath } = resolveDatabasePath(options?.dbPath);
7652
- const latestSupportedVersion = getRuntimeLatestSupportedVersion(options);
7653
- const busyTimeoutMs = resolveBootBusyTimeoutMs(options?.busyTimeoutMs);
7654
- lastSchemaFenceRejection = null;
7655
- lastMigrationOnOpenRefusal = null;
7656
- const existing = databases.get(dbPath);
7657
- if (existing) {
7658
- const startedAt = performance.now();
7659
- const accepted = enforceSchemaFence(existing, dbPath, latestSupportedVersion);
7660
- if (accepted) {
7661
- if (!persistenceByDatabase.has(existing))
7662
- persistenceByDatabase.set(existing, true);
7663
- healWedgedChannel2Claims(existing);
7664
- }
7665
- options?.onBootTimings?.({
7666
- openMs: performance.now() - startedAt,
7667
- guardMs: 0,
7668
- migrateMs: 0
7669
- });
7670
- return accepted ? existing : null;
7671
- }
7672
- const pending = pendingAsyncOpens.get(dbPath);
7673
- if (pending)
7674
- return pending;
7675
- if (!isFileBackedPath(dbPath)) {
7676
- throw new Error("async migration requires a file-backed database; use the explicit synchronous opener for in-memory or URI test databases");
7677
- }
7678
- const opening = (async () => {
7679
- let db;
7680
- const mainThreadBodiesBefore = getMainThreadMigrationBodyCount();
7681
- const openStartedAt = performance.now();
7682
- let openMs = 0;
7683
- let guardMs = 0;
7684
- let migrateMs = 0;
7685
- let guardStartedAt = null;
7686
- let migrateStartedAt = null;
7687
- try {
7688
- if (!explicitDbPath)
7689
- migrateLegacyStorageIfNeeded(dbPath, dbDir);
7690
- ensureSecureStorageDir(dbDir);
7691
- tightenStorageTree(dbDir);
7692
- if (!existsSync3(dbPath) && shouldEnforcePrivateStoragePermissions()) {
7693
- try {
7694
- writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
7695
- } catch (error) {
7696
- if (error.code !== "EEXIST")
7697
- throw error;
7698
- }
7699
- }
7700
- db = new Database(dbPath);
7701
- installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
7702
- openMs = performance.now() - openStartedAt;
7703
- guardStartedAt = performance.now();
7704
- if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
7705
- guardMs = performance.now() - guardStartedAt;
7706
- closeQuietly(db);
7707
- return null;
7708
- }
7709
- const processes = getPersistedSchemaVersion(db) < latestSupportedVersion ? await inspectProcessesAsync() : undefined;
7710
- if (!enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion, processes)) {
7711
- guardMs = performance.now() - guardStartedAt;
7712
- closeQuietly(db);
7713
- return null;
7714
- }
7715
- guardMs = performance.now() - guardStartedAt;
7716
- migrateStartedAt = performance.now();
7717
- if (hasPendingMigrations(db)) {
7718
- await runMigrationsOffThread({
7719
- dbPath,
7720
- busyTimeoutMs,
7721
- sqlitePragmaConfig: { ...sqlitePragmaConfig }
7722
- });
7723
- }
7724
- if (hasPendingMigrations(db)) {
7725
- throw new Error("the migration worker did not complete the pending migration; reinstall or rebuild the plugin");
7726
- }
7727
- initializeDatabase(db, busyTimeoutMs);
7728
- if (hasPendingMigrations(db)) {
7729
- throw new Error("storage initialization left a pending migration; reinstall or rebuild the plugin");
7730
- }
7731
- ensureContextStoreUuid(db);
7732
- const opened = finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
7733
- migrateMs = performance.now() - migrateStartedAt;
7734
- return opened;
7735
- } catch (error) {
7736
- if (db)
7737
- closeQuietly(db);
7738
- const detail = getErrorMessage(error);
7739
- log(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
7740
- throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
7741
- } finally {
7742
- log(`[migrations] async open main-thread migration-body count: ${getMainThreadMigrationBodyCount() - mainThreadBodiesBefore} (total=${getMainThreadMigrationBodyCount()}) path=${dbPath}`);
7743
- if (openMs === 0)
7744
- openMs = performance.now() - openStartedAt;
7745
- if (guardStartedAt !== null && guardMs === 0) {
7746
- guardMs = performance.now() - guardStartedAt;
7747
- }
7748
- if (migrateStartedAt !== null && migrateMs === 0) {
7749
- migrateMs = performance.now() - migrateStartedAt;
7750
- }
7751
- options?.onBootTimings?.({ openMs, guardMs, migrateMs });
7752
- }
7753
- })();
7754
- pendingAsyncOpens.set(dbPath, opening);
7755
- try {
7756
- return await opening;
7757
- } finally {
7758
- if (pendingAsyncOpens.get(dbPath) === opening)
7759
- pendingAsyncOpens.delete(dbPath);
7760
- }
7761
- }
7762
- function isDatabasePersisted(db) {
7763
- if (!db)
7764
- return false;
7765
- return persistenceByDatabase.get(db) ?? false;
7766
- }
7767
- function getDatabasePersistenceError(db) {
7768
- if (!db)
7769
- return null;
7770
- return persistenceErrorByDatabase.get(db) ?? null;
7771
- }
7772
-
7773
- export { FAIL_CLOSED_DOCTOR_COMMAND, formatFailClosedBlockingMessage, formatFailClosedBlockingSummary, createFailClosedBlockingError, isFailClosedBlockingError, shouldBypassFailClosedBlock, resolveAgentNameFromMessages, createFailClosedController, recordHookInitFailure, clearHookInitFailure, getLastHookInitFailure, canonicalProjectDirectory, setHomeProjectPermission, shouldSkipHomeProjectMemory, ProjectIdentityError, resolveProjectIdentityStrict, takeDubiousOwnershipProjectIdentityWarning, isUserHomeDirectory, resolveProjectIdentity, directoryHasGitMetadata, describeUnresolvedProjectIdentity, isUsableProjectIdentity, resolveProjectIdentityForSession, normalizeStoredProjectPath, storedPathBelongsToIdentity, beginBootQuietPeriod, bootQuietRemainingMs, scheduleAfterBootQuiet, registerOwnRpcServerInstance, rpcPortDir, rpcPortFilePath, isPidAlive, parseRpcPortFile, inspectProcessesAsync, configureContextDatabasePragmas, recordMessageFtsRowid, recordIndexedMessageTime, messageFtsOrdinalRangeIsMapped, deleteUnmappedMessageFtsRows, layoutLkgPrefix, assembleLkgPrefix, startBootDeadline, V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, getMemoryCategoryOrder, CATEGORY_DEFAULT_TTL, unescapeXml, parseCompartmentOutput, resolveWorkspaceShareCategories, resolveWorkspaceIdentitySet, expandWorkspaceIdentitySetWithAliases, resolveStoredPathWorkspaceIdentity, storedPathBelongsToWorkspace, sourceNameForMemory, computeWorkspaceEpochFingerprint, managedAuthorityNoteRow, runMigrationsWithRetry, getUnconfirmedMigrationHolders, getSchemaFenceRejection, getMigrationOnOpenRefusal, LATEST_SUPPORTED_VERSION, BOOT_SQLITE_BUSY_TIMEOUT_MS, getDatabasePath, getPersistedSchemaVersion, setSqlitePragmaConfig, runSqliteOptimize, initializeDatabase, openDatabase, openCurrentDatabase, openDatabaseAsync, isDatabasePersisted, getDatabasePersistenceError };
4348
+ export { canonicalProjectDirectory, setHomeProjectPermission, shouldSkipHomeProjectMemory, ProjectIdentityError, resolveProjectIdentityStrict, takeDubiousOwnershipProjectIdentityWarning, isUserHomeDirectory, resolveProjectIdentity, directoryHasGitMetadata, describeUnresolvedProjectIdentity, isUsableProjectIdentity, resolveProjectIdentityForSession, normalizeStoredProjectPath, storedPathBelongsToIdentity, beginBootQuietPeriod, bootQuietRemainingMs, scheduleAfterBootQuiet, recordMessageFtsRowid, recordIndexedMessageTime, startMessageFtsRowidMapBackfill, messageFtsOrdinalRangeIsMapped, deleteUnmappedMessageFtsRows, layoutLkgPrefix, assembleLkgPrefix, LKG_SLOT_CHUNKS_DDL, SESSION_REPLAY_DECISIONS_DDL, LKG_SLOTS_DDL, V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, getMemoryCategoryOrder, CATEGORY_DEFAULT_TTL, unescapeXml, parseCompartmentOutput, installCompartmentHistoryVersions, resolveWorkspaceShareCategories, resolveWorkspaceIdentitySet, expandWorkspaceIdentitySetWithAliases, resolveStoredPathWorkspaceIdentity, storedPathBelongsToWorkspace, sourceNameForMemory, computeWorkspaceEpochFingerprint, FORK_MIGRATION_VERSION_FLOOR, managedAuthorityNoteRow, hasPendingMigrations, getMainThreadMigrationBodyCount, runMigrations, runMigrationsWithRetry };