@cortexkit/opencode-magic-context 0.31.2 → 0.31.4
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/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +16 -12
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +13 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify-prompt.d.ts +2 -2
- package/dist/features/magic-context/dreamer/classify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts +8 -0
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts +1 -0
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/manifest-parser.d.ts +7 -0
- package/dist/features/magic-context/dreamer/manifest-parser.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +2 -3
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +8 -0
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/promote-primers.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +5 -0
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts +2 -2
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +4 -0
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +12 -0
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/verification-paths.d.ts +16 -0
- package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +6 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts +1 -0
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -2
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +3 -2
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/schedule.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +17 -6
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/types.d.ts +6 -1
- package/dist/features/magic-context/smart-notes/types.d.ts.map +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 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/workspaces.d.ts +8 -0
- package/dist/features/magic-context/workspaces.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts +2 -0
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +1 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/todo-view.d.ts +27 -2
- package/dist/hooks/magic-context/todo-view.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-message-helpers.d.ts +6 -3
- package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts +1 -0
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/index.js +1046 -333
- package/dist/shared/assistant-message-extractor.d.ts +1 -0
- package/dist/shared/assistant-message-extractor.d.ts.map +1 -1
- package/dist/shared/redaction.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +21 -16
- 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-utils.d.ts +3 -1
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/types.d.ts +1 -1
- package/dist/tools/ctx-search/types.d.ts.map +1 -1
- package/dist/tui/data/notification-socket.d.ts +1 -0
- package/dist/tui/data/notification-socket.d.ts.map +1 -1
- package/dist/tui-compiled/badge-contrast.d.ts +46 -0
- package/dist/tui-compiled/badge-contrast.d.ts.map +1 -0
- package/dist/tui-compiled/data/context-db.d.ts +45 -0
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -0
- package/dist/tui-compiled/data/notification-socket.d.ts +40 -0
- package/dist/tui-compiled/data/notification-socket.d.ts.map +1 -0
- package/package.json +7 -4
- package/src/shared/assistant-message-extractor.ts +20 -0
- package/src/shared/redaction.ts +1 -0
- package/src/shared/rpc-client.test.ts +164 -5
- package/src/shared/rpc-notifications.ts +73 -21
- package/src/shared/rpc-server.ts +69 -18
- package/src/shared/rpc-utils.ts +12 -2
- package/src/tui/data/notification-socket.test.ts +131 -0
- package/src/tui/data/notification-socket.ts +81 -16
- package/src/tui/entry.mjs +36 -0
- package/src/tui-compiled/badge-contrast.ts +84 -0
- package/src/tui-compiled/data/context-db.ts +353 -0
- package/src/tui-compiled/data/notification-socket.ts +294 -0
- package/src/tui-compiled/index.tsx +1204 -0
- package/src/tui-compiled/slots/sidebar-content.tsx +1052 -0
- package/src/tui-compiled/types/opencode-plugin-tui.d.ts +232 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the text color for the sidebar header badge (a bold label drawn on a
|
|
3
|
+
* `theme.accent` background).
|
|
4
|
+
*
|
|
5
|
+
* Primary rule (matches AFT's sidebar by construction): paint the theme's own
|
|
6
|
+
* `background` color as the label, the inverse-of-panel look. Because it is a
|
|
7
|
+
* fixed theme token rather than an accent-derived computation, MC's badge and
|
|
8
|
+
* AFT's badge agree on EVERY accent automatically, so the same theme can never
|
|
9
|
+
* make one badge black and the other white (issue #198).
|
|
10
|
+
*
|
|
11
|
+
* Fallback rule (the reason a luminance pick exists at all): `theme.background`
|
|
12
|
+
* can be unusable as a label color in two degenerate cases, where it would
|
|
13
|
+
* render the label invisible on the accent:
|
|
14
|
+
* 1. Transparent background. Themes that respect terminal transparency set
|
|
15
|
+
* `background: "none"`, which resolves to `RGBA(0,0,0,0)`; drawing it as
|
|
16
|
+
* text renders fully transparent and the label disappears (issue #186).
|
|
17
|
+
* 2. Background ~= accent. If the theme's background and accent are nearly the
|
|
18
|
+
* same color, background-on-accent text has no contrast.
|
|
19
|
+
* In either case we fall back to a black/white pick that is guaranteed visible
|
|
20
|
+
* on the always-opaque accent.
|
|
21
|
+
*
|
|
22
|
+
* `RGBA` channels from @opentui/core are normalized 0..1 floats (alpha included).
|
|
23
|
+
* We accept the minimal `{ r, g, b, a? }` shape so this stays a pure, trivially
|
|
24
|
+
* testable function independent of the native color class, and we return the
|
|
25
|
+
* passed-in `background` object unchanged on the primary path so it stays the
|
|
26
|
+
* exact same theme token AFT uses.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
type Color = { r: number; g: number; b: number; a?: number };
|
|
30
|
+
|
|
31
|
+
// Below this alpha the theme background is too transparent to read as a label on
|
|
32
|
+
// the accent (issue #186: background:"none" resolves to alpha 0).
|
|
33
|
+
const MIN_OPAQUE_ALPHA = 0.5;
|
|
34
|
+
|
|
35
|
+
// If the theme background and accent are within this per-channel distance they
|
|
36
|
+
// are effectively the same color, so background-on-accent text is unreadable.
|
|
37
|
+
const MIN_CHANNEL_DISTANCE = 0.06;
|
|
38
|
+
|
|
39
|
+
// Luminance midpoint for the fallback pick: accents below this keep white text,
|
|
40
|
+
// accents at/above it (light/pastel/near-white) get black. White-biased relative
|
|
41
|
+
// to the strict equal-contrast crossover (~0.179) so saturated mid-tone accents
|
|
42
|
+
// stay white. Only consulted on the degenerate fallback path.
|
|
43
|
+
const LIGHT_ACCENT_LUMINANCE = 0.5;
|
|
44
|
+
|
|
45
|
+
function srgbChannelToLinear(c: number): number {
|
|
46
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function relativeLuminance(bg: Color): number {
|
|
50
|
+
return (
|
|
51
|
+
0.2126 * srgbChannelToLinear(bg.r) +
|
|
52
|
+
0.7152 * srgbChannelToLinear(bg.g) +
|
|
53
|
+
0.0722 * srgbChannelToLinear(bg.b)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function nearlyEqual(a: Color, b: Color): boolean {
|
|
58
|
+
return (
|
|
59
|
+
Math.abs(a.r - b.r) < MIN_CHANNEL_DISTANCE &&
|
|
60
|
+
Math.abs(a.g - b.g) < MIN_CHANNEL_DISTANCE &&
|
|
61
|
+
Math.abs(a.b - b.b) < MIN_CHANNEL_DISTANCE
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pure black/white pick by accent luminance. Used as the badge fallback and kept
|
|
67
|
+
* exported for callers that only have the accent.
|
|
68
|
+
*/
|
|
69
|
+
export function readableTextColorOn(bg: Color): string {
|
|
70
|
+
return relativeLuminance(bg) < LIGHT_ACCENT_LUMINANCE ? "#ffffff" : "#000000";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Badge label color on the accent: the theme background (AFT parity) when it is
|
|
75
|
+
* usable, else a guaranteed-visible black/white fallback. Returns the passed-in
|
|
76
|
+
* `background` reference unchanged on the primary path.
|
|
77
|
+
*/
|
|
78
|
+
export function badgeTextColor<T extends Color>(accent: T, background: T): T | string {
|
|
79
|
+
const alpha = background.a ?? 1;
|
|
80
|
+
if (alpha >= MIN_OPAQUE_ALPHA && !nearlyEqual(accent, background)) {
|
|
81
|
+
return background;
|
|
82
|
+
}
|
|
83
|
+
return readableTextColorOn(accent);
|
|
84
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI data layer — pure RPC client, no direct SQLite access.
|
|
3
|
+
* All data is fetched from the server plugin via HTTP RPC.
|
|
4
|
+
*/
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { MagicContextRpcClient } from "../../shared/rpc-client";
|
|
8
|
+
import type { EmbedDetail, SidebarSnapshot, StatusDetail } from "../../shared/rpc-types";
|
|
9
|
+
|
|
10
|
+
export type { EmbedDetail, SidebarSnapshot, StatusDetail };
|
|
11
|
+
|
|
12
|
+
let rpcClient: MagicContextRpcClient | null = null;
|
|
13
|
+
let rpcGeneration = 0;
|
|
14
|
+
|
|
15
|
+
function getStorageDir(): string {
|
|
16
|
+
// Plugin v0.16+ uses the shared cortexkit/magic-context path so OpenCode
|
|
17
|
+
// and Pi can share state. The TUI just needs to point its RPC client at
|
|
18
|
+
// the same storage directory the server plugin uses for the lock-file
|
|
19
|
+
// discovery convention.
|
|
20
|
+
const dataDir = process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
|
|
21
|
+
return path.join(dataDir, "cortexkit", "magic-context");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Initialize the RPC client. Call once on TUI startup. */
|
|
25
|
+
export function initRpcClient(directory: string): void {
|
|
26
|
+
const storageDir = getStorageDir();
|
|
27
|
+
// Bump the generation before replacing the client so late notification
|
|
28
|
+
// responses from a disposed client are ignored (the WS socket observes the
|
|
29
|
+
// new generation and abandons its in-flight connect).
|
|
30
|
+
rpcGeneration += 1;
|
|
31
|
+
rpcClient = new MagicContextRpcClient(storageDir, directory);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getRpcGeneration(): number {
|
|
35
|
+
return rpcGeneration;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The live RPC client (for the WS notification socket's endpoint discovery).
|
|
39
|
+
* Null before init / after close. */
|
|
40
|
+
export function getRpcClient(): MagicContextRpcClient | null {
|
|
41
|
+
return rpcClient;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Clean up the RPC client. */
|
|
45
|
+
export function closeRpc(): void {
|
|
46
|
+
// Closing invalidates any already-issued RPC calls; their callbacks must
|
|
47
|
+
// observe the new generation and abandon (the WS socket checks it too).
|
|
48
|
+
rpcGeneration += 1;
|
|
49
|
+
rpcClient?.reset();
|
|
50
|
+
rpcClient = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const EMPTY_SNAPSHOT: SidebarSnapshot = {
|
|
54
|
+
sessionId: "",
|
|
55
|
+
usagePercentage: 0,
|
|
56
|
+
inputTokens: 0,
|
|
57
|
+
contextLimit: 0,
|
|
58
|
+
systemPromptTokens: 0,
|
|
59
|
+
compartmentCount: 0,
|
|
60
|
+
memoryCount: 0,
|
|
61
|
+
memoryBlockCount: 0,
|
|
62
|
+
pendingOpsCount: 0,
|
|
63
|
+
historianRunning: false,
|
|
64
|
+
compartmentInProgress: false,
|
|
65
|
+
sessionNoteCount: 0,
|
|
66
|
+
readySmartNoteCount: 0,
|
|
67
|
+
cacheTtl: "5m",
|
|
68
|
+
lastDreamerRunAt: null,
|
|
69
|
+
projectIdentity: null,
|
|
70
|
+
compartmentTokens: 0,
|
|
71
|
+
factTokens: 0,
|
|
72
|
+
memoryTokens: 0,
|
|
73
|
+
docsTokens: 0,
|
|
74
|
+
profileTokens: 0,
|
|
75
|
+
conversationTokens: 0,
|
|
76
|
+
toolCallTokens: 0,
|
|
77
|
+
toolDefinitionTokens: 0,
|
|
78
|
+
executeThreshold: 65,
|
|
79
|
+
newWorkTokens: null,
|
|
80
|
+
totalInputTokens: null,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Per-session client-side sticky cache. Mirrors the server-side cache in
|
|
85
|
+
* `sidebar-snapshot-cache.ts` but covers the cases the server can't:
|
|
86
|
+
* - RPC call fails entirely (timeout, abort, parse error) → server is never reached
|
|
87
|
+
* - RPC server is not yet up (port file missing, retries exhausted)
|
|
88
|
+
* - Server returns an error envelope
|
|
89
|
+
*
|
|
90
|
+
* In all three cases the breakdown bar would otherwise disappear until the
|
|
91
|
+
* next successful refresh. With this cache, the client returns the most
|
|
92
|
+
* recent good snapshot for the same session so the UI stays stable through
|
|
93
|
+
* transient RPC blips. 5-minute staleness ceiling keeps it from showing
|
|
94
|
+
* obviously old data after long disconnects.
|
|
95
|
+
*/
|
|
96
|
+
interface CachedSnapshot {
|
|
97
|
+
snapshot: SidebarSnapshot;
|
|
98
|
+
cachedAt: number;
|
|
99
|
+
}
|
|
100
|
+
const STICKY_TTL_MS = 5 * 60 * 1000;
|
|
101
|
+
const STICKY_MAX_ENTRIES = 100;
|
|
102
|
+
const stickySidebarCache = new Map<string, CachedSnapshot>();
|
|
103
|
+
|
|
104
|
+
function rememberSidebarSnapshot(snapshot: SidebarSnapshot): void {
|
|
105
|
+
if (!snapshot.sessionId || snapshot.inputTokens <= 0) return;
|
|
106
|
+
// LRU-style bound: drop the oldest entry once we hit the cap. With a
|
|
107
|
+
// 5-min TTL most stale entries time out naturally; this just prevents
|
|
108
|
+
// unbounded growth across many session switches in a long TUI session.
|
|
109
|
+
if (
|
|
110
|
+
stickySidebarCache.size >= STICKY_MAX_ENTRIES &&
|
|
111
|
+
!stickySidebarCache.has(snapshot.sessionId)
|
|
112
|
+
) {
|
|
113
|
+
const firstKey = stickySidebarCache.keys().next().value;
|
|
114
|
+
if (firstKey) stickySidebarCache.delete(firstKey);
|
|
115
|
+
}
|
|
116
|
+
stickySidebarCache.set(snapshot.sessionId, {
|
|
117
|
+
snapshot,
|
|
118
|
+
cachedAt: Date.now(),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function recallSidebarSnapshot(sessionId: string, fallback: SidebarSnapshot): SidebarSnapshot {
|
|
123
|
+
const cached = stickySidebarCache.get(sessionId);
|
|
124
|
+
if (!cached) return fallback;
|
|
125
|
+
if (Date.now() - cached.cachedAt > STICKY_TTL_MS) {
|
|
126
|
+
stickySidebarCache.delete(sessionId);
|
|
127
|
+
return fallback;
|
|
128
|
+
}
|
|
129
|
+
if (!hasInFlightEvidence(fallback)) {
|
|
130
|
+
stickySidebarCache.delete(sessionId);
|
|
131
|
+
return fallback;
|
|
132
|
+
}
|
|
133
|
+
return cached.snapshot;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function hasInFlightEvidence(snapshot: SidebarSnapshot): boolean {
|
|
137
|
+
return (
|
|
138
|
+
snapshot.compartmentInProgress || snapshot.historianRunning || snapshot.pendingOpsCount > 0
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Fetch sidebar snapshot from the server via RPC. */
|
|
143
|
+
export async function loadSidebarSnapshot(
|
|
144
|
+
sessionId: string,
|
|
145
|
+
directory: string,
|
|
146
|
+
): Promise<SidebarSnapshot> {
|
|
147
|
+
const empty: SidebarSnapshot = { ...EMPTY_SNAPSHOT, sessionId };
|
|
148
|
+
if (!rpcClient) return recallSidebarSnapshot(sessionId, empty);
|
|
149
|
+
try {
|
|
150
|
+
const result = await rpcClient.call<SidebarSnapshot>("sidebar-snapshot", {
|
|
151
|
+
sessionId,
|
|
152
|
+
directory,
|
|
153
|
+
});
|
|
154
|
+
if ((result as unknown as Record<string, unknown>).error) {
|
|
155
|
+
return recallSidebarSnapshot(sessionId, empty);
|
|
156
|
+
}
|
|
157
|
+
// Trust successful server responses. The server has its own sticky
|
|
158
|
+
// sidebar cache (`sidebar-snapshot-cache.ts`) that handles transient
|
|
159
|
+
// zero-token windows by hybriding cached breakdown values into a
|
|
160
|
+
// fresh snapshot, AND clears that cache on `session.deleted`. If the
|
|
161
|
+
// server reaches us with `inputTokens === 0`, that's its considered
|
|
162
|
+
// answer — typically because the session was deleted, reverted, or
|
|
163
|
+
// is brand-new with no responses yet.
|
|
164
|
+
//
|
|
165
|
+
// Falling back to the client cache here would resurrect old token
|
|
166
|
+
// data for a deleted session (the client never sees `session.deleted`
|
|
167
|
+
// events, so its cache TTL is the only expiry). Sticky behavior is
|
|
168
|
+
// owned exclusively by the server side.
|
|
169
|
+
rememberSidebarSnapshot(result);
|
|
170
|
+
return result;
|
|
171
|
+
} catch {
|
|
172
|
+
return recallSidebarSnapshot(sessionId, empty);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Fetch full status detail from the server via RPC. */
|
|
177
|
+
export async function loadStatusDetail(
|
|
178
|
+
sessionId: string,
|
|
179
|
+
directory: string,
|
|
180
|
+
modelKey?: string,
|
|
181
|
+
): Promise<StatusDetail> {
|
|
182
|
+
const emptyDetail: StatusDetail = {
|
|
183
|
+
...EMPTY_SNAPSHOT,
|
|
184
|
+
sessionId,
|
|
185
|
+
tagCounter: 0,
|
|
186
|
+
activeTags: 0,
|
|
187
|
+
droppedTags: 0,
|
|
188
|
+
totalTags: 0,
|
|
189
|
+
activeBytes: 0,
|
|
190
|
+
lastResponseTime: 0,
|
|
191
|
+
lastNudgeTokens: 0,
|
|
192
|
+
lastTransformError: null,
|
|
193
|
+
isSubagent: false,
|
|
194
|
+
pendingOps: [],
|
|
195
|
+
contextLimit: 0,
|
|
196
|
+
cacheTtlMs: 0,
|
|
197
|
+
cacheRemainingMs: 0,
|
|
198
|
+
cacheExpired: false,
|
|
199
|
+
executeThreshold: 65,
|
|
200
|
+
executeThresholdMode: "percentage",
|
|
201
|
+
protectedTagCount: 20,
|
|
202
|
+
historyBudgetPercentage: 0.15,
|
|
203
|
+
historyBlockTokens: 0,
|
|
204
|
+
compressionBudget: null,
|
|
205
|
+
compressionUsage: null,
|
|
206
|
+
toastDurationMs: 5000,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
if (!rpcClient) return emptyDetail;
|
|
210
|
+
try {
|
|
211
|
+
const result = await rpcClient.call<StatusDetail>("status-detail", {
|
|
212
|
+
sessionId,
|
|
213
|
+
directory,
|
|
214
|
+
modelKey,
|
|
215
|
+
});
|
|
216
|
+
if ((result as unknown as Record<string, unknown>).error) {
|
|
217
|
+
return emptyDetail;
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
} catch {
|
|
221
|
+
return emptyDetail;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const EMPTY_EMBED_DETAIL: EmbedDetail = {
|
|
226
|
+
enabled: false,
|
|
227
|
+
model: "off",
|
|
228
|
+
provider: "off",
|
|
229
|
+
session: { embedded: 0, total: 0 },
|
|
230
|
+
memories: { embedded: 0, total: 0 },
|
|
231
|
+
commits: { embedded: 0, total: 0, gitEnabled: false },
|
|
232
|
+
statusText: "Embedding is off (no provider configured).",
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** Fetch embedding coverage status for `/ctx-embed` via RPC. */
|
|
236
|
+
export async function loadEmbedDetail(sessionId: string, directory: string): Promise<EmbedDetail> {
|
|
237
|
+
if (!rpcClient) return EMPTY_EMBED_DETAIL;
|
|
238
|
+
try {
|
|
239
|
+
const result = await rpcClient.call<EmbedDetail>("embed-detail", {
|
|
240
|
+
sessionId,
|
|
241
|
+
directory,
|
|
242
|
+
});
|
|
243
|
+
if ((result as unknown as Record<string, unknown>).error) {
|
|
244
|
+
return EMPTY_EMBED_DETAIL;
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
} catch {
|
|
248
|
+
return EMPTY_EMBED_DETAIL;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Get compartment count via RPC. */
|
|
253
|
+
export async function getCompartmentCount(sessionId: string): Promise<number> {
|
|
254
|
+
if (!rpcClient) return 0;
|
|
255
|
+
try {
|
|
256
|
+
const result = await rpcClient.call<{ count: number }>("compartment-count", { sessionId });
|
|
257
|
+
return result.count ?? 0;
|
|
258
|
+
} catch {
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Send recomp request to server via RPC. */
|
|
264
|
+
export async function requestRecomp(sessionId: string): Promise<boolean> {
|
|
265
|
+
if (!rpcClient) return false;
|
|
266
|
+
try {
|
|
267
|
+
const result = await rpcClient.call<{ ok: boolean }>("recomp", { sessionId });
|
|
268
|
+
return result.ok ?? false;
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Run `/ctx-session-upgrade` for the session (full recomp + once-per-project
|
|
275
|
+
* memory migration). Fired from the upgrade dialog's "Run upgrade now" action. */
|
|
276
|
+
export async function requestUpgrade(sessionId: string): Promise<boolean> {
|
|
277
|
+
if (!rpcClient) return false;
|
|
278
|
+
try {
|
|
279
|
+
const result = await rpcClient.call<{ ok: boolean }>("upgrade", { sessionId });
|
|
280
|
+
return result.ok ?? false;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Mark the upgrade reminder dismissed (the user made an explicit Confirm/Cancel
|
|
287
|
+
* choice), setting the durable stamp so the FRESH dialog won't re-show. Resume
|
|
288
|
+
* prompts are staging-driven and unaffected. */
|
|
289
|
+
export async function dismissUpgradeReminder(sessionId: string): Promise<boolean> {
|
|
290
|
+
if (!rpcClient) return false;
|
|
291
|
+
try {
|
|
292
|
+
const result = await rpcClient.call<{ ok: boolean }>("dismiss-upgrade-reminder", {
|
|
293
|
+
sessionId,
|
|
294
|
+
});
|
|
295
|
+
return result.ok ?? false;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Resolve global toast duration from server config via RPC. */
|
|
302
|
+
export async function loadToastDurationMs(): Promise<number> {
|
|
303
|
+
if (!rpcClient) return 5000;
|
|
304
|
+
try {
|
|
305
|
+
const result = await rpcClient.call<{ toastDurationMs?: number }>("toast-duration", {});
|
|
306
|
+
return typeof result.toastDurationMs === "number" ? result.toastDurationMs : 5000;
|
|
307
|
+
} catch {
|
|
308
|
+
return 5000;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Fetch the current startup announcement from the server, if any.
|
|
314
|
+
* Returns `{show: false}` when there's nothing to announce or when the
|
|
315
|
+
* configured ANNOUNCEMENT_VERSION has already been dismissed.
|
|
316
|
+
*/
|
|
317
|
+
export interface AnnouncementResponse {
|
|
318
|
+
show: boolean;
|
|
319
|
+
version?: string;
|
|
320
|
+
features?: string[];
|
|
321
|
+
footer?: string;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function getAnnouncement(): Promise<AnnouncementResponse> {
|
|
325
|
+
if (!rpcClient) return { show: false };
|
|
326
|
+
try {
|
|
327
|
+
const result = await rpcClient.call<{
|
|
328
|
+
show?: boolean;
|
|
329
|
+
version?: string;
|
|
330
|
+
features?: string[];
|
|
331
|
+
footer?: string;
|
|
332
|
+
}>("get-announcement", {});
|
|
333
|
+
return {
|
|
334
|
+
show: result.show === true,
|
|
335
|
+
version: result.version,
|
|
336
|
+
features: Array.isArray(result.features) ? result.features : undefined,
|
|
337
|
+
footer: typeof result.footer === "string" ? result.footer : undefined,
|
|
338
|
+
};
|
|
339
|
+
} catch {
|
|
340
|
+
return { show: false };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Mark the current ANNOUNCEMENT_VERSION as dismissed on the server. */
|
|
345
|
+
export async function markAnnounced(): Promise<boolean> {
|
|
346
|
+
if (!rpcClient) return false;
|
|
347
|
+
try {
|
|
348
|
+
const result = await rpcClient.call<{ ok?: boolean }>("mark-announced", {});
|
|
349
|
+
return result.ok === true;
|
|
350
|
+
} catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|