@cortexkit/opencode-magic-context 0.33.1 → 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 (205) 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 +82 -2
  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-n48c783v.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 +57 -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.map +1 -1
  17. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
  19. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
  21. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  23. package/dist/features/magic-context/fail-closed-block.d.ts +10 -0
  24. package/dist/features/magic-context/fail-closed-block.d.ts.map +1 -1
  25. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -1
  26. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
  27. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
  28. package/dist/features/magic-context/memory/embedding-local.d.ts +7 -1
  29. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  30. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  31. package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
  32. package/dist/features/magic-context/memory/project-identity.d.ts +2 -6
  33. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  34. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  35. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  36. package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
  37. package/dist/features/magic-context/mural/render-mural.d.ts.map +1 -1
  38. package/dist/features/magic-context/overflow-detection.d.ts +8 -1
  39. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  40. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  41. package/dist/features/magic-context/scheduler.d.ts.map +1 -1
  42. package/dist/features/magic-context/schema-fence-probe.d.ts +28 -0
  43. package/dist/features/magic-context/schema-fence-probe.d.ts.map +1 -0
  44. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  45. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  46. package/dist/features/magic-context/storage-db.d.ts +35 -1
  47. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  48. package/dist/features/magic-context/storage-meta-persisted.d.ts +38 -4
  49. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  50. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  51. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  52. package/dist/features/magic-context/storage.d.ts +1 -1
  53. package/dist/features/magic-context/storage.d.ts.map +1 -1
  54. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/boundary-execution.d.ts +1 -1
  56. package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/cache-busting-signals.d.ts +1 -0
  58. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/child-session-spawn.d.ts +26 -0
  60. package/dist/hooks/magic-context/child-session-spawn.d.ts.map +1 -0
  61. package/dist/hooks/magic-context/command-handler.d.ts +2 -0
  62. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/compaction-off-transition.d.ts +96 -0
  64. package/dist/hooks/magic-context/compaction-off-transition.d.ts.map +1 -0
  65. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
  66. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/compartment-runner-mapping.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/compartment-trigger.d.ts +12 -5
  72. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  73. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +47 -1
  74. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/emergency-drop.d.ts +2 -2
  76. package/dist/hooks/magic-context/event-handler.d.ts +7 -0
  77. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  78. package/dist/hooks/magic-context/event-resolvers.d.ts +13 -21
  79. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  80. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
  81. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  82. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -1
  83. package/dist/hooks/magic-context/hook-handlers.d.ts +1 -0
  84. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  85. package/dist/hooks/magic-context/hook.d.ts +11 -1
  86. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  87. package/dist/hooks/magic-context/inject-compartments.d.ts +9 -0
  88. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  89. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  90. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  91. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  92. package/dist/hooks/magic-context/rust-mode-transform.d.ts +3 -0
  93. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  94. package/dist/hooks/magic-context/send-session-notification.d.ts +20 -1
  95. package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
  96. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -0
  97. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  98. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +30 -0
  99. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  100. package/dist/hooks/magic-context/transform.d.ts +27 -0
  101. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  102. package/dist/{index-ey1bc14z.js → index-b563sga5.js} +7 -264
  103. package/dist/{index-y1c9tcm3.js → index-cn960ffd.js} +101 -20
  104. package/dist/{index-xyaekybw.js → index-dtas91s5.js} +6 -4
  105. package/dist/index-fcyvznch.js +279 -0
  106. package/dist/{index-63dq2g4d.js → index-ry8rh8cv.js} +138 -24
  107. package/dist/{index-c2gahrks.js → index-s0mdn690.js} +1599 -892
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +1292 -623
  110. package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
  111. package/dist/plugin/hooks/create-session-hooks.d.ts +11 -0
  112. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  113. package/dist/plugin/messages-transform.d.ts +10 -0
  114. package/dist/plugin/messages-transform.d.ts.map +1 -1
  115. package/dist/plugin/rpc-handlers.d.ts +3 -7
  116. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  117. package/dist/plugin/tool-registry.d.ts +8 -0
  118. package/dist/plugin/tool-registry.d.ts.map +1 -1
  119. package/dist/{recomp-orchestrator-2yb2hfh2.js → recomp-orchestrator-8e82vmvb.js} +6 -4
  120. package/dist/send-session-notification-6njqewzk.js +20 -0
  121. package/dist/shared/conflict-detector.d.ts +38 -1
  122. package/dist/shared/conflict-detector.d.ts.map +1 -1
  123. package/dist/shared/conflict-fixer.d.ts +18 -1
  124. package/dist/shared/conflict-fixer.d.ts.map +1 -1
  125. package/dist/shared/context-limit-provenance.d.ts +3 -0
  126. package/dist/shared/context-limit-provenance.d.ts.map +1 -0
  127. package/dist/shared/escalation-bands.d.ts +9 -0
  128. package/dist/shared/escalation-bands.d.ts.map +1 -0
  129. package/dist/shared/format-threshold.d.ts +1 -1
  130. package/dist/shared/models-dev-cache.d.ts +29 -3
  131. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  132. package/dist/shared/rpc-server.d.ts.map +1 -1
  133. package/dist/shared/rpc-types.d.ts +46 -1
  134. package/dist/shared/rpc-types.d.ts.map +1 -1
  135. package/dist/shared/storage-permissions.d.ts +5 -0
  136. package/dist/shared/storage-permissions.d.ts.map +1 -0
  137. package/dist/{storage-meta-session-td6bwtky.js → storage-meta-session-xt41zbw4.js} +2 -1
  138. package/dist/tui/compaction-off.d.ts +13 -0
  139. package/dist/tui/compaction-off.d.ts.map +1 -0
  140. package/dist/tui/data/context-db.d.ts.map +1 -1
  141. package/dist/tui-compiled/compaction-off.d.ts +13 -0
  142. package/dist/tui-compiled/compaction-off.d.ts.map +1 -0
  143. package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
  144. package/package.json +7 -2
  145. package/src/agents/dreamer.ts +68 -0
  146. package/src/agents/hidden-agent-registrations.ts +331 -0
  147. package/src/agents/historian.ts +3 -0
  148. package/src/agents/language-directive.test.ts +138 -0
  149. package/src/agents/language-directive.ts +121 -0
  150. package/src/agents/magic-context-prompt.language.test.ts +35 -0
  151. package/src/agents/magic-context-prompt.test.ts +292 -0
  152. package/src/agents/magic-context-prompt.ts +182 -0
  153. package/src/agents/permissions.test.ts +252 -0
  154. package/src/agents/permissions.ts +186 -0
  155. package/src/agents/sidekick.ts +1 -0
  156. package/src/agents/smart-note-compiler.ts +1 -0
  157. package/src/config/agent-disable.ts +106 -0
  158. package/src/config/compaction-accessor-guard.test.ts +82 -0
  159. package/src/config/compaction-backcompat.test.ts +52 -0
  160. package/src/config/index.test.ts +904 -0
  161. package/src/config/index.ts +601 -0
  162. package/src/config/migrate-config-location.test.ts +332 -0
  163. package/src/config/migrate-config-location.ts +635 -0
  164. package/src/config/migrate-dreamer-v2.test.ts +287 -0
  165. package/src/config/migrate-dreamer-v2.ts +336 -0
  166. package/src/config/migrate-experimental.ts +159 -0
  167. package/src/config/project-security.test.ts +366 -0
  168. package/src/config/project-security.ts +588 -0
  169. package/src/config/prune-config-leaf.test.ts +78 -0
  170. package/src/config/prune-config-leaf.ts +68 -0
  171. package/src/config/removed-config-keys.test.ts +116 -0
  172. package/src/config/schema/agent-overrides.ts +45 -0
  173. package/src/config/schema/magic-context.test.ts +358 -0
  174. package/src/config/schema/magic-context.ts +1002 -0
  175. package/src/config/transform-mode.test.ts +62 -0
  176. package/src/config/transform-mode.ts +34 -0
  177. package/src/config/variable.test.ts +323 -0
  178. package/src/config/variable.ts +192 -0
  179. package/src/features/magic-context/defaults.ts +1 -0
  180. package/src/features/magic-context/dreamer/cron.ts +243 -0
  181. package/src/features/magic-context/dreamer/task-registry.ts +104 -0
  182. package/src/shared/conflict-detector.test.ts +121 -0
  183. package/src/shared/conflict-detector.ts +50 -3
  184. package/src/shared/conflict-fixer.test.ts +309 -0
  185. package/src/shared/conflict-fixer.ts +82 -5
  186. package/src/shared/context-limit-provenance.ts +6 -0
  187. package/src/shared/escalation-bands.test.ts +23 -0
  188. package/src/shared/escalation-bands.ts +18 -0
  189. package/src/shared/format-threshold.ts +1 -1
  190. package/src/shared/models-dev-cache.test.ts +171 -0
  191. package/src/shared/models-dev-cache.ts +195 -38
  192. package/src/shared/rpc-server.ts +27 -18
  193. package/src/shared/rpc-types.ts +46 -1
  194. package/src/shared/storage-permissions.ts +20 -0
  195. package/src/tui/compaction-off.test.ts +56 -0
  196. package/src/tui/compaction-off.ts +29 -0
  197. package/src/tui/data/context-db.ts +6 -0
  198. package/src/tui/index.tsx +113 -65
  199. package/src/tui/slots/sidebar-content.tsx +139 -83
  200. package/src/tui-compiled/compaction-off.ts +29 -0
  201. package/src/tui-compiled/data/context-db.ts +6 -0
  202. package/src/tui-compiled/index.tsx +558 -455
  203. package/src/tui-compiled/slots/sidebar-content.tsx +237 -161
  204. package/dist/send-session-notification-tghq9gnh.js +0 -11
  205. /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). */
@@ -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