@cortexkit/opencode-magic-context 0.33.0 → 0.34.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 (265) hide show
  1. package/dist/config/agent-disable.d.ts +22 -0
  2. package/dist/config/agent-disable.d.ts.map +1 -1
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/project-security.d.ts +7 -0
  5. package/dist/config/project-security.d.ts.map +1 -1
  6. package/dist/config/schema/magic-context.d.ts +144 -24
  7. package/dist/config/schema/magic-context.d.ts.map +1 -1
  8. package/dist/config/transform-mode.d.ts +2 -0
  9. package/dist/config/transform-mode.d.ts.map +1 -1
  10. package/dist/{conflict-warning-hook-bwa99a04.js → conflict-warning-hook-fvpfvt0w.js} +1 -1
  11. package/dist/{derive-budgets-e9j09sjr.js → derive-budgets-9crp0cvd.js} +1 -1
  12. package/dist/features/builtin-commands/commands.d.ts +1 -1
  13. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  14. package/dist/features/magic-context/compaction-marker.d.ts +106 -0
  15. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  16. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +0 -2
  17. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  19. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  21. package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  23. package/dist/features/magic-context/dreamer/task-registry.d.ts +0 -1
  24. package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
  25. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  26. package/dist/features/magic-context/fail-closed-block.d.ts +10 -0
  27. package/dist/features/magic-context/fail-closed-block.d.ts.map +1 -1
  28. package/dist/features/magic-context/git-commits/index.d.ts +2 -2
  29. package/dist/features/magic-context/git-commits/index.d.ts.map +1 -1
  30. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +0 -2
  31. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -1
  32. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts +0 -13
  33. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
  34. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
  35. package/dist/features/magic-context/memory/embedding-local.d.ts +7 -1
  36. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  37. package/dist/features/magic-context/memory/embedding.d.ts +1 -2
  38. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  39. package/dist/features/magic-context/memory/memory-migration.d.ts +0 -6
  40. package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
  41. package/dist/features/magic-context/memory/project-identity.d.ts +2 -6
  42. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  43. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +0 -1
  44. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  45. package/dist/features/magic-context/memory/storage-memory.d.ts +1 -6
  46. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  47. package/dist/features/magic-context/memory/verification-paths.d.ts +0 -1
  48. package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
  49. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  50. package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
  51. package/dist/features/magic-context/mural/render-mural.d.ts.map +1 -1
  52. package/dist/features/magic-context/mural/storage-mural.d.ts +0 -1
  53. package/dist/features/magic-context/mural/storage-mural.d.ts.map +1 -1
  54. package/dist/features/magic-context/overflow-detection.d.ts +8 -1
  55. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  56. package/dist/features/magic-context/project-embedding-registry.d.ts +16 -2
  57. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  58. package/dist/features/magic-context/scheduler.d.ts.map +1 -1
  59. package/dist/features/magic-context/schema-fence-probe.d.ts +28 -0
  60. package/dist/features/magic-context/schema-fence-probe.d.ts.map +1 -0
  61. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  62. package/dist/features/magic-context/smart-notes/capabilities.d.ts +0 -2
  63. package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
  64. package/dist/features/magic-context/smart-notes/compiler.d.ts +0 -1
  65. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  66. package/dist/features/magic-context/smart-notes/types.d.ts +0 -16
  67. package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
  68. package/dist/features/magic-context/storage-db.d.ts +35 -1
  69. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  70. package/dist/features/magic-context/storage-meta-persisted.d.ts +38 -5
  71. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  72. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  73. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  74. package/dist/features/magic-context/storage.d.ts +1 -1
  75. package/dist/features/magic-context/storage.d.ts.map +1 -1
  76. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  77. package/dist/features/magic-context/user-memory/storage-user-memory.d.ts +0 -2
  78. package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
  79. package/dist/hooks/auto-update-checker/types.d.ts +0 -3
  80. package/dist/hooks/auto-update-checker/types.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/boundary-execution.d.ts +1 -1
  82. package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -1
  83. package/dist/hooks/magic-context/cache-busting-signals.d.ts +1 -1
  84. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
  85. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  86. package/dist/hooks/magic-context/child-session-spawn.d.ts +26 -0
  87. package/dist/hooks/magic-context/child-session-spawn.d.ts.map +1 -0
  88. package/dist/hooks/magic-context/command-handler.d.ts +2 -0
  89. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  90. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +49 -0
  91. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  92. package/dist/hooks/magic-context/compaction-off-transition.d.ts +96 -0
  93. package/dist/hooks/magic-context/compaction-off-transition.d.ts.map +1 -0
  94. package/dist/hooks/magic-context/compartment-parser.d.ts +12 -0
  95. package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
  96. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
  97. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  98. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  99. package/dist/hooks/magic-context/compartment-runner-mapping.d.ts +0 -12
  100. package/dist/hooks/magic-context/compartment-runner-mapping.d.ts.map +1 -1
  101. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  102. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/compartment-trigger.d.ts +12 -5
  104. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +47 -1
  106. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
  107. package/dist/hooks/magic-context/emergency-drop.d.ts +2 -2
  108. package/dist/hooks/magic-context/event-handler.d.ts +7 -0
  109. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  110. package/dist/hooks/magic-context/event-resolvers.d.ts +13 -21
  111. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  112. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
  113. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  114. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -1
  115. package/dist/hooks/magic-context/historian-prompt.generated.d.ts +1 -1
  116. package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +1 -1
  117. package/dist/hooks/magic-context/hook-handlers.d.ts +1 -0
  118. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  119. package/dist/hooks/magic-context/hook.d.ts +11 -1
  120. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  121. package/dist/hooks/magic-context/inject-compartments.d.ts +18 -0
  122. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  123. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  124. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  125. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  126. package/dist/hooks/magic-context/rust-mode-transform.d.ts +3 -0
  127. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  128. package/dist/hooks/magic-context/send-session-notification.d.ts +20 -1
  129. package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
  130. package/dist/hooks/magic-context/sentinel.d.ts +39 -0
  131. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  132. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  133. package/dist/hooks/magic-context/tool-drop-target.d.ts +19 -0
  134. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  135. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -0
  136. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  137. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +30 -0
  138. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  139. package/dist/hooks/magic-context/transform.d.ts +27 -0
  140. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  141. package/dist/{index-kxz4tn6m.js → index-56yjaeb2.js} +56 -4
  142. package/dist/{index-bmkea2dq.js → index-b563sga5.js} +40 -287
  143. package/dist/{index-y1c9tcm3.js → index-cn960ffd.js} +101 -20
  144. package/dist/{index-k5y60ymm.js → index-dtas91s5.js} +27 -6
  145. package/dist/index-fcyvznch.js +279 -0
  146. package/dist/{index-63dq2g4d.js → index-ry8rh8cv.js} +138 -24
  147. package/dist/{index-h86twgsp.js → index-s0mdn690.js} +2113 -1067
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +1356 -627
  150. package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
  151. package/dist/plugin/hooks/create-session-hooks.d.ts +19 -8
  152. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  153. package/dist/plugin/messages-transform.d.ts +10 -0
  154. package/dist/plugin/messages-transform.d.ts.map +1 -1
  155. package/dist/plugin/rpc-handlers.d.ts +3 -7
  156. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  157. package/dist/plugin/tool-registry.d.ts +8 -0
  158. package/dist/plugin/tool-registry.d.ts.map +1 -1
  159. package/dist/{recomp-orchestrator-yh7pepvx.js → recomp-orchestrator-8e82vmvb.js} +6 -5
  160. package/dist/send-session-notification-6njqewzk.js +20 -0
  161. package/dist/shared/conflict-detector.d.ts +38 -1
  162. package/dist/shared/conflict-detector.d.ts.map +1 -1
  163. package/dist/shared/conflict-fixer.d.ts +18 -1
  164. package/dist/shared/conflict-fixer.d.ts.map +1 -1
  165. package/dist/shared/context-limit-provenance.d.ts +3 -0
  166. package/dist/shared/context-limit-provenance.d.ts.map +1 -0
  167. package/dist/shared/escalation-bands.d.ts +9 -0
  168. package/dist/shared/escalation-bands.d.ts.map +1 -0
  169. package/dist/shared/format-threshold.d.ts +1 -1
  170. package/dist/shared/models-dev-cache.d.ts +29 -3
  171. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  172. package/dist/shared/rpc-server.d.ts.map +1 -1
  173. package/dist/shared/rpc-types.d.ts +46 -1
  174. package/dist/shared/rpc-types.d.ts.map +1 -1
  175. package/dist/shared/sqlite.d.ts +9 -4
  176. package/dist/shared/sqlite.d.ts.map +1 -1
  177. package/dist/shared/storage-permissions.d.ts +5 -0
  178. package/dist/shared/storage-permissions.d.ts.map +1 -0
  179. package/dist/{storage-meta-session-1phy0b1z.js → storage-meta-session-xt41zbw4.js} +2 -1
  180. package/dist/{task-config-dn9k9szr.js → task-config-5nx3qfz8.js} +1 -2
  181. package/dist/tui/compaction-off.d.ts +13 -0
  182. package/dist/tui/compaction-off.d.ts.map +1 -0
  183. package/dist/tui/data/context-db.d.ts.map +1 -1
  184. package/dist/tui-compiled/compaction-off.d.ts +13 -0
  185. package/dist/tui-compiled/compaction-off.d.ts.map +1 -0
  186. package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
  187. package/package.json +8 -3
  188. package/src/agents/dreamer.ts +68 -0
  189. package/src/agents/hidden-agent-registrations.ts +331 -0
  190. package/src/agents/historian.ts +3 -0
  191. package/src/agents/language-directive.test.ts +138 -0
  192. package/src/agents/language-directive.ts +121 -0
  193. package/src/agents/magic-context-prompt.language.test.ts +35 -0
  194. package/src/agents/magic-context-prompt.test.ts +292 -0
  195. package/src/agents/magic-context-prompt.ts +182 -0
  196. package/src/agents/permissions.test.ts +252 -0
  197. package/src/agents/permissions.ts +186 -0
  198. package/src/agents/sidekick.ts +1 -0
  199. package/src/agents/smart-note-compiler.ts +1 -0
  200. package/src/config/agent-disable.ts +106 -0
  201. package/src/config/compaction-accessor-guard.test.ts +82 -0
  202. package/src/config/compaction-backcompat.test.ts +52 -0
  203. package/src/config/index.test.ts +904 -0
  204. package/src/config/index.ts +601 -0
  205. package/src/config/migrate-config-location.test.ts +332 -0
  206. package/src/config/migrate-config-location.ts +635 -0
  207. package/src/config/migrate-dreamer-v2.test.ts +287 -0
  208. package/src/config/migrate-dreamer-v2.ts +336 -0
  209. package/src/config/migrate-experimental.ts +159 -0
  210. package/src/config/project-security.test.ts +366 -0
  211. package/src/config/project-security.ts +588 -0
  212. package/src/config/prune-config-leaf.test.ts +78 -0
  213. package/src/config/prune-config-leaf.ts +68 -0
  214. package/src/config/removed-config-keys.test.ts +116 -0
  215. package/src/config/schema/agent-overrides.ts +45 -0
  216. package/src/config/schema/magic-context.test.ts +358 -0
  217. package/src/config/schema/magic-context.ts +1002 -0
  218. package/src/config/transform-mode.test.ts +62 -0
  219. package/src/config/transform-mode.ts +34 -0
  220. package/src/config/variable.test.ts +323 -0
  221. package/src/config/variable.ts +192 -0
  222. package/src/features/magic-context/defaults.ts +1 -0
  223. package/src/features/magic-context/dreamer/cron.ts +243 -0
  224. package/src/features/magic-context/dreamer/task-registry.ts +104 -0
  225. package/src/shared/conflict-detector.test.ts +251 -1
  226. package/src/shared/conflict-detector.ts +84 -5
  227. package/src/shared/conflict-fixer.test.ts +309 -0
  228. package/src/shared/conflict-fixer.ts +82 -5
  229. package/src/shared/context-limit-provenance.ts +6 -0
  230. package/src/shared/escalation-bands.test.ts +23 -0
  231. package/src/shared/escalation-bands.ts +18 -0
  232. package/src/shared/format-threshold.ts +1 -1
  233. package/src/shared/models-dev-cache.test.ts +171 -0
  234. package/src/shared/models-dev-cache.ts +195 -38
  235. package/src/shared/rpc-server.ts +27 -18
  236. package/src/shared/rpc-types.ts +46 -1
  237. package/src/shared/sqlite.ts +13 -33
  238. package/src/shared/storage-permissions.ts +20 -0
  239. package/src/tui/compaction-off.test.ts +56 -0
  240. package/src/tui/compaction-off.ts +29 -0
  241. package/src/tui/data/context-db.ts +6 -0
  242. package/src/tui/index.tsx +113 -65
  243. package/src/tui/slots/sidebar-content.tsx +139 -83
  244. package/src/tui-compiled/compaction-off.ts +29 -0
  245. package/src/tui-compiled/data/context-db.ts +6 -0
  246. package/src/tui-compiled/index.tsx +558 -455
  247. package/src/tui-compiled/slots/sidebar-content.tsx +237 -161
  248. package/dist/config/schema.d.ts +0 -3
  249. package/dist/config/schema.d.ts.map +0 -1
  250. package/dist/features/magic-context/plugin-messages.d.ts +0 -75
  251. package/dist/features/magic-context/plugin-messages.d.ts.map +0 -1
  252. package/dist/hooks/magic-context/compartment-runner-state-xml.d.ts +0 -21
  253. package/dist/hooks/magic-context/compartment-runner-state-xml.d.ts.map +0 -1
  254. package/dist/index-anhrfygk.js +0 -57
  255. package/dist/send-session-notification-tghq9gnh.js +0 -11
  256. package/dist/shared/opencode-compaction-detector.d.ts +0 -2
  257. package/dist/shared/opencode-compaction-detector.d.ts.map +0 -1
  258. package/dist/shared/transcript-opencode.d.ts +0 -71
  259. package/dist/shared/transcript-opencode.d.ts.map +0 -1
  260. package/dist/tools/index.d.ts +0 -6
  261. package/dist/tools/index.d.ts.map +0 -1
  262. package/src/shared/opencode-compaction-detector.test.ts +0 -239
  263. package/src/shared/opencode-compaction-detector.ts +0 -81
  264. package/src/shared/transcript-opencode.ts +0 -302
  265. /package/dist/{index-v7fmc1s2.js → index-ygcng9t8.js} +0 -0
