@cortexkit/opencode-magic-context 0.31.5 → 0.32.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.
- package/README.md +3 -3
- package/dist/agents/magic-context-prompt.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +2 -0
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +9 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/cron.d.ts +2 -2
- package/dist/features/magic-context/dreamer/cron.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts +6 -2
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +7 -4
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +8 -16
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/plugin-messages.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-backfill.d.ts +6 -3
- package/dist/features/magic-context/session-project-backfill.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/runner.d.ts +1 -0
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +8 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +10 -0
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/storage.d.ts +16 -1
- package/dist/features/magic-context/smart-notes/storage.d.ts.map +1 -1
- package/dist/features/magic-context/storage-clone.d.ts +34 -0
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -0
- package/dist/features/magic-context/storage-db.d.ts +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +8 -5
- 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-schema-helpers.d.ts +3 -4
- package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +11 -0
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +2 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/tagger.d.ts +10 -0
- package/dist/features/magic-context/tagger.d.ts.map +1 -1
- package/dist/features/magic-context/tool-owner-backfill.d.ts +2 -0
- package/dist/features/magic-context/tool-owner-backfill.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts +24 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts +12 -0
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -0
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +7 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/decay-render.d.ts +2 -0
- package/dist/hooks/magic-context/decay-render.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +1 -0
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +29 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts +1 -1
- package/dist/hooks/magic-context/historian-prompt.generated.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +3 -0
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +8 -9
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts +5 -4
- package/dist/hooks/magic-context/m0-token-breakdown.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts +17 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +2 -2
- package/dist/hooks/magic-context/read-session-raw.d.ts +32 -2
- package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
- package/dist/hooks/magic-context/shadow-sender.d.ts +300 -0
- package/dist/hooks/magic-context/shadow-sender.d.ts.map +1 -0
- package/dist/hooks/magic-context/tag-content-primitives.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +8 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-awareness.d.ts +3 -3
- package/dist/hooks/magic-context/transform-operations.d.ts +1 -1
- package/dist/hooks/magic-context/transform-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +32 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +4 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5418 -972
- package/dist/plugin/hooks/create-session-hooks.d.ts +48 -175
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +3 -0
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts +5 -0
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +3 -1
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +11 -6
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +2 -2
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/tag-transcript.d.ts +18 -0
- package/dist/shared/tag-transcript.d.ts.map +1 -1
- package/dist/shared/tui-config.d.ts +5 -3
- package/dist/shared/tui-config.d.ts.map +1 -1
- package/dist/tools/ctx-expand/constants.d.ts +1 -1
- package/dist/tools/ctx-expand/constants.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +9 -2
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui/data/notification-socket.d.ts +4 -5
- package/dist/tui/data/notification-socket.d.ts.map +1 -1
- package/dist/tui-compiled/data/context-db.d.ts +9 -2
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/data/notification-socket.d.ts +4 -5
- package/dist/tui-compiled/data/notification-socket.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/shared/models-dev-cache.test.ts +5 -0
- package/src/shared/models-dev-cache.ts +44 -6
- package/src/shared/opencode-compaction-detector.test.ts +11 -2
- package/src/shared/rpc-client.test.ts +213 -9
- package/src/shared/rpc-client.ts +48 -39
- package/src/shared/rpc-notifications.test.ts +26 -5
- package/src/shared/rpc-notifications.ts +44 -35
- package/src/shared/rpc-server.ts +77 -33
- package/src/shared/sqlite.ts +22 -3
- package/src/shared/tag-transcript.test.ts +253 -7
- package/src/shared/tag-transcript.ts +422 -114
- package/src/shared/tui-config.test.ts +4 -12
- package/src/shared/tui-config.ts +6 -6
- package/src/tui/data/context-db.test.ts +113 -0
- package/src/tui/data/context-db.ts +26 -19
- package/src/tui/data/notification-socket.test.ts +277 -11
- package/src/tui/data/notification-socket.ts +257 -86
- package/src/tui/index.tsx +33 -25
- package/src/tui/slots/sidebar-content.tsx +62 -24
- package/src/tui-compiled/data/context-db.ts +26 -19
- package/src/tui-compiled/data/notification-socket.ts +257 -86
- package/src/tui-compiled/index.tsx +41 -24
- package/src/tui-compiled/slots/sidebar-content.tsx +45 -23
|
@@ -33,6 +33,7 @@ export interface SidebarController {
|
|
|
33
33
|
prefs: () => MagicContextTuiPrefs
|
|
34
34
|
collapsed: () => boolean
|
|
35
35
|
toggleCollapsed: () => void
|
|
36
|
+
dispose: () => void
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
// The TUI may unmount and remount sidebar_content when the user switches views
|
|
@@ -52,11 +53,10 @@ function createSidebarController(initialPrefs: MagicContextTuiPrefs): SidebarCon
|
|
|
52
53
|
let lastPersistedCollapsed: boolean | null = initialPrefs.collapsed
|
|
53
54
|
let lastApplied = JSON.stringify(initialPrefs)
|
|
54
55
|
|
|
55
|
-
// Watcher lives for the process lifetime — intentionally never disposed.
|
|
56
56
|
// Collapse echo guard: lastPersistedCollapsed advances only once our own
|
|
57
57
|
// write lands, so a watcher echo of the value we just wrote is rejected by
|
|
58
58
|
// the `!==` check and cannot revert a user click.
|
|
59
|
-
watchTuiPreferences(() => {
|
|
59
|
+
const stopWatchingPreferences = watchTuiPreferences(() => {
|
|
60
60
|
void (async () => {
|
|
61
61
|
const next = resolveMagicContextPrefs(await readTuiPreferencesFile())
|
|
62
62
|
const serialized = JSON.stringify(next)
|
|
@@ -84,7 +84,12 @@ function createSidebarController(initialPrefs: MagicContextTuiPrefs): SidebarCon
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
prefs,
|
|
89
|
+
collapsed,
|
|
90
|
+
toggleCollapsed,
|
|
91
|
+
dispose: stopWatchingPreferences,
|
|
92
|
+
}
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
function compactTokens(value: number): string {
|
|
@@ -490,6 +495,8 @@ const SidebarContent = (props: {
|
|
|
490
495
|
let recompSawPhase = false
|
|
491
496
|
let recompPollCount = 0
|
|
492
497
|
let recompConsecutiveAbsent = 0
|
|
498
|
+
let recompSessionId: string | null = null
|
|
499
|
+
let snapshotRequestSequence = 0
|
|
493
500
|
const RECOMP_PROBE_MAX = 12 // ~15s for the server's "Starting…" to land
|
|
494
501
|
// After we've SEEN an active phase, a momentarily absent snapshot is almost
|
|
495
502
|
// always transient — the server's sticky cache serves a pre-recomp snapshot
|
|
@@ -505,13 +512,14 @@ const SidebarContent = (props: {
|
|
|
505
512
|
const refresh = () => {
|
|
506
513
|
const sid = props.sessionID()
|
|
507
514
|
if (!sid) return
|
|
515
|
+
const sequence = ++snapshotRequestSequence
|
|
508
516
|
const directory = props.api.state.path.directory ?? ""
|
|
509
517
|
void loadSidebarSnapshot(sid, directory)
|
|
510
518
|
.then((data) => {
|
|
511
519
|
// Guard against a session switch while this load was in flight:
|
|
512
520
|
// painting session A's snapshot into the now-active session B shows
|
|
513
521
|
// the wrong session's numbers until B's own refresh resolves.
|
|
514
|
-
if (props.sessionID() !== sid) return
|
|
522
|
+
if (props.sessionID() !== sid || sequence !== snapshotRequestSequence) return
|
|
515
523
|
setSnapshot(data)
|
|
516
524
|
try {
|
|
517
525
|
props.api.renderer.requestRender()
|
|
@@ -524,11 +532,16 @@ const SidebarContent = (props: {
|
|
|
524
532
|
const phase = data?.recompProgress?.phase
|
|
525
533
|
if ((phase === "recomp" || phase === "migration") && !recompActive) {
|
|
526
534
|
kickRecompPoll()
|
|
535
|
+
} else if (recompActive && recompSessionId === sid) {
|
|
536
|
+
scheduleRecompTick()
|
|
527
537
|
}
|
|
528
538
|
})
|
|
529
539
|
.catch(() => {
|
|
530
|
-
// one-shot refresh failure is non-fatal
|
|
531
|
-
//
|
|
540
|
+
// A one-shot refresh failure is non-fatal. If it superseded a fast
|
|
541
|
+
// poll request, keep that poll moving for the captured session.
|
|
542
|
+
if (recompActive && recompSessionId === sid && props.sessionID() === sid) {
|
|
543
|
+
scheduleRecompTick()
|
|
544
|
+
}
|
|
532
545
|
})
|
|
533
546
|
}
|
|
534
547
|
|
|
@@ -540,6 +553,14 @@ const SidebarContent = (props: {
|
|
|
540
553
|
}, REFRESH_DEBOUNCE_MS)
|
|
541
554
|
}
|
|
542
555
|
|
|
556
|
+
const stopRecompPoll = () => {
|
|
557
|
+
recompActive = false
|
|
558
|
+
recompSessionId = null
|
|
559
|
+
snapshotRequestSequence += 1
|
|
560
|
+
if (recompPollTimer) clearTimeout(recompPollTimer)
|
|
561
|
+
recompPollTimer = undefined
|
|
562
|
+
}
|
|
563
|
+
|
|
543
564
|
const scheduleRecompTick = () => {
|
|
544
565
|
if (!recompActive) return
|
|
545
566
|
if (recompPollTimer) clearTimeout(recompPollTimer)
|
|
@@ -547,20 +568,26 @@ const SidebarContent = (props: {
|
|
|
547
568
|
}
|
|
548
569
|
|
|
549
570
|
function recompTick(): void {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
recompActive = false
|
|
571
|
+
const sid = recompSessionId
|
|
572
|
+
if (!recompActive || !sid || props.sessionID() !== sid) {
|
|
573
|
+
stopRecompPoll()
|
|
554
574
|
return
|
|
555
575
|
}
|
|
556
|
-
|
|
557
|
-
if (
|
|
558
|
-
|
|
576
|
+
recompPollCount += 1
|
|
577
|
+
if (recompPollCount > RECOMP_MAX_POLLS) {
|
|
578
|
+
stopRecompPoll()
|
|
559
579
|
return
|
|
560
580
|
}
|
|
581
|
+
const sequence = ++snapshotRequestSequence
|
|
561
582
|
const directory = props.api.state.path.directory ?? ""
|
|
562
583
|
void loadSidebarSnapshot(sid, directory)
|
|
563
584
|
.then((data) => {
|
|
585
|
+
if (
|
|
586
|
+
!recompActive ||
|
|
587
|
+
recompSessionId !== sid ||
|
|
588
|
+
props.sessionID() !== sid ||
|
|
589
|
+
sequence !== snapshotRequestSequence
|
|
590
|
+
) return
|
|
564
591
|
const phase = data?.recompProgress?.phase
|
|
565
592
|
// While a recomp is known-active, a transient snapshot that lost
|
|
566
593
|
// recompProgress (sticky cache / busy-DB empty) must NOT wipe the
|
|
@@ -585,7 +612,7 @@ const SidebarContent = (props: {
|
|
|
585
612
|
// Terminal state rendered — stop. The server keeps "done"/
|
|
586
613
|
// "skipped" for a grace window and "failed" until the next run,
|
|
587
614
|
// so the outcome stays visible without further polling.
|
|
588
|
-
|
|
615
|
+
stopRecompPoll()
|
|
589
616
|
} else {
|
|
590
617
|
// Phase absent this poll.
|
|
591
618
|
recompConsecutiveAbsent += 1
|
|
@@ -593,7 +620,7 @@ const SidebarContent = (props: {
|
|
|
593
620
|
// Still waiting for the server's first "Starting…".
|
|
594
621
|
if (recompPollCount < RECOMP_PROBE_MAX) scheduleRecompTick()
|
|
595
622
|
else {
|
|
596
|
-
|
|
623
|
+
stopRecompPoll()
|
|
597
624
|
}
|
|
598
625
|
} else if (recompConsecutiveAbsent < RECOMP_ABSENT_GIVEUP) {
|
|
599
626
|
// Seen it active — absent is almost certainly the sticky
|
|
@@ -603,14 +630,19 @@ const SidebarContent = (props: {
|
|
|
603
630
|
scheduleRecompTick()
|
|
604
631
|
} else {
|
|
605
632
|
// Long continuous absence — the entry is genuinely gone.
|
|
606
|
-
|
|
633
|
+
stopRecompPoll()
|
|
607
634
|
}
|
|
608
635
|
}
|
|
609
636
|
})
|
|
610
|
-
.catch((
|
|
611
|
-
//
|
|
612
|
-
//
|
|
613
|
-
|
|
637
|
+
.catch(() => {
|
|
638
|
+
// A failed fetch must not kill the loop, but a response from a
|
|
639
|
+
// superseded session or sequence must not restart it either.
|
|
640
|
+
if (
|
|
641
|
+
recompActive &&
|
|
642
|
+
recompSessionId === sid &&
|
|
643
|
+
props.sessionID() === sid &&
|
|
644
|
+
sequence === snapshotRequestSequence
|
|
645
|
+
) scheduleRecompTick()
|
|
614
646
|
})
|
|
615
647
|
}
|
|
616
648
|
|
|
@@ -618,8 +650,12 @@ const SidebarContent = (props: {
|
|
|
618
650
|
// first detects an active recomp). The server emits an immediate "Starting…"
|
|
619
651
|
// entry; the probe window covers the brief RPC race before it lands.
|
|
620
652
|
function kickRecompPoll(): void {
|
|
621
|
-
|
|
653
|
+
const sid = props.sessionID()
|
|
654
|
+
if (!sid) return
|
|
655
|
+
if (recompActive && recompSessionId === sid) return
|
|
656
|
+
stopRecompPoll()
|
|
622
657
|
recompActive = true
|
|
658
|
+
recompSessionId = sid
|
|
623
659
|
recompSawPhase = false
|
|
624
660
|
recompPollCount = 0
|
|
625
661
|
recompConsecutiveAbsent = 0
|
|
@@ -630,14 +666,15 @@ const SidebarContent = (props: {
|
|
|
630
666
|
|
|
631
667
|
onCleanup(() => {
|
|
632
668
|
if (refreshTimer) clearTimeout(refreshTimer)
|
|
633
|
-
|
|
634
|
-
recompActive = false
|
|
669
|
+
stopRecompPoll()
|
|
635
670
|
if (activeRecompPollKick === kickRecompPoll) activeRecompPollKick = null
|
|
636
671
|
})
|
|
637
672
|
|
|
638
673
|
// Refresh on session change
|
|
639
674
|
createEffect(
|
|
640
675
|
on(props.sessionID, () => {
|
|
676
|
+
stopRecompPoll()
|
|
677
|
+
setSnapshot(null)
|
|
641
678
|
refresh()
|
|
642
679
|
}),
|
|
643
680
|
)
|
|
@@ -893,7 +930,7 @@ const SidebarContent = (props: {
|
|
|
893
930
|
)
|
|
894
931
|
}
|
|
895
932
|
|
|
896
|
-
export function createSidebarContentSlot(api: TuiPluginApi): TuiSlotPlugin {
|
|
933
|
+
export function createSidebarContentSlot(api: TuiPluginApi): TuiSlotPlugin & { dispose: () => void } {
|
|
897
934
|
// Seed synchronously at slot construction so the sidebar renders at its
|
|
898
935
|
// final collapse state + order on the first paint (no async flicker). The
|
|
899
936
|
// controller lives here in the factory closure for the plugin lifetime, so
|
|
@@ -903,6 +940,7 @@ export function createSidebarContentSlot(api: TuiPluginApi): TuiSlotPlugin {
|
|
|
903
940
|
const effectiveOrder = computeEffectiveOrder(seedRoot, PLUGIN_KEY, DEFAULT_SLOT_ORDER)
|
|
904
941
|
return {
|
|
905
942
|
order: effectiveOrder,
|
|
943
|
+
dispose: controller.dispose,
|
|
906
944
|
slots: {
|
|
907
945
|
sidebar_content: (ctx, value) => {
|
|
908
946
|
const theme = createMemo(() => ctx.theme.current)
|
|
@@ -102,7 +102,13 @@ const STICKY_MAX_ENTRIES = 100;
|
|
|
102
102
|
const stickySidebarCache = new Map<string, CachedSnapshot>();
|
|
103
103
|
|
|
104
104
|
function rememberSidebarSnapshot(snapshot: SidebarSnapshot): void {
|
|
105
|
-
if (!snapshot.sessionId
|
|
105
|
+
if (!snapshot.sessionId) return;
|
|
106
|
+
if (snapshot.inputTokens <= 0) {
|
|
107
|
+
// A successful zero is authoritative (new/deleted/reverted session) and
|
|
108
|
+
// must prevent a later transport failure from resurrecting old values.
|
|
109
|
+
stickySidebarCache.delete(snapshot.sessionId);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
106
112
|
// LRU-style bound: drop the oldest entry once we hit the cap. With a
|
|
107
113
|
// 5-min TTL most stale entries time out naturally; this just prevents
|
|
108
114
|
// unbounded growth across many session switches in a long TUI session.
|
|
@@ -126,19 +132,9 @@ function recallSidebarSnapshot(sessionId: string, fallback: SidebarSnapshot): Si
|
|
|
126
132
|
stickySidebarCache.delete(sessionId);
|
|
127
133
|
return fallback;
|
|
128
134
|
}
|
|
129
|
-
if (!hasInFlightEvidence(fallback)) {
|
|
130
|
-
stickySidebarCache.delete(sessionId);
|
|
131
|
-
return fallback;
|
|
132
|
-
}
|
|
133
135
|
return cached.snapshot;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
function hasInFlightEvidence(snapshot: SidebarSnapshot): boolean {
|
|
137
|
-
return (
|
|
138
|
-
snapshot.compartmentInProgress || snapshot.historianRunning || snapshot.pendingOpsCount > 0
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
138
|
/** Fetch sidebar snapshot from the server via RPC. */
|
|
143
139
|
export async function loadSidebarSnapshot(
|
|
144
140
|
sessionId: string,
|
|
@@ -152,9 +148,11 @@ export async function loadSidebarSnapshot(
|
|
|
152
148
|
directory,
|
|
153
149
|
});
|
|
154
150
|
if ((result as unknown as Record<string, unknown>).error) {
|
|
151
|
+
// Snapshot-build errors are explicit failure envelopes, equivalent to
|
|
152
|
+
// a transport failure: retain the last known-good client snapshot.
|
|
155
153
|
return recallSidebarSnapshot(sessionId, empty);
|
|
156
154
|
}
|
|
157
|
-
// Trust successful server responses. The server has its own sticky
|
|
155
|
+
// Trust successful server responses, including authoritative zeroes. The server has its own sticky
|
|
158
156
|
// sidebar cache (`sidebar-snapshot-cache.ts`) that handles transient
|
|
159
157
|
// zero-token windows by hybriding cached breakdown values into a
|
|
160
158
|
// fresh snapshot, AND clears that cache on `session.deleted`. If the
|
|
@@ -249,14 +247,23 @@ export async function loadEmbedDetail(sessionId: string, directory: string): Pro
|
|
|
249
247
|
}
|
|
250
248
|
}
|
|
251
249
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
export type CompartmentCountResult = { ok: true; count: number } | { ok: false; error: string };
|
|
251
|
+
|
|
252
|
+
/** Get compartment count without making transport failure look like a real zero. */
|
|
253
|
+
export async function getCompartmentCount(sessionId: string): Promise<CompartmentCountResult> {
|
|
254
|
+
if (!rpcClient) return { ok: false, error: "RPC client is not initialized" };
|
|
255
255
|
try {
|
|
256
|
-
const result = await rpcClient.call<{ count
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
256
|
+
const result = await rpcClient.call<{ count?: number; error?: string }>(
|
|
257
|
+
"compartment-count",
|
|
258
|
+
{ sessionId },
|
|
259
|
+
);
|
|
260
|
+
if (typeof result.error === "string") return { ok: false, error: result.error };
|
|
261
|
+
if (typeof result.count !== "number" || !Number.isFinite(result.count)) {
|
|
262
|
+
return { ok: false, error: "Invalid compartment count response" };
|
|
263
|
+
}
|
|
264
|
+
return { ok: true, count: result.count };
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
260
267
|
}
|
|
261
268
|
}
|
|
262
269
|
|