@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
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import { appendFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
2
5
|
import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
|
|
3
6
|
import type { TuiSlotPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
|
|
4
7
|
import packageJson from "../../../package.json"
|
|
5
8
|
import { loadSidebarSnapshot, type SidebarSnapshot } from "../data/context-db"
|
|
6
9
|
import { formatThresholdPercent } from "../../shared/format-threshold"
|
|
7
10
|
|
|
11
|
+
// TEMP recomp-poll instrumentation (dogfood 2026-05-30). Writes to a dedicated
|
|
12
|
+
// file so we can trace the client poll loop the server log can't see. Remove
|
|
13
|
+
// once the freeze is diagnosed.
|
|
14
|
+
const RECOMP_TRACE = join(tmpdir(), "mc-recomp-trace.log")
|
|
15
|
+
function rtrace(msg: string): void {
|
|
16
|
+
try {
|
|
17
|
+
appendFileSync(RECOMP_TRACE, `[${new Date().toISOString()}] ${msg}\n`)
|
|
18
|
+
} catch {
|
|
19
|
+
// ignore
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Module-level hook so the upgrade/recomp dialog can kick the sidebar into its
|
|
24
|
+
// fast recomp self-poll the INSTANT the user confirms — without waiting for a
|
|
25
|
+
// parent-session message event (the RPC upgrade/recomp call fires none). The
|
|
26
|
+
// mounted SidebarContent registers its refresh here (dogfood 2026-05-30).
|
|
27
|
+
let activeRecompPollKick: (() => void) | null = null
|
|
28
|
+
export function kickRecompProgressRefresh(): void {
|
|
29
|
+
rtrace(`kickRecompProgressRefresh called; activeKick=${activeRecompPollKick ? "set" : "NULL"}`)
|
|
30
|
+
activeRecompPollKick?.()
|
|
31
|
+
}
|
|
32
|
+
|
|
8
33
|
const SINGLE_BORDER = { type: "single" } as any
|
|
9
34
|
const REFRESH_DEBOUNCE_MS = 150
|
|
10
35
|
|
|
@@ -22,13 +47,22 @@ function relativeTime(ms: number): string {
|
|
|
22
47
|
return `${Math.floor(diff / 86_400_000)}d ago`
|
|
23
48
|
}
|
|
24
49
|
|
|
50
|
+
// Text progress bar, e.g. [██████░░░░] for the recomp/upgrade live indicator.
|
|
51
|
+
function progressBar(fraction: number, width = 14): string {
|
|
52
|
+
const clamped = Math.max(0, Math.min(1, fraction))
|
|
53
|
+
const filled = Math.round(clamped * width)
|
|
54
|
+
return `[${"█".repeat(filled)}${"░".repeat(width - filled)}]`
|
|
55
|
+
}
|
|
56
|
+
|
|
25
57
|
// Token breakdown segment colors (hardcoded hex values)
|
|
26
58
|
const COLORS = {
|
|
27
59
|
// Cool / structured — injected by the plugin into message[0]
|
|
28
60
|
system: "#c084fc", // Purple
|
|
61
|
+
docs: "#22d3ee", // Cyan — <project-docs>
|
|
29
62
|
compartments: "#60a5fa", // Blue
|
|
30
63
|
facts: "#fbbf24", // Yellow/orange
|
|
31
64
|
memories: "#34d399", // Green
|
|
65
|
+
profile: "#a3e635", // Lime — <user-profile>
|
|
32
66
|
// Warm / user-facing — regular chat and tool traffic. Grouped visually
|
|
33
67
|
// by hue family so the user reads them as a related block.
|
|
34
68
|
conversation: "#f87171", // Red
|
|
@@ -47,17 +81,17 @@ interface TokenSegment {
|
|
|
47
81
|
const TokenBreakdown = (props: {
|
|
48
82
|
theme: TuiThemeCurrent
|
|
49
83
|
snapshot: SidebarSnapshot
|
|
84
|
+
// Collapsed mode renders only the proportional bar (no per-category legend
|
|
85
|
+
// rows) so the sidebar shrinks to the progress bar + a few summary lines.
|
|
86
|
+
collapsed?: boolean
|
|
50
87
|
}) => {
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
// already does (`Math.max(20, innerWidth)`).
|
|
59
|
-
const barWidth = 24
|
|
60
|
-
|
|
88
|
+
// The bar is rendered as a flex row of colored boxes, each with
|
|
89
|
+
// flexGrow=tokens and flexBasis=0. opentui distributes the parent
|
|
90
|
+
// container's full width proportionally, so the bar always fills the
|
|
91
|
+
// sidebar regardless of terminal size. No hardcoded width is needed —
|
|
92
|
+
// this fixes both the over-wide bar that wrapped onto a second line on
|
|
93
|
+
// narrow sidebars (issue #90) and the under-wide bar that left empty
|
|
94
|
+
// space on the right on wide sidebars.
|
|
61
95
|
const segments = createMemo<TokenSegment[]>(() => {
|
|
62
96
|
const s = props.snapshot
|
|
63
97
|
const total = s.inputTokens || 1
|
|
@@ -73,6 +107,16 @@ const TokenBreakdown = (props: {
|
|
|
73
107
|
})
|
|
74
108
|
}
|
|
75
109
|
|
|
110
|
+
// Docs (cyan) — injected <project-docs> block (ARCHITECTURE/STRUCTURE)
|
|
111
|
+
if (s.docsTokens > 0) {
|
|
112
|
+
result.push({
|
|
113
|
+
key: "docs",
|
|
114
|
+
tokens: s.docsTokens,
|
|
115
|
+
color: COLORS.docs,
|
|
116
|
+
label: "Docs",
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
76
120
|
// Compartments (blue)
|
|
77
121
|
if (s.compartmentTokens > 0) {
|
|
78
122
|
result.push({
|
|
@@ -103,6 +147,16 @@ const TokenBreakdown = (props: {
|
|
|
103
147
|
})
|
|
104
148
|
}
|
|
105
149
|
|
|
150
|
+
// User Profile (lime) — injected <user-profile> block (promoted user memories)
|
|
151
|
+
if (s.profileTokens > 0) {
|
|
152
|
+
result.push({
|
|
153
|
+
key: "profile",
|
|
154
|
+
tokens: s.profileTokens,
|
|
155
|
+
color: COLORS.profile,
|
|
156
|
+
label: "User Profile",
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
106
160
|
// Conversation = real user/assistant text/reasoning/images
|
|
107
161
|
// (excludes injected session-history and excludes tool call I/O).
|
|
108
162
|
//
|
|
@@ -151,88 +205,57 @@ const TokenBreakdown = (props: {
|
|
|
151
205
|
|
|
152
206
|
const totalTokens = createMemo(() => props.snapshot.inputTokens || 1)
|
|
153
207
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// Convert to character widths. Minimum 1 char ONLY when the segment
|
|
164
|
-
// has tokens > 0 — zero-token segments (e.g. Conversation when the
|
|
165
|
-
// calibrator rounded it to zero) must get width 0 so the bar stays
|
|
166
|
-
// proportional. The legend row still renders for zero-token segments
|
|
167
|
-
// to keep the row stable.
|
|
168
|
-
let widths = segs.map((seg, i) =>
|
|
169
|
-
seg.tokens > 0 ? Math.max(1, Math.round(proportions[i] * barWidth)) : 0,
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
// Adjust to exactly barWidth
|
|
173
|
-
const sum = widths.reduce((a, b) => a + b, 0)
|
|
174
|
-
if (sum > barWidth) {
|
|
175
|
-
// Shrink from the largest segments
|
|
176
|
-
let excess = sum - barWidth
|
|
177
|
-
while (excess > 0) {
|
|
178
|
-
const maxIdx = widths.indexOf(Math.max(...widths))
|
|
179
|
-
if (widths[maxIdx] > 1) {
|
|
180
|
-
widths[maxIdx]--
|
|
181
|
-
excess--
|
|
182
|
-
} else {
|
|
183
|
-
break
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
} else if (sum < barWidth) {
|
|
187
|
-
// Expand the largest segments
|
|
188
|
-
let deficit = barWidth - sum
|
|
189
|
-
while (deficit > 0) {
|
|
190
|
-
const maxIdx = widths.indexOf(Math.max(...widths))
|
|
191
|
-
widths[maxIdx]++
|
|
192
|
-
deficit--
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return widths
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
const barSegments = createMemo(() => {
|
|
200
|
-
const segs = segments()
|
|
201
|
-
const widths = segmentWidths()
|
|
202
|
-
return segs.map((seg, i) => ({
|
|
203
|
-
chars: "█".repeat(widths[i] || 0),
|
|
204
|
-
color: seg.color,
|
|
205
|
-
}))
|
|
206
|
-
})
|
|
208
|
+
// Render-time segments for the bar. Zero-token segments are filtered out
|
|
209
|
+
// entirely (no flex weight, no rendered box) so they don't claim any
|
|
210
|
+
// width. Non-zero segments still get a Math.max(1, ...) floor on
|
|
211
|
+
// flexGrow so very small contributions remain visible as a thin sliver.
|
|
212
|
+
// The legend rows below show every segment (including zeros) for table
|
|
213
|
+
// stability — only the bar prunes them.
|
|
214
|
+
const barSegments = createMemo(() =>
|
|
215
|
+
segments().filter((seg) => seg.tokens > 0),
|
|
216
|
+
)
|
|
207
217
|
|
|
208
218
|
return (
|
|
209
219
|
<box width="100%" flexDirection="column">
|
|
210
|
-
{/* Segmented bar
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
220
|
+
{/* Segmented bar: a width="100%" flex row of colored boxes,
|
|
221
|
+
each with flexGrow proportional to its token count and
|
|
222
|
+
flexBasis=0. opentui distributes the parent's full width
|
|
223
|
+
proportionally, so the bar always fills the sidebar
|
|
224
|
+
regardless of terminal size. Height is fixed at 1 row;
|
|
225
|
+
backgroundColor renders the colored bar. */}
|
|
226
|
+
<box width="100%" flexDirection="row" height={1}>
|
|
227
|
+
{barSegments().map((seg) => (
|
|
228
|
+
<box
|
|
229
|
+
key={seg.key}
|
|
230
|
+
flexGrow={Math.max(1, seg.tokens)}
|
|
231
|
+
flexBasis={0}
|
|
232
|
+
height={1}
|
|
233
|
+
backgroundColor={seg.color}
|
|
234
|
+
/>
|
|
214
235
|
))}
|
|
215
236
|
</box>
|
|
216
237
|
|
|
217
|
-
{/* Legend rows */}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
{/* Legend rows — suppressed in collapsed mode (bar only) */}
|
|
239
|
+
{!props.collapsed && (
|
|
240
|
+
<box flexDirection="column" marginTop={0}>
|
|
241
|
+
{segments().map((seg) => {
|
|
242
|
+
const pct = ((seg.tokens / totalTokens()) * 100).toFixed(0)
|
|
243
|
+
return (
|
|
244
|
+
<box
|
|
245
|
+
key={seg.key}
|
|
246
|
+
width="100%"
|
|
247
|
+
flexDirection="row"
|
|
248
|
+
justifyContent="space-between"
|
|
249
|
+
>
|
|
250
|
+
<text fg={seg.color}>{seg.label}</text>
|
|
251
|
+
<text fg={props.theme.textMuted}>
|
|
252
|
+
{compactTokens(seg.tokens)} ({pct}%)
|
|
253
|
+
</text>
|
|
254
|
+
</box>
|
|
255
|
+
)
|
|
256
|
+
})}
|
|
257
|
+
</box>
|
|
258
|
+
)}
|
|
236
259
|
</box>
|
|
237
260
|
)
|
|
238
261
|
}
|
|
@@ -270,26 +293,144 @@ const SectionHeader = (props: { theme: TuiThemeCurrent; title: string }) => (
|
|
|
270
293
|
</box>
|
|
271
294
|
)
|
|
272
295
|
|
|
296
|
+
// Live recomp / session-upgrade progress. Renders while an upgrade runs (and
|
|
297
|
+
// briefly after it finishes) so a multi-minute rebuild is visible instead of a
|
|
298
|
+
// single missed toast (dogfood 2026-05-30).
|
|
299
|
+
const RecompProgressSection = (props: {
|
|
300
|
+
theme: TuiThemeCurrent
|
|
301
|
+
progress: NonNullable<SidebarSnapshot["recompProgress"]>
|
|
302
|
+
}) => {
|
|
303
|
+
// CRITICAL: read `props.progress` reactively on every access — do NOT
|
|
304
|
+
// destructure it into a local `const p = props.progress` at creation time.
|
|
305
|
+
// The parent keeps THIS component instance mounted as the phase advances
|
|
306
|
+
// (recomp → migration → done), so a frozen `p` would render the
|
|
307
|
+
// creation-time phase forever — the sidebar stuck on "upgrading / Running
|
|
308
|
+
// historian (pass 1)…" even though the upgrade finished. Each accessor below
|
|
309
|
+
// tracks the parent signal so the label/bar/note update live (root cause of
|
|
310
|
+
// the dogfood 2026-05-30 "recomp upgrading stays" freeze).
|
|
311
|
+
const phase = () => props.progress.phase
|
|
312
|
+
const fraction = () =>
|
|
313
|
+
props.progress.totalMessages > 0
|
|
314
|
+
? props.progress.processedMessages / props.progress.totalMessages
|
|
315
|
+
: 0
|
|
316
|
+
const pct = () => Math.round(fraction() * 100)
|
|
317
|
+
|
|
318
|
+
const label = createMemo(() => {
|
|
319
|
+
switch (props.progress.phase) {
|
|
320
|
+
case "recomp":
|
|
321
|
+
return { text: "upgrading ⟳", color: props.theme.warning }
|
|
322
|
+
case "migration":
|
|
323
|
+
return { text: "Migrating memories ⟳", color: props.theme.warning }
|
|
324
|
+
case "done":
|
|
325
|
+
return { text: "✓ Upgrade complete", color: props.theme.success ?? props.theme.accent }
|
|
326
|
+
case "failed":
|
|
327
|
+
return { text: "✗ Upgrade failed", color: props.theme.error }
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<>
|
|
333
|
+
<box width="100%" marginTop={1} flexDirection="row" justifyContent="space-between">
|
|
334
|
+
<text fg={props.theme.text}>
|
|
335
|
+
<b>Recomp</b>
|
|
336
|
+
</text>
|
|
337
|
+
<text fg={label().color}>{label().text}</text>
|
|
338
|
+
</box>
|
|
339
|
+
{/* Determinate bar during the compartment-rebuild phase. */}
|
|
340
|
+
{phase() === "recomp" && props.progress.totalMessages > 0 && (
|
|
341
|
+
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
342
|
+
<text fg={props.theme.accent}>{progressBar(fraction())}</text>
|
|
343
|
+
<text fg={props.theme.textMuted}>{pct()}%</text>
|
|
344
|
+
</box>
|
|
345
|
+
)}
|
|
346
|
+
{/* Transient status note (e.g. "Starting…", "Trying fallback
|
|
347
|
+
sonnet-4-6…", "Repair retry…") — surfaces live activity during a
|
|
348
|
+
long pass, including before the determinate range is known. */}
|
|
349
|
+
{(phase() === "recomp" || phase() === "migration") && props.progress.note && (
|
|
350
|
+
<text fg={props.theme.textMuted}>{props.progress.note}</text>
|
|
351
|
+
)}
|
|
352
|
+
{phase() === "recomp" && (
|
|
353
|
+
<StatRow
|
|
354
|
+
theme={props.theme}
|
|
355
|
+
label="Compartments"
|
|
356
|
+
value={`${props.progress.compartmentsCreated} (${props.progress.passCount} pass${props.progress.passCount === 1 ? "" : "es"})`}
|
|
357
|
+
dim
|
|
358
|
+
/>
|
|
359
|
+
)}
|
|
360
|
+
{/* Terminal reason (failed) — kept visible so the user sees WHY. */}
|
|
361
|
+
{phase() === "failed" && props.progress.message && (
|
|
362
|
+
<text fg={props.theme.textMuted}>{props.progress.message}</text>
|
|
363
|
+
)}
|
|
364
|
+
</>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
|
|
273
368
|
const SidebarContent = (props: {
|
|
274
369
|
api: TuiPluginApi
|
|
275
370
|
sessionID: () => string
|
|
276
371
|
theme: TuiThemeCurrent
|
|
277
372
|
}) => {
|
|
278
373
|
const [snapshot, setSnapshot] = createSignal<SidebarSnapshot | null>(null)
|
|
374
|
+
// Collapsed view: progress bar + 3 summary lines (Historian / Memories /
|
|
375
|
+
// Status), no per-category legend or section grid. In-memory only (resets
|
|
376
|
+
// to expanded on TUI restart), mirroring the native MCP sidebar toggle.
|
|
377
|
+
const [collapsed, setCollapsed] = createSignal(false)
|
|
279
378
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined
|
|
379
|
+
// Self-sustaining poll while a recomp/upgrade is running. Recomp work
|
|
380
|
+
// happens in CHILD sessions whose message events are filtered out of the
|
|
381
|
+
// subscription below, so without this the progress bar would freeze until
|
|
382
|
+
// the next parent-session message. Active only during recomp/migration;
|
|
383
|
+
// stops itself once the phase goes terminal/absent (dogfood 2026-05-30).
|
|
384
|
+
let recompPollTimer: ReturnType<typeof setTimeout> | undefined
|
|
385
|
+
const RECOMP_POLL_MS = 1200
|
|
386
|
+
// Robust recomp poll state. The loop MUST survive a failed/slow snapshot
|
|
387
|
+
// fetch — the server is busy doing the historian LLM call during a recomp,
|
|
388
|
+
// so a poll can reject or return a stale (pre-recomp) cached snapshot. The
|
|
389
|
+
// OLD loop reattached the next timer only inside `.then()`, so any rejection
|
|
390
|
+
// killed it and the bar froze mid-pass (dogfood 2026-05-30). This version
|
|
391
|
+
// reschedules on BOTH success and failure, keyed on `recompActive`, and only
|
|
392
|
+
// stops on a terminal phase, a bounded "never started" probe window, or the
|
|
393
|
+
// entry vanishing after we'd seen it active.
|
|
394
|
+
let recompActive = false
|
|
395
|
+
let recompSawPhase = false
|
|
396
|
+
let recompPollCount = 0
|
|
397
|
+
let recompConsecutiveAbsent = 0
|
|
398
|
+
const RECOMP_PROBE_MAX = 12 // ~15s for the server's "Starting…" to land
|
|
399
|
+
// After we've SEEN an active phase, a momentarily absent snapshot is almost
|
|
400
|
+
// always transient — the server's sticky cache serves a pre-recomp snapshot
|
|
401
|
+
// (no recompProgress) during the token-quiet recomp window, or a concurrent
|
|
402
|
+
// BEGIN-IMMEDIATE publish makes the snapshot DB read throw → bare empty. The
|
|
403
|
+
// entry is held until terminal + a 30s grace, so we keep polling through many
|
|
404
|
+
// absents and only give up after a long run of them (entry truly gone but we
|
|
405
|
+
// somehow missed "done"). This was the freeze: the old logic stopped on the
|
|
406
|
+
// FIRST absent-after-active (dogfood 2026-05-30).
|
|
407
|
+
const RECOMP_ABSENT_GIVEUP = 40 // ~48s of continuous absence → stop
|
|
408
|
+
const RECOMP_MAX_POLLS = 1500 // ~30min absolute safety cap
|
|
280
409
|
|
|
281
410
|
const refresh = () => {
|
|
282
411
|
const sid = props.sessionID()
|
|
283
412
|
if (!sid) return
|
|
284
413
|
const directory = props.api.state.path.directory ?? ""
|
|
285
|
-
void loadSidebarSnapshot(sid, directory)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
414
|
+
void loadSidebarSnapshot(sid, directory)
|
|
415
|
+
.then((data) => {
|
|
416
|
+
setSnapshot(data)
|
|
417
|
+
try {
|
|
418
|
+
props.api.renderer.requestRender()
|
|
419
|
+
} catch {
|
|
420
|
+
// Ignore render errors
|
|
421
|
+
}
|
|
422
|
+
// If a recomp/upgrade is running (detected via any refresh, e.g.
|
|
423
|
+
// a /ctx-recomp command not started from the dialog), make sure
|
|
424
|
+
// the dedicated poll loop is running.
|
|
425
|
+
const phase = data?.recompProgress?.phase
|
|
426
|
+
if ((phase === "recomp" || phase === "migration") && !recompActive) {
|
|
427
|
+
kickRecompPoll()
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
.catch(() => {
|
|
431
|
+
// one-shot refresh failure is non-fatal; the recomp loop (if any)
|
|
432
|
+
// has its own resilient retry.
|
|
433
|
+
})
|
|
293
434
|
}
|
|
294
435
|
|
|
295
436
|
const scheduleRefresh = () => {
|
|
@@ -300,8 +441,108 @@ const SidebarContent = (props: {
|
|
|
300
441
|
}, REFRESH_DEBOUNCE_MS)
|
|
301
442
|
}
|
|
302
443
|
|
|
444
|
+
const scheduleRecompTick = () => {
|
|
445
|
+
if (!recompActive) return
|
|
446
|
+
if (recompPollTimer) clearTimeout(recompPollTimer)
|
|
447
|
+
recompPollTimer = setTimeout(recompTick, RECOMP_POLL_MS)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function recompTick(): void {
|
|
451
|
+
if (!recompActive) return
|
|
452
|
+
recompPollCount += 1
|
|
453
|
+
if (recompPollCount > RECOMP_MAX_POLLS) {
|
|
454
|
+
recompActive = false
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
const sid = props.sessionID()
|
|
458
|
+
if (!sid) {
|
|
459
|
+
recompActive = false
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
const directory = props.api.state.path.directory ?? ""
|
|
463
|
+
void loadSidebarSnapshot(sid, directory)
|
|
464
|
+
.then((data) => {
|
|
465
|
+
const phase = data?.recompProgress?.phase
|
|
466
|
+
rtrace(
|
|
467
|
+
`poll#${recompPollCount} phase=${phase ?? "ABSENT"} passCount=${data?.recompProgress?.passCount ?? "-"} note=${data?.recompProgress?.note ?? "-"} sawPhase=${recompSawPhase} absent=${recompConsecutiveAbsent}`,
|
|
468
|
+
)
|
|
469
|
+
// While a recomp is known-active, a transient snapshot that lost
|
|
470
|
+
// recompProgress (sticky cache / busy-DB empty) must NOT wipe the
|
|
471
|
+
// visible bar — carry the last good progress forward so it stays
|
|
472
|
+
// stable until a real update or the terminal state lands.
|
|
473
|
+
const prevProgress = snapshot()?.recompProgress
|
|
474
|
+
const merged =
|
|
475
|
+
!phase && recompSawPhase && prevProgress
|
|
476
|
+
? { ...data, recompProgress: prevProgress }
|
|
477
|
+
: data
|
|
478
|
+
setSnapshot(merged)
|
|
479
|
+
try {
|
|
480
|
+
props.api.renderer.requestRender()
|
|
481
|
+
} catch {
|
|
482
|
+
// ignore render errors
|
|
483
|
+
}
|
|
484
|
+
if (phase === "recomp" || phase === "migration") {
|
|
485
|
+
recompSawPhase = true
|
|
486
|
+
recompConsecutiveAbsent = 0
|
|
487
|
+
scheduleRecompTick()
|
|
488
|
+
} else if (phase === "done" || phase === "failed") {
|
|
489
|
+
// Terminal state rendered — stop. The server keeps "done" for
|
|
490
|
+
// a grace window and "failed" until the next run, so the
|
|
491
|
+
// outcome stays visible without further polling.
|
|
492
|
+
rtrace(`STOP: terminal phase=${phase}`)
|
|
493
|
+
recompActive = false
|
|
494
|
+
} else {
|
|
495
|
+
// Phase absent this poll.
|
|
496
|
+
recompConsecutiveAbsent += 1
|
|
497
|
+
if (!recompSawPhase) {
|
|
498
|
+
// Still waiting for the server's first "Starting…".
|
|
499
|
+
if (recompPollCount < RECOMP_PROBE_MAX) scheduleRecompTick()
|
|
500
|
+
else {
|
|
501
|
+
rtrace("STOP: probe window exhausted, never saw phase")
|
|
502
|
+
recompActive = false
|
|
503
|
+
}
|
|
504
|
+
} else if (recompConsecutiveAbsent < RECOMP_ABSENT_GIVEUP) {
|
|
505
|
+
// Seen it active — absent is almost certainly the sticky
|
|
506
|
+
// cache / a transient snapshot read. Keep polling so we
|
|
507
|
+
// still catch the terminal state. DON'T overwrite the
|
|
508
|
+
// last good progress snapshot with this transient empty.
|
|
509
|
+
scheduleRecompTick()
|
|
510
|
+
} else {
|
|
511
|
+
// Long continuous absence — the entry is genuinely gone.
|
|
512
|
+
rtrace("STOP: absent giveup")
|
|
513
|
+
recompActive = false
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
.catch((err) => {
|
|
518
|
+
// CRITICAL: a failed/slow fetch must NOT kill the loop — keep
|
|
519
|
+
// polling while active so we still catch the terminal state.
|
|
520
|
+
rtrace(`poll#${recompPollCount} FETCH ERROR: ${String(err)}`)
|
|
521
|
+
scheduleRecompTick()
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Kick the resilient recomp poll loop on dialog confirm (or when a refresh
|
|
526
|
+
// first detects an active recomp). The server emits an immediate "Starting…"
|
|
527
|
+
// entry; the probe window covers the brief RPC race before it lands.
|
|
528
|
+
function kickRecompPoll(): void {
|
|
529
|
+
rtrace(`kickRecompPoll: recompActive=${recompActive} (${recompActive ? "SKIP" : "starting"})`)
|
|
530
|
+
if (recompActive) return // already running
|
|
531
|
+
recompActive = true
|
|
532
|
+
recompSawPhase = false
|
|
533
|
+
recompPollCount = 0
|
|
534
|
+
recompConsecutiveAbsent = 0
|
|
535
|
+
recompTick()
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
activeRecompPollKick = kickRecompPoll
|
|
539
|
+
rtrace("SidebarContent mounted; registered activeRecompPollKick")
|
|
540
|
+
|
|
303
541
|
onCleanup(() => {
|
|
304
542
|
if (refreshTimer) clearTimeout(refreshTimer)
|
|
543
|
+
if (recompPollTimer) clearTimeout(recompPollTimer)
|
|
544
|
+
recompActive = false
|
|
545
|
+
if (activeRecompPollKick === kickRecompPoll) activeRecompPollKick = null
|
|
305
546
|
})
|
|
306
547
|
|
|
307
548
|
// Refresh on session change
|
|
@@ -358,19 +599,28 @@ const SidebarContent = (props: {
|
|
|
358
599
|
paddingLeft={1}
|
|
359
600
|
paddingRight={1}
|
|
360
601
|
>
|
|
361
|
-
{/* Header
|
|
362
|
-
|
|
602
|
+
{/* Header: triangle toggle + badge + version. Clicking the row
|
|
603
|
+
collapses/expands the panel (mirrors OpenCode's native MCP
|
|
604
|
+
sidebar section and AFT's sidebar). */}
|
|
605
|
+
<box
|
|
606
|
+
flexDirection="row"
|
|
607
|
+
justifyContent="space-between"
|
|
608
|
+
alignItems="center"
|
|
609
|
+
onMouseDown={() => setCollapsed((x) => !x)}
|
|
610
|
+
>
|
|
363
611
|
<box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
|
|
364
612
|
<text fg={props.theme.background}>
|
|
365
|
-
<b>Magic Context</b>
|
|
613
|
+
<b>{collapsed() ? "▶ " : "▼ "}Magic Context</b>
|
|
366
614
|
</text>
|
|
367
615
|
</box>
|
|
368
616
|
<text fg={props.theme.textMuted}>v{packageJson.version}</text>
|
|
369
617
|
</box>
|
|
370
618
|
|
|
371
|
-
{/* Token breakdown bar
|
|
619
|
+
{/* Token breakdown bar. In collapsed mode the header, bar and the
|
|
620
|
+
3 summary rows stack with no vertical padding for a compact look;
|
|
621
|
+
expanded mode keeps the 1-row gap above the bar. */}
|
|
372
622
|
{s() && s()!.inputTokens > 0 && (
|
|
373
|
-
<box marginTop={1} flexDirection="column">
|
|
623
|
+
<box marginTop={collapsed() ? 0 : 1} flexDirection="column">
|
|
374
624
|
{(s()?.contextLimit ?? 0) > 0 && (
|
|
375
625
|
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
376
626
|
{/* Left: current usage vs the per-model execute
|
|
@@ -390,17 +640,56 @@ const SidebarContent = (props: {
|
|
|
390
640
|
</text>
|
|
391
641
|
</box>
|
|
392
642
|
)}
|
|
393
|
-
<TokenBreakdown theme={props.theme} snapshot={s()!} />
|
|
643
|
+
<TokenBreakdown theme={props.theme} snapshot={s()!} collapsed={collapsed()} />
|
|
394
644
|
</box>
|
|
395
645
|
)}
|
|
396
646
|
|
|
647
|
+
{/* Collapsed view — progress bar (above) + 3 summary lines:
|
|
648
|
+
Historian (with compartment count), Memories (injected/total),
|
|
649
|
+
Status (Q=queued ops, N=session notes). */}
|
|
650
|
+
{collapsed() && (
|
|
651
|
+
<box width="100%" flexDirection="column">
|
|
652
|
+
{/* Collapsed rows are intentionally uniform faded-grey, not
|
|
653
|
+
bold/accent — they're a glanceable summary, so the label
|
|
654
|
+
and value share the muted tone (matches Memories row). */}
|
|
655
|
+
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
656
|
+
<text fg={props.theme.textMuted}>Historian</text>
|
|
657
|
+
{s()?.historianRunning ? (
|
|
658
|
+
<text fg={props.theme.warning}>comparting ⟳</text>
|
|
659
|
+
) : (
|
|
660
|
+
<text fg={props.theme.textMuted}>idle</text>
|
|
661
|
+
)}
|
|
662
|
+
</box>
|
|
663
|
+
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
664
|
+
<text fg={props.theme.textMuted}>Memories</text>
|
|
665
|
+
<text fg={props.theme.textMuted}>
|
|
666
|
+
{(s()?.memoryBlockCount ?? 0) > 0
|
|
667
|
+
? `${s()!.memoryBlockCount}/${s()?.memoryCount ?? 0}`
|
|
668
|
+
: String(s()?.memoryCount ?? 0)}
|
|
669
|
+
</text>
|
|
670
|
+
</box>
|
|
671
|
+
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
672
|
+
<text fg={props.theme.textMuted}>Status</text>
|
|
673
|
+
<text fg={props.theme.textMuted}>
|
|
674
|
+
C:{s()?.compartmentCount ?? 0} Q:{s()?.pendingOpsCount ?? 0} N:{s()?.sessionNoteCount ?? 0}
|
|
675
|
+
</text>
|
|
676
|
+
</box>
|
|
677
|
+
{s()?.recompProgress && (
|
|
678
|
+
<RecompProgressSection theme={props.theme} progress={s()!.recompProgress!} />
|
|
679
|
+
)}
|
|
680
|
+
</box>
|
|
681
|
+
)}
|
|
682
|
+
|
|
683
|
+
{/* Expanded view — full section grid. */}
|
|
684
|
+
{!collapsed() && (
|
|
685
|
+
<>
|
|
397
686
|
{/* Historian section */}
|
|
398
687
|
<box width="100%" marginTop={1} flexDirection="row" justifyContent="space-between">
|
|
399
688
|
<text fg={props.theme.text}>
|
|
400
689
|
<b>Historian</b>
|
|
401
690
|
</text>
|
|
402
691
|
{s()?.historianRunning ? (
|
|
403
|
-
<text fg={props.theme.warning}>
|
|
692
|
+
<text fg={props.theme.warning}>comparting ⟳</text>
|
|
404
693
|
) : (
|
|
405
694
|
<text fg={props.theme.textMuted}>idle</text>
|
|
406
695
|
)}
|
|
@@ -416,6 +705,11 @@ const SidebarContent = (props: {
|
|
|
416
705
|
value={String(s()?.factCount ?? 0)}
|
|
417
706
|
/>
|
|
418
707
|
|
|
708
|
+
{/* Recomp / session-upgrade live progress */}
|
|
709
|
+
{s()?.recompProgress && (
|
|
710
|
+
<RecompProgressSection theme={props.theme} progress={s()!.recompProgress!} />
|
|
711
|
+
)}
|
|
712
|
+
|
|
419
713
|
{/* Memory section */}
|
|
420
714
|
<SectionHeader theme={props.theme} title="Memory" />
|
|
421
715
|
<StatRow
|
|
@@ -477,6 +771,26 @@ const SidebarContent = (props: {
|
|
|
477
771
|
/>
|
|
478
772
|
</>
|
|
479
773
|
)}
|
|
774
|
+
|
|
775
|
+
{/* Stats — v0.21.8 ships a single "Total tokens" number while we
|
|
776
|
+
figure out how to present the new-work / reprocessed
|
|
777
|
+
categorization without confusing users. The underlying
|
|
778
|
+
snapshot fields (newWorkTokens, totalInputTokens) and the
|
|
779
|
+
session_meta columns are still populated; only the UI is
|
|
780
|
+
simplified for now. */}
|
|
781
|
+
{s()?.totalInputTokens != null && (
|
|
782
|
+
<>
|
|
783
|
+
<SectionHeader theme={props.theme} title="Stats" />
|
|
784
|
+
<StatRow
|
|
785
|
+
theme={props.theme}
|
|
786
|
+
label="Total tokens"
|
|
787
|
+
value={compactTokens(s()!.totalInputTokens ?? 0)}
|
|
788
|
+
dim
|
|
789
|
+
/>
|
|
790
|
+
</>
|
|
791
|
+
)}
|
|
792
|
+
</>
|
|
793
|
+
)}
|
|
480
794
|
</box>
|
|
481
795
|
)
|
|
482
796
|
}
|