@@ -8,8 +8,21 @@ export interface SidebarSnapshot {
8
8
  usagePercentage: number;
9
9
  inputTokens: number;
10
10
  contextLimit: number;
11
+ /**
12
+ * Raw wire-input pressure against the resolved model window. This is separate
13
+ * from Magic Context's execute threshold so native-compaction UI never shows
14
+ * a threshold-relative fill percentage.
15
+ */
16
+ native_context_usage_percentage?: number;
17
+ /**
18
+ * Magic Context compaction mode is resolved at startup and sent in the
19
+ * status-detail wire payload; snake_case matches that payload's field naming.
20
+ */
21
+ compaction_enabled?: boolean;
11
22
  systemPromptTokens: number;
12
23
  compartmentCount: number;
24
+ /** Historical compartment rows retained while native compaction owns the window. */
25
+ archivedCompartmentCount?: number;
13
26
  memoryCount: number;
14
27
  memoryBlockCount: number;
15
28
  pendingOpsCount: number;
@@ -18,6 +31,8 @@ export interface SidebarSnapshot {
18
31
  sessionNoteCount: number;
19
32
  readySmartNoteCount: number;
20
33
  cacheTtl: string;
34
+ /** Persistent runtime failure shown directly in the sidebar when non-null. */
35
+ lastTransformError: string | null;
21
36
  lastDreamerRunAt: number | null;
22
37
  projectIdentity: string | null;
23
38
  compartmentTokens: number;
@@ -69,7 +84,7 @@ export interface SidebarSnapshot {
69
84
  executeThreshold: number;
70
85
  /**
71
86
  * True when `executeThreshold` was clamped down from a higher configured value
72
- * (tokens config above 80% × contextLimit, or a percentage above the 80% cap).
87
+ * (tokens config above 90% × contextLimit, or a percentage above the 90% cap).
73
88
  * The sidebar/status dialog append a small marker so the user knows their
74
89
  * configured value was reduced rather than applied verbatim (issue #241).
75
90
  * Absent when no clamp occurred.
@@ -111,9 +126,20 @@ export interface StatusDetail extends SidebarSnapshot {
111
126
  isSubagent: boolean;
112
127
  pendingOps: Array<{ tagId: number; operation: string }>;
113
128
  contextLimit: number;
129
+ /**
130
+ * Parsed cache TTL in ms. -1 = never expires (cacheTtl "never"; Infinity
131
+ * cannot ride JSON-RPC and 0 would be indistinguishable from unset). The
132
+ * values discriminate without the cacheNeverExpires flag: -1 never /
133
+ * N live. Falsy-value contract: see storage_versions for the precedent.
134
+ */
114
135
  cacheTtlMs: number;
136
+ /** Remaining ms in the idle-TTL window. -1 = never expires; 0 = expired
137
+ * (only meaningful when lastResponseTime > 0); N = live countdown. */
115
138
  cacheRemainingMs: number;
116
139
  cacheExpired: boolean;
140
+ /** True when cacheTtl is "never" — the idle-TTL heuristic is disabled on
141
+ * this lane. Redundant with cacheTtlMs === -1; kept as the readable form. */
142
+ cacheNeverExpires?: boolean;
117
143
  executeThreshold: number;
118
144
  /**
119
145
  * Which config source produced `executeThreshold`. "tokens" means
@@ -135,6 +161,25 @@ export interface StatusDetail extends SidebarSnapshot {
135
161
  toastDurationMs: number;
136
162
  /** One-line status data for the experimental memory mural. */
137
163
  mural?: { present: boolean; ageMs: number | null };
164
+ /**
165
+ * Stable storage-version probe: "which schema is the DB at, which fence does
166
+ * this binary carry". Field names are deliberately snake_case, mirroring the
167
+ * `storage_versions` block of the mc-module status envelope, so fleet probes
168
+ * parse one shape across both surfaces. The module cannot read context.db
169
+ * (the plugin owns it), so this surface supplies the live DB value; the module
170
+ * surface supplies the module-store value instead.
171
+ */
172
+ storage_versions: {
173
+ /**
174
+ * Persisted schema version of context.db (MAX of schema_migrations).
175
+ * null = the version probe FAILED (read threw — broken/unreachable store);
176
+ * 0 = probe succeeded on a fresh DB without a migrations table. Distinct so
177
+ * readers never conflate broken-with-empty (fleet status-surface contract).
178
+ */
179
+ context_db_schema_version: number | null;
180
+ /** Highest context.db schema version this plugin build supports. */
181
+ plugin_supported_version: number;
182
+ };
138
183
  }
139
184
 
140
185
  /** Embedding coverage for `/ctx-embed` status (mirrors getEmbeddingCoverageStatus). */
@@ -201,30 +201,6 @@ export type Database = BetterSqlite3.Database;
201
201
  export type Statement = BetterSqlite3.Statement<unknown[], unknown>;
202
202
 
203
203
  const privilegeDepth = new WeakMap<Database, number>();
204
- const registeredPrivilegeFunctions = new WeakSet<Database>();
205
- const privilegeUdfAvailable = new WeakSet<Database>();
206
-
207
- /** Register the connection-local privilege predicate used by managed-write guards. */
208
- export function registerPrivilegedWriter(db: Database): void {
209
- if (registeredPrivilegeFunctions.has(db)) return;
210
- const native = db as unknown as {
211
- function?: (name: string, implementation: () => number) => void;
212
- createFunction?: (name: string, implementation: () => number) => void;
213
- };
214
- const implementation = (): number => ((privilegeDepth.get(db) ?? 0) > 0 ? 1 : 0);
215
- if (typeof native.function === "function") {
216
- native.function("mc_privileged_writer", implementation);
217
- } else if (typeof native.createFunction === "function") {
218
- native.createFunction("mc_privileged_writer", implementation);
219
- } else {
220
- // Older Bun releases do not expose scalar UDF registration. Migration 55
221
- // retains the pre-UDF state-table trigger on those runtimes.
222
- registeredPrivilegeFunctions.add(db);
223
- return;
224
- }
225
- privilegeUdfAvailable.add(db);
226
- registeredPrivilegeFunctions.add(db);
227
- }
228
204
 
229
205
  function isInTransaction(db: Database): boolean {
230
206
  const candidate = db as unknown as { inTransaction?: unknown; isTransaction?: unknown };
@@ -232,11 +208,17 @@ function isInTransaction(db: Database): boolean {
232
208
  }
233
209
 
234
210
  /**
235
- * Run a storage operation while the connection-local privilege predicate is enabled.
236
- * The depth lives outside SQLite, so a second connection can never observe or inherit it.
211
+ * Run a storage operation with the managed-write privilege enabled.
212
+ *
213
+ * The privilege is recorded in the durable `context_privilege_state` table (row
214
+ * id=1, enabled=1) so the guard triggers — which reference that table, never a
215
+ * connection-local UDF — stand down for this connection's writes. The write happens
216
+ * inside a BEGIN IMMEDIATE transaction (single writer), and enabled is cleared back
217
+ * to 0 before commit, so no second connection can ever observe enabled=1. Nesting is
218
+ * tracked by privilegeDepth (outside SQLite): only the outermost scope clears the
219
+ * flag, so an inner scope releasing does not drop permission out from under its caller.
237
220
  */
238
221
  export function withPrivilegedWriter<T>(db: Database, operation: () => T): T {
239
- registerPrivilegedWriter(db);
240
222
  const previousDepth = privilegeDepth.get(db) ?? 0;
241
223
  const nested = isInTransaction(db);
242
224
  const savepoint = "mc_privilege_scope";
@@ -247,13 +229,11 @@ export function withPrivilegedWriter<T>(db: Database, operation: () => T): T {
247
229
  }
248
230
  privilegeDepth.set(db, previousDepth + 1);
249
231
  try {
250
- if (!privilegeUdfAvailable.has(db)) {
251
- db.prepare(
252
- "INSERT INTO context_privilege_state(id, enabled) VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET enabled = 1",
253
- ).run();
254
- }
232
+ db.prepare(
233
+ "INSERT INTO context_privilege_state(id, enabled) VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET enabled = 1",
234
+ ).run();
255
235
  const result = operation();
256
- if (!privilegeUdfAvailable.has(db) && previousDepth === 0) {
236
+ if (previousDepth === 0) {
257
237
  db.prepare("UPDATE context_privilege_state SET enabled = 0 WHERE id = 1").run();
258
238
  }
259
239
  if (nested) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Process-wide storage permission policy resolved from trusted user config.
3
+ *
4
+ * Storage is shared by OpenCode and Pi, so all writers consult one setting before
5
+ * applying POSIX modes. The default preserves the historical owner-only policy.
6
+ */
7
+ let enforcePrivateStoragePermissions = true;
8
+
9
+ export function setStoragePrivatePermissionEnforcement(enforce: boolean): void {
10
+ enforcePrivateStoragePermissions = enforce;
11
+ }
12
+
13
+ export function shouldEnforcePrivateStoragePermissions(): boolean {
14
+ return enforcePrivateStoragePermissions;
15
+ }
16
+
17
+ /** Test-only reset for suites that exercise both permission policies in one process. */
18
+ export function __resetStoragePrivatePermissionEnforcementForTests(): void {
19
+ enforcePrivateStoragePermissions = true;
20
+ }
@@ -0,0 +1,56 @@
1
+ import { expect, test } from "bun:test";
2
+ import type { SidebarSnapshot } from "../shared/rpc-types";
3
+ import { compactionOffSidebarRows, nativeCompactionContextLabel } from "./compaction-off";
4
+
5
+ function snapshot(overrides: Partial<SidebarSnapshot> = {}): SidebarSnapshot {
6
+ return {
7
+ sessionId: "ses-native-ui",
8
+ usagePercentage: 63.1,
9
+ inputTokens: 41_000,
10
+ contextLimit: 100_000,
11
+ native_context_usage_percentage: 41,
12
+ compaction_enabled: false,
13
+ systemPromptTokens: 0,
14
+ compartmentCount: 12,
15
+ archivedCompartmentCount: 3,
16
+ memoryCount: 5,
17
+ memoryBlockCount: 2,
18
+ pendingOpsCount: 4,
19
+ historianRunning: true,
20
+ compartmentInProgress: true,
21
+ sessionNoteCount: 2,
22
+ readySmartNoteCount: 1,
23
+ cacheTtl: "5m",
24
+ lastDreamerRunAt: null,
25
+ projectIdentity: null,
26
+ compartmentTokens: 0,
27
+ factTokens: 0,
28
+ memoryTokens: 0,
29
+ docsTokens: 0,
30
+ profileTokens: 0,
31
+ conversationTokens: 0,
32
+ toolCallTokens: 0,
33
+ toolDefinitionTokens: 0,
34
+ executeThreshold: 65,
35
+ ...overrides,
36
+ };
37
+ }
38
+
39
+ test("renders raw input-versus-model usage rather than threshold-relative fill", () => {
40
+ const value = nativeCompactionContextLabel(snapshot());
41
+
42
+ expect(value).toBe("Context: 41.0% · native compaction");
43
+ expect(value).not.toBe("Context: 63.1% · native compaction");
44
+ });
45
+
46
+ test("keeps historical compartments as a static archived row", () => {
47
+ const initialRows = compactionOffSidebarRows(snapshot());
48
+ const activeCountChangedRows = compactionOffSidebarRows(snapshot({ compartmentCount: 99 }));
49
+
50
+ expect(initialRows).toEqual([
51
+ { label: "Memories", value: "5" },
52
+ { label: "Notes", value: "2" },
53
+ { label: "Archived compartments", value: "3" },
54
+ ]);
55
+ expect(activeCountChangedRows).toEqual(initialRows);
56
+ });
@@ -0,0 +1,29 @@
1
+ import type { SidebarSnapshot } from "../shared/rpc-types";
2
+
3
+ export interface CompactionOffSidebarRow {
4
+ label: "Memories" | "Notes" | "Archived compartments";
5
+ value: string;
6
+ }
7
+
8
+ /**
9
+ * Formats context pressure from the current wire input and the model context
10
+ * limit. It deliberately does not use the stored Magic Context threshold
11
+ * percentage, because native compaction acts on the model window instead.
12
+ */
13
+ export function nativeCompactionContextLabel(snapshot: SidebarSnapshot): string {
14
+ if (snapshot.contextLimit <= 0) return "Context: unknown · native compaction";
15
+ const percentage = (snapshot.inputTokens / snapshot.contextLimit) * 100;
16
+ return `Context: ${percentage.toFixed(1)}% · native compaction`;
17
+ }
18
+
19
+ export function compactionOffSidebarRows(snapshot: SidebarSnapshot): CompactionOffSidebarRow[] {
20
+ const rows: CompactionOffSidebarRow[] = [
21
+ { label: "Memories", value: String(snapshot.memoryCount) },
22
+ { label: "Notes", value: String(snapshot.sessionNoteCount) },
23
+ ];
24
+ const archivedCount = snapshot.archivedCompartmentCount ?? 0;
25
+ if (archivedCount > 0) {
26
+ rows.push({ label: "Archived compartments", value: String(archivedCount) });
27
+ }
28
+ return rows;
29
+ }
@@ -65,6 +65,7 @@ const EMPTY_SNAPSHOT: SidebarSnapshot = {
65
65
  sessionNoteCount: 0,
66
66
  readySmartNoteCount: 0,
67
67
  cacheTtl: "5m",
68
+ lastTransformError: null,
68
69
  lastDreamerRunAt: null,
69
70
  projectIdentity: null,
70
71
  compartmentTokens: 0,
@@ -194,6 +195,7 @@ export async function loadStatusDetail(
194
195
  cacheTtlMs: 0,
195
196
  cacheRemainingMs: 0,
196
197
  cacheExpired: false,
198
+ cacheNeverExpires: false,
197
199
  executeThreshold: 65,
198
200
  executeThresholdMode: "percentage",
199
201
  protectedTagCount: 20,
@@ -202,6 +204,10 @@ export async function loadStatusDetail(
202
204
  compressionBudget: null,
203
205
  compressionUsage: null,
204
206
  toastDurationMs: 5000,
207
+ storage_versions: {
208
+ context_db_schema_version: null,
209
+ plugin_supported_version: 0,
210
+ },
205
211
  };
206
212
 
207
213
  if (!rpcClient) return emptyDetail;
package/src/tui/index.tsx CHANGED
@@ -2,11 +2,18 @@
2
2
  // @ts-nocheck
3
3
  import { createMemo } from "solid-js"
4
4
  import type { TuiPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
5
- import { createSidebarContentSlot, kickRecompProgressRefresh } from "./slots/sidebar-content"
5
+ import {
6
+ createSidebarContentSlot,
7
+ kickRecompProgressRefresh,
8
+ refreshSidebarSnapshot,
9
+ } from "./slots/sidebar-content"
6
10
  import packageJson from "../../package.json"
7
11
  import { closeRpc, dismissUpgradeReminder, getAnnouncement, getCompartmentCount, getRpcGeneration, initRpcClient, loadEmbedDetail, loadStatusDetail, loadToastDurationMs, markAnnounced, requestRecomp, requestUpgrade, type EmbedDetail, type StatusDetail } from "./data/context-db"
8
12
  import { startNotificationSocket, stopNotificationSocket, type SocketNotification } from "./data/notification-socket"
9
13
  import { formatThresholdPercent } from "../shared/format-threshold"
14
+ import { compactionOffSidebarRows, nativeCompactionContextLabel } from "./compaction-off"
15
+ import { isCompactionEnabled } from "../config/agent-disable"
16
+ import { loadPluginConfig } from "../config"
10
17
  import { detectConflicts } from "../shared/conflict-detector"
11
18
  import { fixConflicts } from "../shared/conflict-fixer"
12
19
 
@@ -130,6 +137,7 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
130
137
  const theme = createMemo(() => (props.api as any).theme.current)
131
138
  const t = () => theme()
132
139
  const s = () => props.s
140
+ const compactionOff = () => s().compaction_enabled === false
133
141
 
134
142
  // Prefer the RPC-provided model context limit (what the sidebar shows) so the
135
143
  // two surfaces never disagree. Fall back to deriving from usage% only when the
@@ -169,7 +177,7 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
169
177
  segs.push({ label: "System", tokens: d.systemPromptTokens, color: COLORS.system })
170
178
  if (d.docsTokens > 0)
171
179
  segs.push({ label: "Docs", tokens: d.docsTokens, color: COLORS.docs })
172
- if (d.compartmentTokens > 0)
180
+ if (!compactionOff() && d.compartmentTokens > 0)
173
181
  segs.push({
174
182
  label: "Compartments",
175
183
  tokens: d.compartmentTokens,
@@ -217,15 +225,17 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
217
225
  <text fg={t().textMuted}>v{packageJson.version}</text>
218
226
  </box>
219
227
 
220
- {/* Context summary line. Mirrors the sidebar header layout
221
- ("47.5% / 65% 475K / 1.0M") so users can recognize the
222
- same shape in the status dialog. The execute threshold tells
223
- them how close they are to compaction triggering. */}
224
228
  <box flexDirection="row" justifyContent="space-between" width="100%">
225
- <text fg={s().usagePercentage >= 80 ? t().error : s().usagePercentage >= 65 ? t().warning : t().accent}>
226
- <b>{s().usagePercentage.toFixed(1)}%</b> / {formatThresholdPercent(s().executeThreshold)}%{s().executeThresholdClamped ? "*" : ""}
227
- </text>
228
- <text fg={s().usagePercentage >= 80 ? t().error : s().usagePercentage >= 65 ? t().warning : t().accent}>
229
+ {compactionOff() ? (
230
+ <text fg={t().accent}>
231
+ <b>{nativeCompactionContextLabel(s())}</b>
232
+ </text>
233
+ ) : (
234
+ <text fg={s().usagePercentage >= 80 ? t().error : s().usagePercentage >= 65 ? t().warning : t().accent}>
235
+ <b>{s().usagePercentage.toFixed(1)}%</b> / {formatThresholdPercent(s().executeThreshold)}%{s().executeThresholdClamped ? "*" : ""}
236
+ </text>
237
+ )}
238
+ <text fg={compactionOff() ? t().accent : s().usagePercentage >= 80 ? t().error : s().usagePercentage >= 65 ? t().warning : t().accent}>
229
239
  {fmt(s().inputTokens)} / {contextLimit() > 0 ? fmt(contextLimit()) : "?"} tokens
230
240
  </text>
231
241
  </box>
@@ -259,7 +269,7 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
259
269
 
260
270
  {/* Recomp / session-upgrade live progress (full width, only while
261
271
  running or just finished — dogfood 2026-05-30). */}
262
- {s().recompProgress && (() => {
272
+ {!compactionOff() && s().recompProgress && (() => {
263
273
  const p = s().recompProgress!
264
274
  // Label follows the flow that started the run, so a plain
265
275
  // /ctx-recomp never reads as an "Upgrade" (dogfood 2026-06-04).
@@ -295,61 +305,80 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
295
305
  )
296
306
  })()}
297
307
 
298
- {/* 2-column layout */}
299
308
  <box flexDirection="row" width="100%" marginTop={1} gap={4}>
300
- {/* Left column */}
301
- <box flexDirection="column" flexGrow={1} flexBasis={0}>
302
- <text fg={t().text}><b>Tags</b></text>
303
- <R t={t()} l="Active" v={`${s().activeTags} (~${fmtBytes(s().activeBytes)})`} />
304
- <R t={t()} l="Dropped" v={String(s().droppedTags)} />
305
- <R t={t()} l="Total" v={String(s().totalTags)} fg={t().textMuted} />
306
- <box marginTop={1}>
307
- <text fg={t().text}><b>Pending Queue</b></text>
308
- </box>
309
- <R t={t()} l="Drops" v={String(s().pendingOpsCount)} fg={s().pendingOpsCount > 0 ? t().warning : t().textMuted} />
310
- <box marginTop={1}>
311
- <text fg={t().text}><b>Cache TTL</b></text>
312
- </box>
313
- <R t={t()} l="Configured" v={s().cacheTtl} />
314
- <R t={t()} l="Last response" v={s().lastResponseTime > 0 ? `${Math.round(elapsed() / 1000)}s ago` : "never"} />
315
- <R t={t()} l="Remaining" v={s().cacheExpired ? "expired" : `${Math.round(s().cacheRemainingMs / 1000)}s`} fg={s().cacheExpired ? t().warning : t().textMuted} />
316
- <R t={t()} l="Auto-execute" v={s().cacheExpired ? "yes (expired)" : `at TTL or ≥${formatThresholdPercent(s().executeThreshold)}%`} fg={t().textMuted} />
317
- <box marginTop={1}>
318
- <text fg={t().text}><b>Memory</b></text>
319
- </box>
320
- <R t={t()} l="Active" v={String(s().memoryCount)} fg={t().accent} />
321
- <R t={t()} l="Injected" v={String(s().memoryBlockCount)} fg={t().textMuted} />
322
- </box>
323
- {/* Right column */}
324
- <box flexDirection="column" flexGrow={1} flexBasis={0}>
325
- <text fg={t().text}><b>Reductions</b></text>
326
- <R t={t()} l="Execute threshold" v={`${formatThresholdPercent(s().executeThreshold)}%${s().executeThresholdClamped ? "*" : ""}`} />
327
- <R t={t()} l="Last reduce anchor" v={`${fmt(s().lastNudgeTokens)} tok`} />
328
- <box marginTop={1}>
329
- <text fg={t().text}><b>Context Details</b></text>
330
- </box>
331
- <R t={t()} l="Protected tags" v={String(s().protectedTagCount)} fg={t().textMuted} />
332
- <R t={t()} l="Subagent" v={s().isSubagent ? "yes" : "no"} fg={t().textMuted} />
333
- <box marginTop={1}>
334
- <text fg={t().text}><b>History Compression</b></text>
309
+ {compactionOff() ? (
310
+ <box flexDirection="column" flexGrow={1} flexBasis={0}>
311
+ <text fg={t().text}><b>Knowledge</b></text>
312
+ {compactionOffSidebarRows(s()).map((row) => (
313
+ <R
314
+ t={t()}
315
+ l={row.label}
316
+ v={row.value}
317
+ fg={row.label === "Memories" ? t().accent : t().textMuted}
318
+ />
319
+ ))}
320
+ {s().readySmartNoteCount > 0 && (
321
+ <R t={t()} l="Smart Notes" v={`${s().readySmartNoteCount} ready`} fg={t().accent} />
322
+ )}
323
+ {s().lastDreamerRunAt && (
324
+ <R t={t()} l="Dreamer" v={`last ${relTime(s().lastDreamerRunAt!)}`} fg={t().textMuted} />
325
+ )}
335
326
  </box>
336
- {typeof s().boundaryPresent === "boolean" && (
337
- <R t={t()} l="Boundary" v={s().boundaryPresent ? "present" : "absent"} />
338
- )}
339
- {s().coverageOrdinal !== undefined && (
340
- <R t={t()} l="Coverage ordinal" v={s().coverageOrdinal == null ? "none" : String(s().coverageOrdinal)} />
341
- )}
342
- {typeof s().boundaryPresent === "boolean" && (
343
- <R t={t()} l="Compartments" v={String(s().compartmentCount)} />
344
- )}
345
- <R t={t()} l="History block" v={`~${fmt(s().historyBlockTokens)} tok`} />
346
- {s().compressionBudget != null && (
347
- <R t={t()} l="Budget" v={`~${fmt(s().compressionBudget!)} tok (${s().compressionUsage} used)`} />
348
- )}
349
- {s().lastDreamerRunAt && (
350
- <R t={t()} l="Dreamer" v={`last ${relTime(s().lastDreamerRunAt!)}`} fg={t().textMuted} />
351
- )}
352
- </box>
327
+ ) : (
328
+ <>
329
+ <box flexDirection="column" flexGrow={1} flexBasis={0}>
330
+ <text fg={t().text}><b>Tags</b></text>
331
+ <R t={t()} l="Active" v={`${s().activeTags} (~${fmtBytes(s().activeBytes)})`} />
332
+ <R t={t()} l="Dropped" v={String(s().droppedTags)} />
333
+ <R t={t()} l="Total" v={String(s().totalTags)} fg={t().textMuted} />
334
+ <box marginTop={1}>
335
+ <text fg={t().text}><b>Pending Queue</b></text>
336
+ </box>
337
+ <R t={t()} l="Drops" v={String(s().pendingOpsCount)} fg={s().pendingOpsCount > 0 ? t().warning : t().textMuted} />
338
+ <box marginTop={1}>
339
+ <text fg={t().text}><b>Cache TTL</b></text>
340
+ </box>
341
+ <R t={t()} l="Configured" v={s().cacheTtl} />
342
+ <R t={t()} l="Last response" v={s().lastResponseTime > 0 ? `${Math.round(elapsed() / 1000)}s ago` : "never"} />
343
+ <R t={t()} l="Remaining" v={s().cacheExpired ? "expired" : s().cacheNeverExpires ? "never expires (always-warm lane)" : `${Math.round(s().cacheRemainingMs / 1000)}s`} fg={s().cacheExpired ? t().warning : t().textMuted} />
344
+ <R t={t()} l="Auto-execute" v={s().cacheExpired ? "yes (expired)" : s().cacheNeverExpires ? `at ≥${formatThresholdPercent(s().executeThreshold)}%` : `at TTL or ≥${formatThresholdPercent(s().executeThreshold)}%`} fg={t().textMuted} />
345
+ <box marginTop={1}>
346
+ <text fg={t().text}><b>Memory</b></text>
347
+ </box>
348
+ <R t={t()} l="Active" v={String(s().memoryCount)} fg={t().accent} />
349
+ <R t={t()} l="Injected" v={String(s().memoryBlockCount)} fg={t().textMuted} />
350
+ </box>
351
+ <box flexDirection="column" flexGrow={1} flexBasis={0}>
352
+ <text fg={t().text}><b>Reductions</b></text>
353
+ <R t={t()} l="Execute threshold" v={`${formatThresholdPercent(s().executeThreshold)}%${s().executeThresholdClamped ? "*" : ""}`} />
354
+ <R t={t()} l="Last reduce anchor" v={`${fmt(s().lastNudgeTokens)} tok`} />
355
+ <box marginTop={1}>
356
+ <text fg={t().text}><b>Context Details</b></text>
357
+ </box>
358
+ <R t={t()} l="Protected tags" v={String(s().protectedTagCount)} fg={t().textMuted} />
359
+ <R t={t()} l="Subagent" v={s().isSubagent ? "yes" : "no"} fg={t().textMuted} />
360
+ <box marginTop={1}>
361
+ <text fg={t().text}><b>History Compression</b></text>
362
+ </box>
363
+ {typeof s().boundaryPresent === "boolean" && (
364
+ <R t={t()} l="Boundary" v={s().boundaryPresent ? "present" : "absent"} />
365
+ )}
366
+ {s().coverageOrdinal !== undefined && (
367
+ <R t={t()} l="Coverage ordinal" v={s().coverageOrdinal == null ? "none" : String(s().coverageOrdinal)} />
368
+ )}
369
+ {typeof s().boundaryPresent === "boolean" && (
370
+ <R t={t()} l="Compartments" v={String(s().compartmentCount)} />
371
+ )}
372
+ <R t={t()} l="History block" v={`~${fmt(s().historyBlockTokens)} tok`} />
373
+ {s().compressionBudget != null && (
374
+ <R t={t()} l="Budget" v={`~${fmt(s().compressionBudget!)} tok (${s().compressionUsage} used)`} />
375
+ )}
376
+ {s().lastDreamerRunAt && (
377
+ <R t={t()} l="Dreamer" v={`last ${relTime(s().lastDreamerRunAt!)}`} fg={t().textMuted} />
378
+ )}
379
+ </box>
380
+ </>
381
+ )}
353
382
  </box>
354
383
 
355
384
  {/* Error (full width, conditional) */}
@@ -945,7 +974,21 @@ const tui: TuiPlugin = async (api, _options, meta) => {
945
974
  const directory = api.state.path.directory ?? ""
946
975
  // A conflicted installation intentionally has no server. Gate before RPC
947
976
  // discovery or socket startup so disabled installs perform no idle work.
948
- const conflictResult = detectConflicts(directory)
977
+ // The resolved MC compaction mode is threaded in explicitly via the same
978
+ // loader + accessor the plugin boot uses, so the TUI never re-derives the
979
+ // compaction decision from directory alone. On config load failure the
980
+ // accessor resolves default-on (mode-on), preserving today's conflict
981
+ // gate rather than silently skipping the check.
982
+ let pluginConfig: ReturnType<typeof loadPluginConfig> | undefined
983
+ try {
984
+ pluginConfig = loadPluginConfig(directory)
985
+ } catch {
986
+ // Config load failure: fail toward mode-on (today's behavior) by
987
+ // leaving pluginConfig undefined so isCompactionEnabled defaults true.
988
+ }
989
+ const conflictResult = detectConflicts(directory, {
990
+ compactionEnabled: isCompactionEnabled(pluginConfig ?? {}),
991
+ })
949
992
  if (conflictResult.hasConflict) {
950
993
  showConflictDialog(api, directory, conflictResult.reasons, conflictResult.conflicts)
951
994
  return
@@ -1023,6 +1066,11 @@ const tui: TuiPlugin = async (api, _options, meta) => {
1023
1066
  if (action === "show-embed-dialog") {
1024
1067
  return stillActive() && (await showEmbedDialog(api, requestedSessionId))
1025
1068
  }
1069
+ if (action === "refresh-sidebar") {
1070
+ if (!stillActive()) return false
1071
+ refreshSidebarSnapshot()
1072
+ return true
1073
+ }
1026
1074
  if (action === "wrapup-progress-kick") {
1027
1075
  // /ctx-wrapup blocks its command turn and fires no message events, so
1028
1076
  // the sidebar poll would never notice the run. Kick the fast progress