@cortexkit/opencode-magic-context 0.21.7 → 0.22.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.
- package/README.md +116 -325
- package/dist/agents/magic-context-prompt.d.ts.map +1 -1
- package/dist/agents/permissions.d.ts +29 -14
- package/dist/agents/permissions.d.ts.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/migrate-experimental.d.ts +29 -0
- package/dist/config/migrate-experimental.d.ts.map +1 -0
- package/dist/config/schema/magic-context.d.ts +80 -104
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/builtin-commands/commands.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-embedding.d.ts +34 -0
- package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-events.d.ts +50 -0
- package/dist/features/magic-context/compartment-events.d.ts.map +1 -0
- package/dist/features/magic-context/compartment-storage.d.ts +22 -0
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/queue.d.ts +13 -2
- package/dist/features/magic-context/dreamer/queue.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/runner.d.ts +11 -0
- package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-prompts.d.ts +1 -1
- package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/identify-key-files.d.ts +1 -1
- package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/project-key-files.d.ts.map +1 -1
- package/dist/features/magic-context/key-files/read-stats.d.ts +1 -1
- package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -1
- package/dist/features/magic-context/memory/constants.d.ts +4 -0
- package/dist/features/magic-context/memory/constants.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/index.d.ts +1 -1
- package/dist/features/magic-context/memory/index.d.ts.map +1 -1
- package/dist/features/magic-context/memory/memory-migration.d.ts +133 -0
- package/dist/features/magic-context/memory/memory-migration.d.ts.map +1 -0
- package/dist/features/magic-context/memory/project-identity.d.ts +38 -7
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +15 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/types.d.ts +3 -1
- package/dist/features/magic-context/memory/types.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +7 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/project-docs-hash.d.ts +6 -0
- package/dist/features/magic-context/project-docs-hash.d.ts.map +1 -0
- package/dist/features/magic-context/project-identity.d.ts +2 -0
- package/dist/features/magic-context/project-identity.d.ts.map +1 -0
- package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +51 -7
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts +73 -0
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -0
- package/dist/features/magic-context/storage-identity-rekey-map.d.ts +11 -0
- package/dist/features/magic-context/storage-identity-rekey-map.d.ts.map +1 -0
- package/dist/features/magic-context/storage-m0-mutation-log.d.ts +22 -0
- package/dist/features/magic-context/storage-m0-mutation-log.d.ts.map +1 -0
- package/dist/features/magic-context/storage-memory-mutation-log.d.ts +25 -0
- package/dist/features/magic-context/storage-memory-mutation-log.d.ts.map +1 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts +5 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts +44 -0
- package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +2 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage-project-state.d.ts +19 -0
- package/dist/features/magic-context/storage-project-state.d.ts.map +1 -0
- package/dist/features/magic-context/storage-subagent-invocations.d.ts +61 -0
- package/dist/features/magic-context/storage-subagent-invocations.d.ts.map +1 -0
- package/dist/features/magic-context/storage-tags.d.ts +21 -1
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage-v22-backfill-failures.d.ts +24 -0
- package/dist/features/magic-context/storage-v22-backfill-failures.d.ts.map +1 -0
- package/dist/features/magic-context/storage.d.ts +13 -3
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/subagent-token-capture.d.ts +33 -0
- package/dist/features/magic-context/subagent-token-capture.d.ts.map +1 -0
- package/dist/features/magic-context/tagger.d.ts +15 -1
- package/dist/features/magic-context/tagger.d.ts.map +1 -1
- package/dist/features/magic-context/types.d.ts +21 -0
- package/dist/features/magic-context/types.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
- package/dist/features/magic-context/v22-deferred-backfill.d.ts +46 -0
- package/dist/features/magic-context/v22-deferred-backfill.d.ts.map +1 -0
- package/dist/features/magic-context/work-metrics.d.ts +79 -0
- package/dist/features/magic-context/work-metrics.d.ts.map +1 -0
- package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +9 -0
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +13 -1
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-parser.d.ts +25 -0
- package/dist/hooks/magic-context/compartment-parser.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts +27 -16
- package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts +2 -0
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-mapping.d.ts +6 -2
- package/dist/hooks/magic-context/compartment-runner-mapping.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts +9 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +68 -4
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/decay-curve.d.ts +78 -0
- package/dist/hooks/magic-context/decay-curve.d.ts.map +1 -0
- package/dist/hooks/magic-context/decay-render.d.ts +67 -0
- package/dist/hooks/magic-context/decay-render.d.ts.map +1 -0
- package/dist/hooks/magic-context/event-handler.d.ts +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +17 -0
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts +2 -0
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts +3 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +9 -21
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +126 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/key-files-block.d.ts.map +1 -1
- package/dist/hooks/magic-context/live-session-state.d.ts +9 -0
- package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts +35 -0
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts +9 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +7 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts +104 -0
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -0
- package/dist/hooks/magic-context/reference-retrieval.d.ts +61 -0
- package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -0
- package/dist/hooks/magic-context/reference-seeds.generated.d.ts +8 -0
- package/dist/hooks/magic-context/reference-seeds.generated.d.ts.map +1 -0
- package/dist/hooks/magic-context/send-session-notification.d.ts +1 -1
- package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +5 -6
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tokenizer-calibration.d.ts +6 -0
- package/dist/hooks/magic-context/tokenizer-calibration.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +0 -7
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +18 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +9 -7
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/upgrade-reminder.d.ts +73 -0
- package/dist/hooks/magic-context/upgrade-reminder.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22111 -16352
- package/dist/plugin/conflict-warning-hook.d.ts +13 -0
- package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/shared/announcement.d.ts +1 -1
- package/dist/shared/announcement.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +1 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +27 -5
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +32 -2
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +9 -0
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/shared/sqlite-helpers.d.ts +7 -7
- package/dist/shared/sqlite.d.ts +23 -14
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/subagent-runner.d.ts +5 -0
- package/dist/shared/subagent-runner.d.ts.map +1 -1
- package/dist/shared/tag-transcript.d.ts +10 -1
- package/dist/shared/tag-transcript.d.ts.map +1 -1
- package/dist/tools/ctx-expand/tools.d.ts +5 -1
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +16 -1
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/package.json +2 -4
- package/src/shared/announcement.ts +6 -7
- package/src/shared/rpc-client.test.ts +49 -2
- package/src/shared/rpc-client.ts +19 -9
- package/src/shared/rpc-notifications.test.ts +54 -1
- package/src/shared/rpc-notifications.ts +82 -13
- package/src/shared/rpc-server.ts +33 -4
- package/src/shared/rpc-types.ts +32 -2
- package/src/shared/rpc-utils.ts +10 -0
- package/src/shared/sqlite-helpers.ts +9 -9
- package/src/shared/sqlite.ts +99 -80
- package/src/shared/subagent-runner.ts +14 -0
- package/src/shared/tag-transcript.test.ts +280 -0
- package/src/shared/tag-transcript.ts +162 -33
- package/src/tui/data/context-db.ts +77 -11
- package/src/tui/index.tsx +240 -57
- package/src/tui/slots/sidebar-content.tsx +415 -101
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +0 -87
- package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +0 -1
- package/dist/shared/native-binding.d.ts +0 -87
- package/dist/shared/native-binding.d.ts.map +0 -1
- package/src/shared/native-binding.ts +0 -311
package/src/tui/index.tsx
CHANGED
|
@@ -4,9 +4,9 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs"
|
|
|
4
4
|
import { dirname, join } from "node:path"
|
|
5
5
|
import { createMemo } from "solid-js"
|
|
6
6
|
import type { TuiPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
|
|
7
|
-
import { createSidebarContentSlot } from "./slots/sidebar-content"
|
|
7
|
+
import { createSidebarContentSlot, kickRecompProgressRefresh } from "./slots/sidebar-content"
|
|
8
8
|
import packageJson from "../../package.json"
|
|
9
|
-
import { closeRpc, consumeTuiMessages, getAnnouncement, getCompartmentCount, initRpcClient, loadStatusDetail, markAnnounced, requestRecomp, type StatusDetail } from "./data/context-db"
|
|
9
|
+
import { closeRpc, consumeTuiMessages, dismissUpgradeReminder, getAnnouncement, getCompartmentCount, getRpcGeneration, initRpcClient, loadStatusDetail, markAnnounced, markTuiMessagesHandled, requestRecomp, requestUpgrade, type TuiMessage, type StatusDetail } from "./data/context-db"
|
|
10
10
|
import { formatThresholdPercent } from "../shared/format-threshold"
|
|
11
11
|
import { detectConflicts } from "../shared/conflict-detector"
|
|
12
12
|
import { fixConflicts } from "../shared/conflict-fixer"
|
|
@@ -213,9 +213,11 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
|
|
|
213
213
|
const COLORS = {
|
|
214
214
|
// Cool / structured — injected by the plugin into message[0]
|
|
215
215
|
system: "#c084fc",
|
|
216
|
+
docs: "#22d3ee",
|
|
216
217
|
compartments: "#60a5fa",
|
|
217
218
|
facts: "#fbbf24",
|
|
218
219
|
memories: "#34d399",
|
|
220
|
+
profile: "#a3e635",
|
|
219
221
|
// Warm / user-facing — chat and tool traffic
|
|
220
222
|
conversation: "#f87171",
|
|
221
223
|
toolCalls: "#fb923c",
|
|
@@ -229,6 +231,8 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
|
|
|
229
231
|
|
|
230
232
|
if (d.systemPromptTokens > 0)
|
|
231
233
|
segs.push({ label: "System", tokens: d.systemPromptTokens, color: COLORS.system })
|
|
234
|
+
if (d.docsTokens > 0)
|
|
235
|
+
segs.push({ label: "Docs", tokens: d.docsTokens, color: COLORS.docs })
|
|
232
236
|
if (d.compartmentTokens > 0)
|
|
233
237
|
segs.push({
|
|
234
238
|
label: "Compartments",
|
|
@@ -250,6 +254,8 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
|
|
|
250
254
|
color: COLORS.memories,
|
|
251
255
|
detail: `(${d.memoryBlockCount})`,
|
|
252
256
|
})
|
|
257
|
+
if (d.profileTokens > 0)
|
|
258
|
+
segs.push({ label: "User Profile", tokens: d.profileTokens, color: COLORS.profile })
|
|
253
259
|
|
|
254
260
|
if (d.conversationTokens > 0)
|
|
255
261
|
segs.push({ label: "Conversation", tokens: d.conversationTokens, color: COLORS.conversation })
|
|
@@ -261,27 +267,12 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
|
|
|
261
267
|
return { segs, total }
|
|
262
268
|
}
|
|
263
269
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
let sum = widths.reduce((a, b) => a + b, 0)
|
|
271
|
-
while (sum > barWidth) {
|
|
272
|
-
const maxIdx = widths.indexOf(Math.max(...widths))
|
|
273
|
-
if (widths[maxIdx] > 1) { widths[maxIdx]--; sum-- } else break
|
|
274
|
-
}
|
|
275
|
-
while (sum < barWidth) {
|
|
276
|
-
const maxIdx = widths.indexOf(Math.max(...widths))
|
|
277
|
-
widths[maxIdx]++; sum++
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return segs.map((seg, i) => ({
|
|
281
|
-
chars: "█".repeat(widths[i] || 0),
|
|
282
|
-
color: seg.color,
|
|
283
|
-
}))
|
|
284
|
-
}
|
|
270
|
+
// The status-dialog breakdown bar uses flex layout (same approach as the
|
|
271
|
+
// sidebar breakdown). Each segment becomes a colored box with
|
|
272
|
+
// flexGrow=tokens and flexBasis=0, parent has width="100%", so opentui
|
|
273
|
+
// distributes the dialog's full width proportionally regardless of the
|
|
274
|
+
// dialog's actual rendered width.
|
|
275
|
+
const barSegments = () => breakdownSegments().segs.filter((seg) => seg.tokens > 0)
|
|
285
276
|
|
|
286
277
|
return (
|
|
287
278
|
<box flexDirection="column" width="100%" paddingLeft={2} paddingRight={2} paddingTop={1} paddingBottom={1}>
|
|
@@ -304,10 +295,17 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
|
|
|
304
295
|
</text>
|
|
305
296
|
</box>
|
|
306
297
|
|
|
307
|
-
{/* Segmented breakdown bar
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
298
|
+
{/* Segmented breakdown bar: flex row of colored boxes filling
|
|
299
|
+
the dialog width. See barSegments comment above. */}
|
|
300
|
+
<box width="100%" flexDirection="row" height={1}>
|
|
301
|
+
{barSegments().map((seg) => (
|
|
302
|
+
<box
|
|
303
|
+
key={seg.label}
|
|
304
|
+
flexGrow={Math.max(1, seg.tokens)}
|
|
305
|
+
flexBasis={0}
|
|
306
|
+
height={1}
|
|
307
|
+
backgroundColor={seg.color}
|
|
308
|
+
/>
|
|
311
309
|
))}
|
|
312
310
|
</box>
|
|
313
311
|
|
|
@@ -324,6 +322,35 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
|
|
|
324
322
|
})}
|
|
325
323
|
</box>
|
|
326
324
|
|
|
325
|
+
{/* Recomp / session-upgrade live progress (full width, only while
|
|
326
|
+
running or just finished — dogfood 2026-05-30). */}
|
|
327
|
+
{s().recompProgress && (
|
|
328
|
+
<box marginTop={1} width="100%" flexDirection="column">
|
|
329
|
+
<text fg={t().text}><b>Recomp / Upgrade</b></text>
|
|
330
|
+
{(() => {
|
|
331
|
+
const p = s().recompProgress!
|
|
332
|
+
if (p.phase === "recomp") {
|
|
333
|
+
const frac = p.totalMessages > 0 ? p.processedMessages / p.totalMessages : 0
|
|
334
|
+
const width = 24
|
|
335
|
+
const filled = Math.round(Math.max(0, Math.min(1, frac)) * width)
|
|
336
|
+
const bar = p.totalMessages > 0
|
|
337
|
+
? `[${"█".repeat(filled)}${"░".repeat(width - filled)}]`
|
|
338
|
+
: "(starting…)"
|
|
339
|
+
return (
|
|
340
|
+
<>
|
|
341
|
+
<R t={t()} l="upgrading" v={p.totalMessages > 0 ? `${bar} ${Math.round(frac * 100)}%` : bar} fg={t().warning} />
|
|
342
|
+
{p.note ? <R t={t()} l="Status" v={p.note} fg={t().textMuted} /> : null}
|
|
343
|
+
<R t={t()} l="Compartments" v={`${p.compartmentsCreated} (${p.passCount} pass${p.passCount === 1 ? "" : "es"})`} fg={t().textMuted} />
|
|
344
|
+
</>
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
if (p.phase === "migration") return <R t={t()} l="Status" v={p.note ?? "Migrating memories ⟳"} fg={t().warning} />
|
|
348
|
+
if (p.phase === "done") return <R t={t()} l="Status" v="✓ Upgrade complete" fg={t().accent} />
|
|
349
|
+
return <R t={t()} l="Status" v={`✗ Failed${p.message ? `: ${p.message}` : ""}`} fg={t().error} />
|
|
350
|
+
})()}
|
|
351
|
+
</box>
|
|
352
|
+
)}
|
|
353
|
+
|
|
327
354
|
{/* 2-column layout */}
|
|
328
355
|
<box flexDirection="row" width="100%" marginTop={1} gap={4}>
|
|
329
356
|
{/* Left column */}
|
|
@@ -414,49 +441,139 @@ function getModelKeyFromMessages(api: TuiPluginApi, sessionId: string): string |
|
|
|
414
441
|
return undefined
|
|
415
442
|
}
|
|
416
443
|
|
|
417
|
-
function showRecompDialog(api: TuiPluginApi) {
|
|
418
|
-
const sessionId =
|
|
444
|
+
async function showRecompDialog(api: TuiPluginApi, targetSessionId = getSessionId(api)): Promise<boolean> {
|
|
445
|
+
const sessionId = targetSessionId
|
|
419
446
|
if (!sessionId) {
|
|
420
447
|
api.ui.toast({ message: "No active session", variant: "warning" })
|
|
421
|
-
return
|
|
448
|
+
return false
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const count = await getCompartmentCount(sessionId)
|
|
452
|
+
// Ack only after the dialog is actually shown for the same active session;
|
|
453
|
+
// route switches while the RPC detail load is in flight must leave it pending.
|
|
454
|
+
if (getSessionId(api) !== sessionId) return false
|
|
455
|
+
|
|
456
|
+
api.ui.dialog.replace(() => (
|
|
457
|
+
<api.ui.DialogConfirm
|
|
458
|
+
title="⚠️ Recomp Confirmation"
|
|
459
|
+
message={[
|
|
460
|
+
`You have ${count} compartments.`,
|
|
461
|
+
"",
|
|
462
|
+
"Recomp will regenerate all compartments and facts from raw history.",
|
|
463
|
+
"This may take a long time and consume significant tokens.",
|
|
464
|
+
"",
|
|
465
|
+
"Proceed?",
|
|
466
|
+
].join("\n")}
|
|
467
|
+
onConfirm={() => {
|
|
468
|
+
void requestRecomp(sessionId)
|
|
469
|
+
kickRecompProgressRefresh()
|
|
470
|
+
api.ui.toast({ message: "Recomp requested — historian will start shortly", variant: "info", duration: 5000 })
|
|
471
|
+
}}
|
|
472
|
+
onCancel={() => {
|
|
473
|
+
api.ui.toast({ message: "Recomp cancelled", variant: "info", duration: 3000 })
|
|
474
|
+
}}
|
|
475
|
+
/>
|
|
476
|
+
))
|
|
477
|
+
return true
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function showUpgradeDialog(
|
|
481
|
+
api: TuiPluginApi,
|
|
482
|
+
resume?: { stagedCount: number; stagedThrough: number },
|
|
483
|
+
targetSessionId = getSessionId(api),
|
|
484
|
+
): boolean {
|
|
485
|
+
const sessionId = targetSessionId
|
|
486
|
+
if (!sessionId) {
|
|
487
|
+
// No active session — nothing to upgrade. Silently skip (the server only
|
|
488
|
+
// enqueues this for sessions with legacy compartments, but the TUI may
|
|
489
|
+
// have switched sessions before the poller fired).
|
|
490
|
+
return false
|
|
422
491
|
}
|
|
423
492
|
|
|
424
|
-
|
|
425
|
-
|
|
493
|
+
if (getSessionId(api) !== sessionId) return false
|
|
494
|
+
|
|
495
|
+
const title = resume ? "🎆 Resume the interrupted upgrade?" : "🎆 Historian V2 is released!"
|
|
496
|
+
const message = resume
|
|
497
|
+
? [
|
|
498
|
+
`An earlier upgrade to the new historian format was interrupted. ${resume.stagedCount} compartment${resume.stagedCount === 1 ? " was" : "s were"} already rebuilt (through message ${resume.stagedThrough}). Resuming continues from where it left off — nothing already rebuilt is reprocessed.`,
|
|
499
|
+
"",
|
|
500
|
+
"Resuming will:",
|
|
501
|
+
"• Rebuild the remaining compartments into the new layered format",
|
|
502
|
+
"• Re-organize this project's memories into the new taxonomy (once per project)",
|
|
503
|
+
"",
|
|
504
|
+
"The historian runs in the background and you can keep working. You can also resume via /ctx-session-upgrade later.",
|
|
505
|
+
"",
|
|
506
|
+
"Resume the upgrade now?",
|
|
507
|
+
].join("\n")
|
|
508
|
+
: [
|
|
509
|
+
"This session's compartments are written by the old historian. The session is still usable with its old compartments, however it's strongly advised to upgrade them to the new format. This means every compartment needs to be reprocessed by the new historian, which might take a while depending on how big your session is.",
|
|
510
|
+
"",
|
|
511
|
+
"Running the upgrade will:",
|
|
512
|
+
"• Rebuild this session's compartments into the new layered format",
|
|
513
|
+
"• Re-organize this project's memories into the new taxonomy (once per project)",
|
|
514
|
+
"",
|
|
515
|
+
"The historian runs in the background and you can keep working while older compartments are reprocessed. You can also upgrade via /ctx-session-upgrade later.",
|
|
516
|
+
"",
|
|
517
|
+
"Run the upgrade now?",
|
|
518
|
+
].join("\n")
|
|
519
|
+
|
|
520
|
+
api.ui.dialog.replace(
|
|
521
|
+
() => (
|
|
426
522
|
<api.ui.DialogConfirm
|
|
427
|
-
title=
|
|
428
|
-
message={
|
|
429
|
-
`You have ${count} compartments.`,
|
|
430
|
-
"",
|
|
431
|
-
"Recomp will regenerate all compartments and facts from raw history.",
|
|
432
|
-
"This may take a long time and consume significant tokens.",
|
|
433
|
-
"",
|
|
434
|
-
"Proceed?",
|
|
435
|
-
].join("\n")}
|
|
523
|
+
title={title}
|
|
524
|
+
message={message}
|
|
436
525
|
onConfirm={() => {
|
|
437
|
-
|
|
438
|
-
|
|
526
|
+
// Explicit choice → dismiss the fresh reminder durably so it
|
|
527
|
+
// won't re-show. (Resume prompts are staging-driven and still
|
|
528
|
+
// fire if this run is later interrupted.)
|
|
529
|
+
void dismissUpgradeReminder(sessionId)
|
|
530
|
+
void requestUpgrade(sessionId)
|
|
531
|
+
// Start the sidebar's recomp self-poll immediately — the RPC
|
|
532
|
+
// call fires no message event, so without this the progress
|
|
533
|
+
// bar wouldn't appear until the upgrade finished.
|
|
534
|
+
kickRecompProgressRefresh()
|
|
535
|
+
api.ui.toast({
|
|
536
|
+
message: resume
|
|
537
|
+
? "Resuming session upgrade — running in the background"
|
|
538
|
+
: "Session upgrade started — running in the background",
|
|
539
|
+
variant: "info",
|
|
540
|
+
duration: 5000,
|
|
541
|
+
})
|
|
439
542
|
}}
|
|
440
543
|
onCancel={() => {
|
|
441
|
-
|
|
544
|
+
// Explicit decline → set the durable stamp so we don't re-prompt
|
|
545
|
+
// on every restart. The fix for stamp-on-display trapping a
|
|
546
|
+
// never-upgraded session (dogfood 2026-05-30) relies on THIS
|
|
547
|
+
// being the only place the TUI path stamps.
|
|
548
|
+
void dismissUpgradeReminder(sessionId)
|
|
549
|
+
api.ui.toast({
|
|
550
|
+
message: "Upgrade skipped — run /ctx-session-upgrade anytime",
|
|
551
|
+
variant: "info",
|
|
552
|
+
duration: 4000,
|
|
553
|
+
})
|
|
442
554
|
}}
|
|
443
555
|
/>
|
|
444
|
-
)
|
|
445
|
-
|
|
556
|
+
),
|
|
557
|
+
)
|
|
558
|
+
return true
|
|
446
559
|
}
|
|
447
560
|
|
|
448
|
-
function showStatusDialog(api: TuiPluginApi) {
|
|
449
|
-
const sessionId =
|
|
561
|
+
async function showStatusDialog(api: TuiPluginApi, targetSessionId = getSessionId(api)): Promise<boolean> {
|
|
562
|
+
const sessionId = targetSessionId
|
|
450
563
|
if (!sessionId) {
|
|
451
564
|
api.ui.toast({ message: "No active session", variant: "warning" })
|
|
452
|
-
return
|
|
565
|
+
return false
|
|
453
566
|
}
|
|
454
567
|
|
|
455
568
|
const directory = api.state.path.directory ?? ""
|
|
456
569
|
const modelKey = getModelKeyFromMessages(api, sessionId)
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
570
|
+
const detail = await loadStatusDetail(sessionId, directory, modelKey)
|
|
571
|
+
// Ack only after the dialog is actually shown for the same active session;
|
|
572
|
+
// route switches while the RPC detail load is in flight must leave it pending.
|
|
573
|
+
if (getSessionId(api) !== sessionId) return false
|
|
574
|
+
|
|
575
|
+
api.ui.dialog.replace(() => <StatusDialog api={api} s={detail} />)
|
|
576
|
+
return true
|
|
460
577
|
}
|
|
461
578
|
|
|
462
579
|
/**
|
|
@@ -644,10 +761,46 @@ const tui: TuiPlugin = async (api, _options, meta) => {
|
|
|
644
761
|
registerCommandPaletteEntries(api)
|
|
645
762
|
|
|
646
763
|
// Poll for server→TUI messages: toasts and dialog requests.
|
|
647
|
-
//
|
|
764
|
+
// The poller owns cursor advancement so notifications are acked only after
|
|
765
|
+
// they are accepted for the still-active session and delivered to the UI.
|
|
766
|
+
let pollInFlight = false
|
|
648
767
|
const messagePoller = setInterval(() => {
|
|
649
|
-
|
|
650
|
-
|
|
768
|
+
// Scope the drain to the TUI's active session so notifications tagged for
|
|
769
|
+
// a different session (served by the same RPC process) are not consumed
|
|
770
|
+
// here. Do not poll on non-session routes: a session-scoped action fetched
|
|
771
|
+
// while sessionless could otherwise be acked without being shown.
|
|
772
|
+
// Avoid overlapping read-only drains: the server re-delivers until acked,
|
|
773
|
+
// so a second in-flight poll can fetch and dispatch the same batch twice.
|
|
774
|
+
if (pollInFlight) return
|
|
775
|
+
|
|
776
|
+
const requestedSessionId = getSessionId(api)
|
|
777
|
+
if (!requestedSessionId) return
|
|
778
|
+
|
|
779
|
+
pollInFlight = true
|
|
780
|
+
const pollGeneration = getRpcGeneration()
|
|
781
|
+
void consumeTuiMessages(requestedSessionId).then(async (messages) => {
|
|
782
|
+
// The dialog handlers read the current session when they run. If the
|
|
783
|
+
// user switched routes while the RPC was in flight, drop this whole
|
|
784
|
+
// batch without advancing the cursor; the next poll for the new
|
|
785
|
+
// session will fetch the right notifications.
|
|
786
|
+
// Ignore late responses from an older RPC client generation; close/init
|
|
787
|
+
// clears cursors and stale callbacks must not recreate them.
|
|
788
|
+
if (getRpcGeneration() !== pollGeneration) return
|
|
789
|
+
|
|
790
|
+
if (getSessionId(api) !== requestedSessionId) return
|
|
791
|
+
|
|
792
|
+
const orderedMessages = [...messages].sort((a, b) => a.id - b.id)
|
|
793
|
+
const handledMessageIds = new Set<number>()
|
|
794
|
+
for (const msg of orderedMessages) {
|
|
795
|
+
// Drop any action/dialog whose sessionId doesn't match this TUI's
|
|
796
|
+
// active session (session-less/global notifications still apply).
|
|
797
|
+
if (
|
|
798
|
+
msg.type === "action" &&
|
|
799
|
+
msg.sessionId &&
|
|
800
|
+
msg.sessionId !== requestedSessionId
|
|
801
|
+
) {
|
|
802
|
+
continue
|
|
803
|
+
}
|
|
651
804
|
if (msg.type === "toast") {
|
|
652
805
|
const p = msg.payload
|
|
653
806
|
api.ui.toast({
|
|
@@ -655,17 +808,47 @@ const tui: TuiPlugin = async (api, _options, meta) => {
|
|
|
655
808
|
variant: (p.variant as "info" | "warning" | "error" | "success") ?? "info",
|
|
656
809
|
duration: typeof p.duration === "number" ? p.duration : 5000,
|
|
657
810
|
})
|
|
811
|
+
handledMessageIds.add(msg.id)
|
|
658
812
|
} else if (msg.type === "action") {
|
|
659
813
|
const action = msg.payload?.action
|
|
660
814
|
if (action === "show-status-dialog") {
|
|
661
|
-
showStatusDialog(api)
|
|
815
|
+
if (await showStatusDialog(api, requestedSessionId)) {
|
|
816
|
+
handledMessageIds.add(msg.id)
|
|
817
|
+
}
|
|
662
818
|
} else if (action === "show-recomp-dialog") {
|
|
663
|
-
showRecompDialog(api)
|
|
819
|
+
if (await showRecompDialog(api, requestedSessionId)) {
|
|
820
|
+
handledMessageIds.add(msg.id)
|
|
821
|
+
}
|
|
822
|
+
} else if (action === "show-upgrade-dialog") {
|
|
823
|
+
const resume =
|
|
824
|
+
msg.payload?.resume === true
|
|
825
|
+
? {
|
|
826
|
+
stagedCount: Number(msg.payload?.stagedCount ?? 0),
|
|
827
|
+
stagedThrough: Number(msg.payload?.stagedThrough ?? 0),
|
|
828
|
+
}
|
|
829
|
+
: undefined
|
|
830
|
+
if (showUpgradeDialog(api, resume, requestedSessionId)) {
|
|
831
|
+
handledMessageIds.add(msg.id)
|
|
832
|
+
}
|
|
664
833
|
}
|
|
665
834
|
}
|
|
666
835
|
}
|
|
836
|
+
const handledPrefixMessages: TuiMessage[] = []
|
|
837
|
+
for (const msg of orderedMessages) {
|
|
838
|
+
if (!handledMessageIds.has(msg.id)) break
|
|
839
|
+
handledPrefixMessages.push(msg)
|
|
840
|
+
}
|
|
841
|
+
// A dialog helper may have awaited more RPC work; re-check before
|
|
842
|
+
// acking so a dispose/reinit or route switch during that await cannot
|
|
843
|
+
// advance a stale cursor.
|
|
844
|
+
if (getRpcGeneration() !== pollGeneration) return
|
|
845
|
+
if (getSessionId(api) !== requestedSessionId) return
|
|
846
|
+
|
|
847
|
+
markTuiMessagesHandled(requestedSessionId, handledPrefixMessages)
|
|
667
848
|
}).catch(() => {
|
|
668
849
|
// Intentional: message polling should never crash the TUI
|
|
850
|
+
}).finally(() => {
|
|
851
|
+
pollInFlight = false
|
|
669
852
|
})
|
|
670
853
|
}, 500)
|
|
671
854
|
|