@cortexkit/opencode-magic-context 0.33.1 → 0.34.1

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 (240) hide show
  1. package/README.md +20 -3
  2. package/dist/config/agent-disable.d.ts +22 -0
  3. package/dist/config/agent-disable.d.ts.map +1 -1
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/migrate-experimental.d.ts +2 -1
  6. package/dist/config/migrate-experimental.d.ts.map +1 -1
  7. package/dist/config/project-security.d.ts +9 -0
  8. package/dist/config/project-security.d.ts.map +1 -1
  9. package/dist/config/schema/magic-context.d.ts +90 -17
  10. package/dist/config/schema/magic-context.d.ts.map +1 -1
  11. package/dist/config/transform-mode.d.ts +2 -0
  12. package/dist/config/transform-mode.d.ts.map +1 -1
  13. package/dist/{conflict-warning-hook-n48c783v.js → conflict-warning-hook-fvpfvt0w.js} +1 -1
  14. package/dist/{derive-budgets-e9j09sjr.js → derive-budgets-9crp0cvd.js} +1 -1
  15. package/dist/features/builtin-commands/commands.d.ts +1 -1
  16. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  17. package/dist/features/magic-context/compaction-marker.d.ts +57 -0
  18. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  19. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/classify.d.ts +1 -0
  21. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
  23. package/dist/features/magic-context/dreamer/map-memories.d.ts +1 -0
  24. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  25. package/dist/features/magic-context/dreamer/refresh-primers.d.ts +1 -0
  26. package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
  27. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts +2 -0
  28. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
  29. package/dist/features/magic-context/dreamer/task-executor.d.ts +4 -1
  30. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  31. package/dist/features/magic-context/dreamer/task-gates.d.ts +19 -1
  32. package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
  33. package/dist/features/magic-context/dreamer/task-registry.d.ts +28 -0
  34. package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
  35. package/dist/features/magic-context/dreamer/task-scheduler.d.ts +7 -1
  36. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  37. package/dist/features/magic-context/dreamer/verify.d.ts +1 -0
  38. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  39. package/dist/features/magic-context/fail-closed-block.d.ts +10 -0
  40. package/dist/features/magic-context/fail-closed-block.d.ts.map +1 -1
  41. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -1
  42. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
  43. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
  44. package/dist/features/magic-context/memory/embedding-local.d.ts +7 -1
  45. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  46. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  47. package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -1
  48. package/dist/features/magic-context/memory/project-identity.d.ts +2 -6
  49. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  50. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  51. package/dist/features/magic-context/migrations.d.ts +23 -1
  52. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  53. package/dist/features/magic-context/mural/compress-cues.d.ts +1 -0
  54. package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
  55. package/dist/features/magic-context/mural/render-mural.d.ts.map +1 -1
  56. package/dist/features/magic-context/overflow-detection.d.ts +8 -1
  57. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  58. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  59. package/dist/features/magic-context/scheduler.d.ts.map +1 -1
  60. package/dist/features/magic-context/schema-fence-probe.d.ts +28 -0
  61. package/dist/features/magic-context/schema-fence-probe.d.ts.map +1 -0
  62. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  63. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  64. package/dist/features/magic-context/storage-db.d.ts +37 -2
  65. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  66. package/dist/features/magic-context/storage-meta-persisted.d.ts +38 -4
  67. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  68. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  69. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  70. package/dist/features/magic-context/storage.d.ts +2 -2
  71. package/dist/features/magic-context/storage.d.ts.map +1 -1
  72. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  73. package/dist/hooks/magic-context/boundary-execution.d.ts +1 -1
  74. package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/cache-busting-signals.d.ts +1 -0
  76. package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
  77. package/dist/hooks/magic-context/child-session-spawn.d.ts +26 -0
  78. package/dist/hooks/magic-context/child-session-spawn.d.ts.map +1 -0
  79. package/dist/hooks/magic-context/command-handler.d.ts +3 -0
  80. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/compaction-off-transition.d.ts +96 -0
  82. package/dist/hooks/magic-context/compaction-off-transition.d.ts.map +1 -0
  83. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
  84. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  85. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  86. package/dist/hooks/magic-context/compartment-runner-mapping.d.ts.map +1 -1
  87. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  88. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  89. package/dist/hooks/magic-context/compartment-trigger.d.ts +12 -5
  90. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  91. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +47 -1
  92. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
  93. package/dist/hooks/magic-context/emergency-drop.d.ts +2 -2
  94. package/dist/hooks/magic-context/event-handler.d.ts +7 -0
  95. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  96. package/dist/hooks/magic-context/event-resolvers.d.ts +13 -21
  97. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  98. package/dist/hooks/magic-context/execute-status.d.ts +5 -1
  99. package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
  100. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
  101. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  102. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -1
  103. package/dist/hooks/magic-context/hook-handlers.d.ts +1 -0
  104. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/hook.d.ts +14 -6
  106. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  107. package/dist/hooks/magic-context/inject-compartments.d.ts +10 -1
  108. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  109. package/dist/hooks/magic-context/live-session-state.d.ts +3 -0
  110. package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
  111. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  112. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  113. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  114. package/dist/hooks/magic-context/rust-mode-transform.d.ts +3 -0
  115. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  116. package/dist/hooks/magic-context/send-session-notification.d.ts +20 -1
  117. package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
  118. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -0
  119. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  120. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +31 -1
  121. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  122. package/dist/hooks/magic-context/transform.d.ts +29 -2
  123. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  124. package/dist/{index-56yjaeb2.js → index-2y0ybv5x.js} +11 -1
  125. package/dist/{index-ey1bc14z.js → index-b563sga5.js} +7 -264
  126. package/dist/{index-y1c9tcm3.js → index-cn960ffd.js} +101 -20
  127. package/dist/{index-xyaekybw.js → index-dtas91s5.js} +6 -4
  128. package/dist/index-fcyvznch.js +279 -0
  129. package/dist/{index-63dq2g4d.js → index-ry8rh8cv.js} +138 -24
  130. package/dist/{index-c2gahrks.js → index-x8spt5wy.js} +1682 -893
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +1677 -723
  133. package/dist/plugin/dream-timer.d.ts +4 -2
  134. package/dist/plugin/dream-timer.d.ts.map +1 -1
  135. package/dist/plugin/embedding-bootstrap.d.ts.map +1 -1
  136. package/dist/plugin/hooks/create-session-hooks.d.ts +12 -1
  137. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  138. package/dist/plugin/messages-transform.d.ts +10 -0
  139. package/dist/plugin/messages-transform.d.ts.map +1 -1
  140. package/dist/plugin/rpc-handlers.d.ts +3 -7
  141. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  142. package/dist/plugin/tool-registry.d.ts +8 -0
  143. package/dist/plugin/tool-registry.d.ts.map +1 -1
  144. package/dist/{recomp-orchestrator-2yb2hfh2.js → recomp-orchestrator-k72ejkkh.js} +6 -4
  145. package/dist/send-session-notification-6njqewzk.js +20 -0
  146. package/dist/shared/conflict-detector.d.ts +38 -1
  147. package/dist/shared/conflict-detector.d.ts.map +1 -1
  148. package/dist/shared/conflict-fixer.d.ts +18 -1
  149. package/dist/shared/conflict-fixer.d.ts.map +1 -1
  150. package/dist/shared/context-limit-provenance.d.ts +3 -0
  151. package/dist/shared/context-limit-provenance.d.ts.map +1 -0
  152. package/dist/shared/escalation-bands.d.ts +9 -0
  153. package/dist/shared/escalation-bands.d.ts.map +1 -0
  154. package/dist/shared/format-threshold.d.ts +1 -1
  155. package/dist/shared/models-dev-cache.d.ts +29 -3
  156. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  157. package/dist/shared/rpc-server.d.ts.map +1 -1
  158. package/dist/shared/rpc-types.d.ts +51 -1
  159. package/dist/shared/rpc-types.d.ts.map +1 -1
  160. package/dist/shared/rpc-utils.d.ts +19 -0
  161. package/dist/shared/rpc-utils.d.ts.map +1 -1
  162. package/dist/shared/storage-permissions.d.ts +5 -0
  163. package/dist/shared/storage-permissions.d.ts.map +1 -0
  164. package/dist/shared/tui-runtime-specifiers.d.ts +20 -0
  165. package/dist/shared/tui-runtime-specifiers.d.ts.map +1 -0
  166. package/dist/{storage-meta-session-td6bwtky.js → storage-meta-session-xt41zbw4.js} +2 -1
  167. package/dist/{task-config-5nx3qfz8.js → task-config-gbp7r23g.js} +1 -1
  168. package/dist/tui/compaction-off.d.ts +13 -0
  169. package/dist/tui/compaction-off.d.ts.map +1 -0
  170. package/dist/tui/data/context-db.d.ts.map +1 -1
  171. package/dist/tui-compiled/compaction-off.d.ts +13 -0
  172. package/dist/tui-compiled/compaction-off.d.ts.map +1 -0
  173. package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
  174. package/package.json +6 -1
  175. package/src/agents/dreamer.ts +68 -0
  176. package/src/agents/hidden-agent-registrations.ts +331 -0
  177. package/src/agents/historian.ts +3 -0
  178. package/src/agents/language-directive.test.ts +138 -0
  179. package/src/agents/language-directive.ts +121 -0
  180. package/src/agents/magic-context-prompt.language.test.ts +35 -0
  181. package/src/agents/magic-context-prompt.test.ts +292 -0
  182. package/src/agents/magic-context-prompt.ts +182 -0
  183. package/src/agents/permissions.test.ts +252 -0
  184. package/src/agents/permissions.ts +186 -0
  185. package/src/agents/sidekick.ts +1 -0
  186. package/src/agents/smart-note-compiler.ts +1 -0
  187. package/src/config/agent-disable.ts +106 -0
  188. package/src/config/compaction-accessor-guard.test.ts +82 -0
  189. package/src/config/compaction-backcompat.test.ts +52 -0
  190. package/src/config/index.test.ts +936 -0
  191. package/src/config/index.ts +601 -0
  192. package/src/config/migrate-config-location.test.ts +332 -0
  193. package/src/config/migrate-config-location.ts +635 -0
  194. package/src/config/migrate-dreamer-v2.test.ts +287 -0
  195. package/src/config/migrate-dreamer-v2.ts +336 -0
  196. package/src/config/migrate-experimental.test.ts +41 -0
  197. package/src/config/migrate-experimental.ts +185 -0
  198. package/src/config/project-security.test.ts +388 -0
  199. package/src/config/project-security.ts +611 -0
  200. package/src/config/prune-config-leaf.test.ts +78 -0
  201. package/src/config/prune-config-leaf.ts +68 -0
  202. package/src/config/removed-config-keys.test.ts +116 -0
  203. package/src/config/schema/agent-overrides.ts +45 -0
  204. package/src/config/schema/magic-context.test.ts +359 -0
  205. package/src/config/schema/magic-context.ts +991 -0
  206. package/src/config/transform-mode.test.ts +62 -0
  207. package/src/config/transform-mode.ts +34 -0
  208. package/src/config/variable.test.ts +323 -0
  209. package/src/config/variable.ts +192 -0
  210. package/src/features/magic-context/defaults.ts +1 -0
  211. package/src/features/magic-context/dreamer/cron.ts +243 -0
  212. package/src/features/magic-context/dreamer/task-registry.ts +151 -0
  213. package/src/shared/conflict-detector.test.ts +121 -0
  214. package/src/shared/conflict-detector.ts +50 -3
  215. package/src/shared/conflict-fixer.test.ts +309 -0
  216. package/src/shared/conflict-fixer.ts +82 -5
  217. package/src/shared/context-limit-provenance.ts +6 -0
  218. package/src/shared/escalation-bands.test.ts +23 -0
  219. package/src/shared/escalation-bands.ts +18 -0
  220. package/src/shared/format-threshold.ts +1 -1
  221. package/src/shared/models-dev-cache.test.ts +171 -0
  222. package/src/shared/models-dev-cache.ts +195 -38
  223. package/src/shared/rpc-server.ts +27 -18
  224. package/src/shared/rpc-types.ts +55 -1
  225. package/src/shared/rpc-utils.test.ts +157 -0
  226. package/src/shared/rpc-utils.ts +137 -0
  227. package/src/shared/storage-permissions.ts +20 -0
  228. package/src/shared/tui-runtime-specifiers.ts +32 -0
  229. package/src/tui/compaction-off.test.ts +56 -0
  230. package/src/tui/compaction-off.ts +29 -0
  231. package/src/tui/data/context-db.ts +6 -0
  232. package/src/tui/index.tsx +113 -65
  233. package/src/tui/slots/sidebar-content.tsx +189 -91
  234. package/src/tui/tui-compiled-runtime-imports.test.ts +151 -0
  235. package/src/tui-compiled/compaction-off.ts +29 -0
  236. package/src/tui-compiled/data/context-db.ts +6 -0
  237. package/src/tui-compiled/index.tsx +558 -455
  238. package/src/tui-compiled/slots/sidebar-content.tsx +334 -169
  239. package/dist/send-session-notification-tghq9gnh.js +0 -11
  240. /package/dist/{index-v7fmc1s2.js → index-ygcng9t8.js} +0 -0
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
@@ -1,10 +1,11 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
- import { Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
2
+ import { For, Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
3
3
  import type { TuiSlotPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
4
4
  import packageJson from "../../../package.json"
5
5
  import { badgeTextColor } from '../badge-contrast';
6
6
  import { loadSidebarSnapshot, type SidebarSnapshot } from "../data/context-db"
7
7
  import { formatThresholdPercent } from "../../shared/format-threshold"
8
+ import { compactionOffSidebarRows, nativeCompactionContextLabel } from "../compaction-off"
8
9
  import {
9
10
  computeEffectiveOrder,
10
11
  DEFAULT_SLOT_ORDER,
@@ -22,10 +23,16 @@ import {
22
23
  // parent-session message event (the RPC upgrade/recomp call fires none). The
23
24
  // mounted SidebarContent registers its refresh here.
24
25
  let activeRecompPollKick: (() => void) | null = null
26
+ let activeSidebarRefresh: (() => void) | null = null
25
27
  export function kickRecompProgressRefresh(): void {
26
28
  activeRecompPollKick?.()
27
29
  }
28
30
 
31
+ /** Ask the mounted sidebar to fetch an out-of-band status update now. */
32
+ export function refreshSidebarSnapshot(): void {
33
+ activeSidebarRefresh?.()
34
+ }
35
+
29
36
  const SINGLE_BORDER = { type: "single" } as any
30
37
  const REFRESH_DEBOUNCE_MS = 150
31
38
 
@@ -663,11 +670,13 @@ const SidebarContent = (props: {
663
670
  }
664
671
 
665
672
  activeRecompPollKick = kickRecompPoll
673
+ activeSidebarRefresh = refresh
666
674
 
667
675
  onCleanup(() => {
668
676
  if (refreshTimer) clearTimeout(refreshTimer)
669
677
  stopRecompPoll()
670
678
  if (activeRecompPollKick === kickRecompPoll) activeRecompPollKick = null
679
+ if (activeSidebarRefresh === refresh) activeSidebarRefresh = null
671
680
  })
672
681
 
673
682
  // Refresh on session change
@@ -708,7 +717,9 @@ const SidebarContent = (props: {
708
717
  )
709
718
 
710
719
  const s = createMemo(() => snapshot())
720
+ const compactionOff = () => s()?.compaction_enabled === false
711
721
  const contextSummaryColor = createMemo(() => {
722
+ if (compactionOff()) return props.theme.accent
712
723
  const usage = s()?.usagePercentage ?? 0
713
724
  if (usage >= 80) return props.theme.error
714
725
  if (usage >= 65) return props.theme.warning
@@ -743,6 +754,23 @@ const SidebarContent = (props: {
743
754
  <text fg={props.theme.textMuted}>v{packageJson.version}</text>
744
755
  </box>
745
756
 
757
+ {/* The fence probe writes the failure into the server-owned snapshot,
758
+ so this survives sidebar refreshes and is visible independently of
759
+ the one-shot toast. */}
760
+ {s()?.lastTransformError && (
761
+ <box marginTop={1} width="100%">
762
+ <text fg={props.theme.error}>⚠ {s()!.lastTransformError}</text>
763
+ </box>
764
+ )}
765
+
766
+ {s()?.dreamerProgress && (
767
+ <box marginTop={1} width="100%">
768
+ <text fg={props.theme.warning}>
769
+ Dreamer {s()!.dreamerProgress!.task}: {s()!.dreamerProgress!.processed}/{s()!.dreamerProgress!.total} processed
770
+ </text>
771
+ </box>
772
+ )}
773
+
746
774
  {/* Token breakdown bar. In collapsed mode the header, bar and the
747
775
  3 summary rows stack with no vertical padding for a compact look;
748
776
  expanded mode keeps the 1-row gap above the bar. */}
@@ -750,16 +778,15 @@ const SidebarContent = (props: {
750
778
  <box marginTop={collapsed() ? 0 : 1} flexDirection="column">
751
779
  {(s()?.contextLimit ?? 0) > 0 && (
752
780
  <box width="100%" flexDirection="row" justifyContent="space-between">
753
- {/* Left: current usage vs the per-model execute
754
- threshold (the value Magic Context compares
755
- against when scheduling historian / drops).
756
- "47.5% / 65%" tells the user how close they
757
- are to the next compaction trigger. A trailing
758
- "*" marks a threshold that was clamped down from
759
- a higher configured value (issue #241). */}
760
- <text fg={contextSummaryColor()}>
761
- <b>{s()!.usagePercentage.toFixed(1)}%</b> / {formatThresholdPercent(s()!.executeThreshold)}%{s()!.executeThresholdClamped ? "*" : ""}
762
- </text>
781
+ {compactionOff() ? (
782
+ <text fg={contextSummaryColor()}>
783
+ <b>{nativeCompactionContextLabel(s()!)}</b>
784
+ </text>
785
+ ) : (
786
+ <text fg={contextSummaryColor()}>
787
+ <b>{s()!.usagePercentage.toFixed(1)}%</b> / {formatThresholdPercent(s()!.executeThreshold)}%{s()!.executeThresholdClamped ? "*" : ""}
788
+ </text>
789
+ )}
763
790
  {/* Right: absolute token usage vs the model's
764
791
  full context window (separate from the
765
792
  execute threshold so users still know how
@@ -778,36 +805,57 @@ const SidebarContent = (props: {
778
805
  Status (Q=queued ops, N=session notes). */}
779
806
  {collapsed() && (
780
807
  <box width="100%" flexDirection="column">
781
- {/* Collapsed rows are intentionally uniform faded-grey, not
782
- bold/accent — they're a glanceable summary, so the label
783
- and value share the muted tone (matches Memories row). */}
784
- <box width="100%" flexDirection="row" justifyContent="space-between">
785
- <text fg={props.theme.textMuted}>Historian</text>
786
- {s()?.historianRunning ? (
787
- <text fg={props.theme.warning}>comparting ⟳</text>
788
- ) : (
789
- <text fg={props.theme.textMuted}>idle</text>
790
- )}
791
- </box>
792
- <box width="100%" flexDirection="row" justifyContent="space-between">
793
- <text fg={props.theme.textMuted}>Memories</text>
794
- <text fg={props.theme.textMuted}>
795
- {(s()?.memoryBlockCount ?? 0) > 0
796
- ? `${s()!.memoryBlockCount}/${s()?.memoryCount ?? 0}`
797
- : String(s()?.memoryCount ?? 0)}
798
- </text>
799
- </box>
800
- <box width="100%" flexDirection="row" justifyContent="space-between">
801
- <text fg={props.theme.textMuted}>Status</text>
802
- <text fg={props.theme.textMuted}>
803
- C:{s()?.compartmentCount ?? 0} Q:{s()?.pendingOpsCount ?? 0} N:{s()?.sessionNoteCount ?? 0}
804
- </text>
805
- </box>
806
- <Show when={s()?.recompProgress}>
807
- {(progress) => (
808
- <RecompProgressSection theme={props.theme} progress={progress()} />
809
- )}
810
- </Show>
808
+ {compactionOff() ? (
809
+ compactionOffSidebarRows(s()!).map((row) => (
810
+ <StatRow
811
+ theme={props.theme}
812
+ label={row.label}
813
+ value={row.value}
814
+ accent={row.label === "Memories"}
815
+ dim={row.label !== "Memories"}
816
+ />
817
+ ))
818
+ ) : (
819
+ <>
820
+ <box width="100%" flexDirection="row" justifyContent="space-between">
821
+ <text fg={props.theme.textMuted}>Historian</text>
822
+ {s()?.historianRunning ? (
823
+ <text fg={props.theme.warning}>comparting ⟳</text>
824
+ ) : (
825
+ <text fg={props.theme.textMuted}>idle</text>
826
+ )}
827
+ </box>
828
+ <Show when={s()?.dreamerProgress}>
829
+ {(progress) => (
830
+ <box width="100%" flexDirection="row" justifyContent="space-between">
831
+ <text fg={props.theme.textMuted}>Dreamer</text>
832
+ <text fg={props.theme.warning}>
833
+ {progress().task} {progress().processed}/{progress().total}
834
+ </text>
835
+ </box>
836
+ )}
837
+ </Show>
838
+ <box width="100%" flexDirection="row" justifyContent="space-between">
839
+ <text fg={props.theme.textMuted}>Memories</text>
840
+ <text fg={props.theme.textMuted}>
841
+ {(s()?.memoryBlockCount ?? 0) > 0
842
+ ? `${s()!.memoryBlockCount}/${s()?.memoryCount ?? 0}`
843
+ : String(s()?.memoryCount ?? 0)}
844
+ </text>
845
+ </box>
846
+ <box width="100%" flexDirection="row" justifyContent="space-between">
847
+ <text fg={props.theme.textMuted}>Status</text>
848
+ <text fg={props.theme.textMuted}>
849
+ C:{s()?.compartmentCount ?? 0} Q:{s()?.pendingOpsCount ?? 0} N:{s()?.sessionNoteCount ?? 0}
850
+ </text>
851
+ </box>
852
+ <Show when={s()?.recompProgress}>
853
+ {(progress) => (
854
+ <RecompProgressSection theme={props.theme} progress={progress()} />
855
+ )}
856
+ </Show>
857
+ </>
858
+ )}
811
859
  </box>
812
860
  )}
813
861
 
@@ -815,7 +863,7 @@ const SidebarContent = (props: {
815
863
  {!collapsed() && (
816
864
  <>
817
865
  {/* Historian section */}
818
- {sections().historian && (
866
+ {!compactionOff() && sections().historian && (
819
867
  <>
820
868
  <box width="100%" marginTop={1} flexDirection="row" justifyContent="space-between">
821
869
  <text fg={props.theme.text}>
@@ -846,66 +894,116 @@ const SidebarContent = (props: {
846
894
  {sections().memory && (
847
895
  <>
848
896
  <SectionHeader theme={props.theme} title="Memory" />
849
- <StatRow
850
- theme={props.theme}
851
- label="Memories"
852
- value={String(s()?.memoryCount ?? 0)}
853
- accent
854
- />
855
- {(s()?.memoryBlockCount ?? 0) > 0 && (
856
- <StatRow
857
- theme={props.theme}
858
- label="Injected"
859
- value={String(s()!.memoryBlockCount)}
860
- dim
861
- />
862
- )}
863
- </>
864
- )}
865
-
866
- {/* Queue & Status */}
867
- {sections().status &&
868
- ((s()?.pendingOpsCount ?? 0) > 0 ||
869
- (s()?.sessionNoteCount ?? 0) > 0 ||
870
- (s()?.readySmartNoteCount ?? 0) > 0) && (
897
+ {compactionOff() ? (
898
+ compactionOffSidebarRows(s()!)
899
+ .filter((row) => row.label === "Memories")
900
+ .map((row) => (
901
+ <StatRow theme={props.theme} label={row.label} value={row.value} accent />
902
+ ))
903
+ ) : (
871
904
  <>
872
- <SectionHeader theme={props.theme} title="Status" />
873
- {(s()?.pendingOpsCount ?? 0) > 0 && (
874
- <StatRow
875
- theme={props.theme}
876
- label="Queue"
877
- value={`${s()!.pendingOpsCount} pending`}
878
- warning
879
- />
880
- )}
881
- {(s()?.sessionNoteCount ?? 0) > 0 && (
882
- <StatRow
883
- theme={props.theme}
884
- label="Notes"
885
- value={String(s()!.sessionNoteCount)}
886
- />
887
- )}
888
- {(s()?.readySmartNoteCount ?? 0) > 0 && (
905
+ <StatRow
906
+ theme={props.theme}
907
+ label="Memories"
908
+ value={String(s()?.memoryCount ?? 0)}
909
+ accent
910
+ />
911
+ {(s()?.memoryBlockCount ?? 0) > 0 && (
889
912
  <StatRow
890
913
  theme={props.theme}
891
- label="Smart Notes"
892
- value={`${s()!.readySmartNoteCount} ready`}
893
- accent
914
+ label="Injected"
915
+ value={String(s()!.memoryBlockCount)}
916
+ dim
894
917
  />
895
918
  )}
896
919
  </>
897
920
  )}
921
+ </>
922
+ )}
923
+
924
+ {/* Queue & Status */}
925
+ {sections().status &&
926
+ (compactionOff() ||
927
+ (s()?.pendingOpsCount ?? 0) > 0 ||
928
+ (s()?.sessionNoteCount ?? 0) > 0 ||
929
+ (s()?.readySmartNoteCount ?? 0) > 0) && (
930
+ <>
931
+ <SectionHeader theme={props.theme} title="Status" />
932
+ {compactionOff() ? (
933
+ compactionOffSidebarRows(s()!)
934
+ .filter((row) => row.label !== "Memories")
935
+ .map((row) => (
936
+ <StatRow
937
+ theme={props.theme}
938
+ label={row.label}
939
+ value={row.value}
940
+ dim
941
+ />
942
+ ))
943
+ ) : (
944
+ <>
945
+ {(s()?.pendingOpsCount ?? 0) > 0 && (
946
+ <StatRow
947
+ theme={props.theme}
948
+ label="Queue"
949
+ value={`${s()!.pendingOpsCount} pending`}
950
+ warning
951
+ />
952
+ )}
953
+ {(s()?.sessionNoteCount ?? 0) > 0 && (
954
+ <StatRow
955
+ theme={props.theme}
956
+ label="Notes"
957
+ value={String(s()!.sessionNoteCount)}
958
+ />
959
+ )}
960
+ {(s()?.readySmartNoteCount ?? 0) > 0 && (
961
+ <StatRow
962
+ theme={props.theme}
963
+ label="Smart Notes"
964
+ value={`${s()!.readySmartNoteCount} ready`}
965
+ accent
966
+ />
967
+ )}
968
+ </>
969
+ )}
970
+ </>
971
+ )}
898
972
 
899
973
  {/* Dreamer */}
900
- {sections().dreamer && s()?.lastDreamerRunAt && (
974
+ {sections().dreamer && (s()?.lastDreamerRunAt || s()?.dreamerProgress) && (
901
975
  <>
902
976
  <SectionHeader theme={props.theme} title="Dreamer" />
903
- <StatRow
904
- theme={props.theme}
905
- label="Last run"
906
- value={relativeTime(s()!.lastDreamerRunAt!)}
907
- dim
908
- />
977
+ <Show when={s()?.dreamerProgress}>
978
+ {(progress) => (
979
+ <StatRow
980
+ theme={props.theme}
981
+ label="Current"
982
+ value={`${progress().task} ${progress().processed}/${progress().total}`}
983
+ warning
984
+ />
985
+ )}
986
+ </Show>
987
+ <Show when={s()?.lastDreamerRunAt}>
988
+ {(lastRunAt) => (
989
+ <StatRow
990
+ theme={props.theme}
991
+ label="Last run"
992
+ value={relativeTime(lastRunAt())}
993
+ dim
994
+ />
995
+ )}
996
+ </Show>
997
+ <For each={Object.entries(s()?.dreamerBacklog ?? {})}>
998
+ {([task, backlog]) => (
999
+ <StatRow
1000
+ theme={props.theme}
1001
+ label={task}
1002
+ value={`${backlog.pending}/${backlog.total}`}
1003
+ dim
1004
+ />
1005
+ )}
1006
+ </For>
909
1007
  </>
910
1008
  )}
911
1009