@cortexkit/aft-opencode 0.45.0 → 0.46.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/dist/config.d.ts +0 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/types.d.ts +0 -3
- package/dist/hooks/auto-update-checker/types.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +347 -127
- package/dist/lsp-github-table.d.ts +0 -8
- package/dist/lsp-github-table.d.ts.map +1 -1
- package/dist/notifications.d.ts +2 -9
- package/dist/notifications.d.ts.map +1 -1
- package/dist/shared/ignored-message.d.ts +9 -7
- package/dist/shared/ignored-message.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +23 -1
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/subc-tool-schemas.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +0 -7
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/permissions.d.ts +7 -0
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tui/notification-socket.d.ts.map +1 -1
- package/package.json +17 -14
- package/src/shared/ignored-message.ts +38 -19
- package/src/shared/rpc-client.ts +116 -4
- package/src/tui/entry.mjs +16 -0
- package/src/tui/notification-socket.ts +28 -1
- package/src/tui-compiled/badge-contrast.ts +43 -0
- package/src/tui-compiled/index.tsx +992 -0
- package/src/tui-compiled/notification-socket.ts +448 -0
- package/src/tui-compiled/preferences.ts +243 -0
- package/src/tui-compiled/sidebar.tsx +936 -0
- package/src/tui-compiled/types/opencode-plugin-tui.d.ts +239 -0
- package/dist/tui.js +0 -13462
|
@@ -0,0 +1,992 @@
|
|
|
1
|
+
import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
2
|
+
import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
3
|
+
import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
4
|
+
import { effect as _$effect } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
5
|
+
import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
6
|
+
import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
7
|
+
import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
8
|
+
import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
9
|
+
/** @jsxImportSource @opentui/solid */
|
|
10
|
+
// @ts-nocheck
|
|
11
|
+
|
|
12
|
+
import { createMemo, createSignal, onCleanup } from "opentui:runtime-module:solid-js";
|
|
13
|
+
import { version as packageVersion } from "../../package.json";
|
|
14
|
+
import { AftRpcClient } from "../shared/rpc-client";
|
|
15
|
+
import { coerceAftStatus, formatBytes, formatSemanticIndexStatus, formatSemanticRefreshing } from "../shared/status";
|
|
16
|
+
import { createDebouncedStatusRefresh, refreshAftTuiSocketScope, startAftTuiSocket, stopAftTuiSocket, subscribeStatusInvalidations } from "./notification-socket";
|
|
17
|
+
import { createAftSidebarSlot, formatCompressionSidebarRows, isSnapshotForContext, resolveTuiStorageDir, shouldSuppressUninitializedDowngrade } from "./sidebar";
|
|
18
|
+
|
|
19
|
+
// The TUI talks to the server plugin via AftRpcClient. The client reads the
|
|
20
|
+
// JSON port file written by AftRpcServer ({ port, token }) and includes that
|
|
21
|
+
// per-server token on every RPC request; legacy integer port files are still
|
|
22
|
+
// tolerated for already-running older server plugins.
|
|
23
|
+
|
|
24
|
+
// RPC clients keyed by directory — one per project
|
|
25
|
+
const rpcClients = new Map();
|
|
26
|
+
function getRpcClient(directory) {
|
|
27
|
+
let client = rpcClients.get(directory);
|
|
28
|
+
if (client) return client;
|
|
29
|
+
client = new AftRpcClient(resolveTuiStorageDir(), directory);
|
|
30
|
+
rpcClients.set(directory, client);
|
|
31
|
+
return client;
|
|
32
|
+
}
|
|
33
|
+
function getSessionId(api) {
|
|
34
|
+
try {
|
|
35
|
+
const route = api.route.current;
|
|
36
|
+
if (route?.name === "session" && route.params?.sessionID) {
|
|
37
|
+
return route.params.sessionID;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// ignore
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// StatusDialog — themed, two-column JSX dialog. OpenCode renders DialogAlert
|
|
47
|
+
// text in a proportional font with no column alignment, so the status view uses
|
|
48
|
+
// TUI flex primitives instead of a padded monospace string. The component
|
|
49
|
+
// subscribes to server-pushed invalidations so it can re-render as the status
|
|
50
|
+
// snapshot changes, with no parent re-mount.
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
function formatCountShort(value) {
|
|
54
|
+
if (value == null || !Number.isFinite(value)) return "—";
|
|
55
|
+
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
56
|
+
if (value >= 1_000) return `${Math.round(value / 1_000)}K`;
|
|
57
|
+
return String(value);
|
|
58
|
+
}
|
|
59
|
+
function statusTone(status) {
|
|
60
|
+
switch (status) {
|
|
61
|
+
case "ready":
|
|
62
|
+
return "ok";
|
|
63
|
+
case "loading":
|
|
64
|
+
case "building":
|
|
65
|
+
return "warn";
|
|
66
|
+
case "failed":
|
|
67
|
+
case "error":
|
|
68
|
+
return "err";
|
|
69
|
+
default:
|
|
70
|
+
return "muted";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function pickToneColor(theme, tone) {
|
|
74
|
+
switch (tone) {
|
|
75
|
+
case "ok":
|
|
76
|
+
return theme.success ?? theme.accent;
|
|
77
|
+
case "warn":
|
|
78
|
+
return theme.warning;
|
|
79
|
+
case "err":
|
|
80
|
+
return theme.error;
|
|
81
|
+
case "muted":
|
|
82
|
+
return theme.textMuted;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Label/value row. Label is left-aligned and muted; value is right-aligned
|
|
88
|
+
* and themed. flexDirection="row" + justifyContent="space-between" replaces
|
|
89
|
+
* the monospace `padEnd(40)` hack from the previous string formatter.
|
|
90
|
+
*/
|
|
91
|
+
const R = props => {
|
|
92
|
+
const fg = createMemo(() => {
|
|
93
|
+
if (!props.tone) return props.theme.text;
|
|
94
|
+
if (props.tone === "accent") return props.theme.accent;
|
|
95
|
+
return pickToneColor(props.theme, props.tone);
|
|
96
|
+
});
|
|
97
|
+
return (() => {
|
|
98
|
+
var _el$ = _$createElement("box"),
|
|
99
|
+
_el$2 = _$createElement("text"),
|
|
100
|
+
_el$3 = _$createElement("text");
|
|
101
|
+
_$insertNode(_el$, _el$2);
|
|
102
|
+
_$insertNode(_el$, _el$3);
|
|
103
|
+
_$setProp(_el$, "flexDirection", "row");
|
|
104
|
+
_$setProp(_el$, "width", "100%");
|
|
105
|
+
_$setProp(_el$, "justifyContent", "space-between");
|
|
106
|
+
_$insert(_el$2, () => props.label);
|
|
107
|
+
_$insert(_el$3, () => props.value);
|
|
108
|
+
_$effect(_p$ => {
|
|
109
|
+
var _v$ = props.theme.textMuted,
|
|
110
|
+
_v$2 = fg();
|
|
111
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
|
|
112
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$3, "fg", _v$2, _p$.t));
|
|
113
|
+
return _p$;
|
|
114
|
+
}, {
|
|
115
|
+
e: undefined,
|
|
116
|
+
t: undefined
|
|
117
|
+
});
|
|
118
|
+
return _el$;
|
|
119
|
+
})();
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Shared accept-gate for status RPC calls: skip warm responses that describe
|
|
123
|
+
* another project (cross-project contamination from multi-project hosts —
|
|
124
|
+
* see isSnapshotForContext) so they can't beat the right server's response.
|
|
125
|
+
*/
|
|
126
|
+
function statusAcceptGate(directory) {
|
|
127
|
+
return result => {
|
|
128
|
+
const rec = result;
|
|
129
|
+
if (rec?.success === false) return true; // error envelopes handled by callers
|
|
130
|
+
return isSnapshotForContext(coerceAftStatus(rec), directory, rec?.served_directory);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const StatusDialog = props => {
|
|
134
|
+
const theme = createMemo(() => props.api.theme.current);
|
|
135
|
+
const t = () => theme();
|
|
136
|
+
|
|
137
|
+
// Reactive status signal — the dialog re-renders on pushed status
|
|
138
|
+
// invalidations without remounting.
|
|
139
|
+
const [status, setStatus] = createSignal(props.initial);
|
|
140
|
+
const [error, setError] = createSignal(props.initialError);
|
|
141
|
+
let refreshGeneration = 0;
|
|
142
|
+
let refreshController = null;
|
|
143
|
+
const refreshStatus = async () => {
|
|
144
|
+
if (refreshController) return;
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const requestGeneration = ++refreshGeneration;
|
|
147
|
+
refreshController = controller;
|
|
148
|
+
try {
|
|
149
|
+
const response = await props.client.call("status", {
|
|
150
|
+
sessionID: props.sessionID
|
|
151
|
+
}, {
|
|
152
|
+
signal: controller.signal,
|
|
153
|
+
accept: statusAcceptGate(props.directory)
|
|
154
|
+
});
|
|
155
|
+
if (controller.signal.aborted || requestGeneration !== refreshGeneration) return;
|
|
156
|
+
if (response.success !== false) {
|
|
157
|
+
const snapshot = coerceAftStatus(response);
|
|
158
|
+
// Stale-while-revalidate: don't downgrade a good snapshot to a transient
|
|
159
|
+
// `not_initialized` (for example, while the bridge process respawns or
|
|
160
|
+
// a session directory lookup briefly misses) — it arrives as success:true
|
|
161
|
+
// and would blank the dialog until the next refresh.
|
|
162
|
+
const current = status();
|
|
163
|
+
if (shouldSuppressUninitializedDowngrade(snapshot.cache_role, current !== null && current.cache_role !== "not_initialized")) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
setStatus(snapshot);
|
|
167
|
+
setError(null);
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
if (controller.signal.aborted || requestGeneration !== refreshGeneration) return;
|
|
171
|
+
// transient — keep showing last good snapshot
|
|
172
|
+
} finally {
|
|
173
|
+
if (refreshController === controller) refreshController = null;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const statusDebouncer = createDebouncedStatusRefresh(refreshStatus, 200);
|
|
177
|
+
const unsubscribeStatusInvalidations = subscribeStatusInvalidations(event => {
|
|
178
|
+
if (event.sessionId && event.sessionId !== props.sessionID) return;
|
|
179
|
+
statusDebouncer.schedule();
|
|
180
|
+
});
|
|
181
|
+
onCleanup(() => {
|
|
182
|
+
unsubscribeStatusInvalidations();
|
|
183
|
+
statusDebouncer.dispose();
|
|
184
|
+
refreshGeneration++;
|
|
185
|
+
if (refreshController) {
|
|
186
|
+
refreshController.abort();
|
|
187
|
+
refreshController = null;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Visual cache-role badge: main is accent, worktree is warning,
|
|
192
|
+
// not_initialized is muted. Matches the sidebar convention.
|
|
193
|
+
const cacheRoleTone = role => role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
|
|
194
|
+
// Reuse the sidebar's label/value formatter so the dialog and sidebar
|
|
195
|
+
// render identical text (e.g. "Session" / "-174,489 tokens, 59% reduction").
|
|
196
|
+
// The earlier `formatCompressionDialogRows` returned padded strings that
|
|
197
|
+
// looked offset against neighboring sections.
|
|
198
|
+
const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
|
|
199
|
+
return (() => {
|
|
200
|
+
var _el$4 = _$createElement("box"),
|
|
201
|
+
_el$5 = _$createElement("box"),
|
|
202
|
+
_el$6 = _$createElement("text"),
|
|
203
|
+
_el$7 = _$createElement("b"),
|
|
204
|
+
_el$9 = _$createElement("box"),
|
|
205
|
+
_el$0 = _$createElement("text");
|
|
206
|
+
_$insertNode(_el$4, _el$5);
|
|
207
|
+
_$insertNode(_el$4, _el$9);
|
|
208
|
+
_$setProp(_el$4, "flexDirection", "column");
|
|
209
|
+
_$setProp(_el$4, "width", "100%");
|
|
210
|
+
_$setProp(_el$4, "paddingLeft", 2);
|
|
211
|
+
_$setProp(_el$4, "paddingRight", 2);
|
|
212
|
+
_$setProp(_el$4, "paddingTop", 1);
|
|
213
|
+
_$setProp(_el$4, "paddingBottom", 1);
|
|
214
|
+
_$insertNode(_el$5, _el$6);
|
|
215
|
+
_$setProp(_el$5, "justifyContent", "center");
|
|
216
|
+
_$setProp(_el$5, "width", "100%");
|
|
217
|
+
_$setProp(_el$5, "marginBottom", 1);
|
|
218
|
+
_$setProp(_el$5, "flexDirection", "row");
|
|
219
|
+
_$setProp(_el$5, "gap", 2);
|
|
220
|
+
_$insertNode(_el$6, _el$7);
|
|
221
|
+
_$insertNode(_el$7, _$createTextNode(`⚡ AFT Status`));
|
|
222
|
+
_$insert(_el$5, (() => {
|
|
223
|
+
var _c$ = _$memo(() => status()?.cache_role !== "not_initialized");
|
|
224
|
+
return () => _c$() && (() => {
|
|
225
|
+
var _el$10 = _$createElement("text"),
|
|
226
|
+
_el$11 = _$createTextNode(`v`);
|
|
227
|
+
_$insertNode(_el$10, _el$11);
|
|
228
|
+
_$insert(_el$10, () => status()?.version ?? packageVersion, null);
|
|
229
|
+
_$effect(_$p => _$setProp(_el$10, "fg", t().textMuted, _$p));
|
|
230
|
+
return _el$10;
|
|
231
|
+
})();
|
|
232
|
+
})(), null);
|
|
233
|
+
_$insert(_el$4, (() => {
|
|
234
|
+
var _c$2 = _$memo(() => !!error());
|
|
235
|
+
return () => _c$2() ? (() => {
|
|
236
|
+
var _el$12 = _$createElement("box"),
|
|
237
|
+
_el$13 = _$createElement("text");
|
|
238
|
+
_$insertNode(_el$12, _el$13);
|
|
239
|
+
_$setProp(_el$12, "width", "100%");
|
|
240
|
+
_$setProp(_el$12, "marginBottom", 1);
|
|
241
|
+
_$insert(_el$13, error);
|
|
242
|
+
_$effect(_$p => _$setProp(_el$13, "fg", t().warning, _$p));
|
|
243
|
+
return _el$12;
|
|
244
|
+
})() : null;
|
|
245
|
+
})(), _el$9);
|
|
246
|
+
_$insert(_el$4, (() => {
|
|
247
|
+
var _c$3 = _$memo(() => status()?.cache_role === "not_initialized");
|
|
248
|
+
return () => _c$3() ? (() => {
|
|
249
|
+
var _el$14 = _$createElement("box"),
|
|
250
|
+
_el$15 = _$createElement("text");
|
|
251
|
+
_$insertNode(_el$14, _el$15);
|
|
252
|
+
_$setProp(_el$14, "width", "100%");
|
|
253
|
+
_$setProp(_el$14, "marginTop", 1);
|
|
254
|
+
_$setProp(_el$14, "justifyContent", "center");
|
|
255
|
+
_$insert(_el$15, () => status().message || "AFT bridge is now spawned lazily, information here will be populated after first tool call.");
|
|
256
|
+
_$effect(_$p => _$setProp(_el$15, "fg", t().textMuted, _$p));
|
|
257
|
+
return _el$14;
|
|
258
|
+
})() : null;
|
|
259
|
+
})(), _el$9);
|
|
260
|
+
_$insert(_el$4, (() => {
|
|
261
|
+
var _c$4 = _$memo(() => !!(status() && status().cache_role !== "not_initialized"));
|
|
262
|
+
return () => _c$4() ? (() => {
|
|
263
|
+
var _el$16 = _$createElement("box");
|
|
264
|
+
_$setProp(_el$16, "flexDirection", "column");
|
|
265
|
+
_$setProp(_el$16, "width", "100%");
|
|
266
|
+
_$setProp(_el$16, "marginBottom", 1);
|
|
267
|
+
_$insert(_el$16, _$createComponent(R, {
|
|
268
|
+
get theme() {
|
|
269
|
+
return t();
|
|
270
|
+
},
|
|
271
|
+
label: "Project root",
|
|
272
|
+
get value() {
|
|
273
|
+
return status().project_root ?? "(not configured)";
|
|
274
|
+
}
|
|
275
|
+
}), null);
|
|
276
|
+
_$insert(_el$16, _$createComponent(R, {
|
|
277
|
+
get theme() {
|
|
278
|
+
return t();
|
|
279
|
+
},
|
|
280
|
+
label: "Canonical root",
|
|
281
|
+
get value() {
|
|
282
|
+
return status().canonical_root ?? "(not configured)";
|
|
283
|
+
}
|
|
284
|
+
}), null);
|
|
285
|
+
_$insert(_el$16, _$createComponent(R, {
|
|
286
|
+
get theme() {
|
|
287
|
+
return t();
|
|
288
|
+
},
|
|
289
|
+
label: "Cache role",
|
|
290
|
+
get value() {
|
|
291
|
+
return status().cache_role;
|
|
292
|
+
},
|
|
293
|
+
get tone() {
|
|
294
|
+
return cacheRoleTone(status().cache_role);
|
|
295
|
+
}
|
|
296
|
+
}), null);
|
|
297
|
+
return _el$16;
|
|
298
|
+
})() : null;
|
|
299
|
+
})(), _el$9);
|
|
300
|
+
_$insert(_el$4, (() => {
|
|
301
|
+
var _c$5 = _$memo(() => !!(status() && status().cache_role !== "not_initialized"));
|
|
302
|
+
return () => _c$5() ? (() => {
|
|
303
|
+
var _el$17 = _$createElement("box"),
|
|
304
|
+
_el$18 = _$createElement("box"),
|
|
305
|
+
_el$19 = _$createElement("text"),
|
|
306
|
+
_el$20 = _$createElement("b"),
|
|
307
|
+
_el$22 = _$createElement("box"),
|
|
308
|
+
_el$23 = _$createElement("text"),
|
|
309
|
+
_el$24 = _$createElement("b"),
|
|
310
|
+
_el$26 = _$createElement("box"),
|
|
311
|
+
_el$27 = _$createElement("text"),
|
|
312
|
+
_el$28 = _$createElement("b"),
|
|
313
|
+
_el$30 = _$createElement("box"),
|
|
314
|
+
_el$31 = _$createElement("text"),
|
|
315
|
+
_el$32 = _$createElement("b"),
|
|
316
|
+
_el$34 = _$createElement("box"),
|
|
317
|
+
_el$35 = _$createElement("text"),
|
|
318
|
+
_el$36 = _$createElement("b");
|
|
319
|
+
_$insertNode(_el$17, _el$18);
|
|
320
|
+
_$insertNode(_el$17, _el$30);
|
|
321
|
+
_$setProp(_el$17, "flexDirection", "row");
|
|
322
|
+
_$setProp(_el$17, "width", "100%");
|
|
323
|
+
_$setProp(_el$17, "gap", 4);
|
|
324
|
+
_$insertNode(_el$18, _el$19);
|
|
325
|
+
_$insertNode(_el$18, _el$22);
|
|
326
|
+
_$insertNode(_el$18, _el$26);
|
|
327
|
+
_$setProp(_el$18, "flexDirection", "column");
|
|
328
|
+
_$setProp(_el$18, "flexGrow", 1);
|
|
329
|
+
_$setProp(_el$18, "flexBasis", 0);
|
|
330
|
+
_$insertNode(_el$19, _el$20);
|
|
331
|
+
_$insertNode(_el$20, _$createTextNode(`Search index`));
|
|
332
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
333
|
+
get theme() {
|
|
334
|
+
return t();
|
|
335
|
+
},
|
|
336
|
+
label: "Status",
|
|
337
|
+
get value() {
|
|
338
|
+
return status().search_index.status;
|
|
339
|
+
},
|
|
340
|
+
get tone() {
|
|
341
|
+
return statusTone(status().search_index.status);
|
|
342
|
+
}
|
|
343
|
+
}), _el$22);
|
|
344
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
345
|
+
get theme() {
|
|
346
|
+
return t();
|
|
347
|
+
},
|
|
348
|
+
label: "Files",
|
|
349
|
+
get value() {
|
|
350
|
+
return formatCountShort(status().search_index.files);
|
|
351
|
+
}
|
|
352
|
+
}), _el$22);
|
|
353
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
354
|
+
get theme() {
|
|
355
|
+
return t();
|
|
356
|
+
},
|
|
357
|
+
label: "Trigrams",
|
|
358
|
+
get value() {
|
|
359
|
+
return formatCountShort(status().search_index.trigrams);
|
|
360
|
+
}
|
|
361
|
+
}), _el$22);
|
|
362
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
363
|
+
get theme() {
|
|
364
|
+
return t();
|
|
365
|
+
},
|
|
366
|
+
label: "Disk",
|
|
367
|
+
get value() {
|
|
368
|
+
return formatBytes(status().disk.trigram_disk_bytes);
|
|
369
|
+
},
|
|
370
|
+
tone: "muted"
|
|
371
|
+
}), _el$22);
|
|
372
|
+
_$insertNode(_el$22, _el$23);
|
|
373
|
+
_$setProp(_el$22, "marginTop", 1);
|
|
374
|
+
_$insertNode(_el$23, _el$24);
|
|
375
|
+
_$insertNode(_el$24, _$createTextNode(`Runtime`));
|
|
376
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
377
|
+
get theme() {
|
|
378
|
+
return t();
|
|
379
|
+
},
|
|
380
|
+
label: "LSP servers",
|
|
381
|
+
get value() {
|
|
382
|
+
return String(status().lsp_servers);
|
|
383
|
+
}
|
|
384
|
+
}), _el$26);
|
|
385
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
386
|
+
get theme() {
|
|
387
|
+
return t();
|
|
388
|
+
},
|
|
389
|
+
label: "Symbol cache (local)",
|
|
390
|
+
get value() {
|
|
391
|
+
return formatCountShort(status().symbol_cache.local_entries);
|
|
392
|
+
}
|
|
393
|
+
}), _el$26);
|
|
394
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
395
|
+
get theme() {
|
|
396
|
+
return t();
|
|
397
|
+
},
|
|
398
|
+
label: "Symbol cache (warm)",
|
|
399
|
+
get value() {
|
|
400
|
+
return formatCountShort(status().symbol_cache.warm_entries);
|
|
401
|
+
},
|
|
402
|
+
tone: "muted"
|
|
403
|
+
}), _el$26);
|
|
404
|
+
_$insertNode(_el$26, _el$27);
|
|
405
|
+
_$setProp(_el$26, "marginTop", 1);
|
|
406
|
+
_$insertNode(_el$27, _el$28);
|
|
407
|
+
_$insertNode(_el$28, _$createTextNode(`Features`));
|
|
408
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
409
|
+
get theme() {
|
|
410
|
+
return t();
|
|
411
|
+
},
|
|
412
|
+
label: "format_on_edit",
|
|
413
|
+
get value() {
|
|
414
|
+
return status().features.format_on_edit ? "on" : "off";
|
|
415
|
+
},
|
|
416
|
+
get tone() {
|
|
417
|
+
return status().features.format_on_edit ? "ok" : "muted";
|
|
418
|
+
}
|
|
419
|
+
}), null);
|
|
420
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
421
|
+
get theme() {
|
|
422
|
+
return t();
|
|
423
|
+
},
|
|
424
|
+
label: "search_index",
|
|
425
|
+
get value() {
|
|
426
|
+
return status().features.search_index ? "on" : "off";
|
|
427
|
+
},
|
|
428
|
+
get tone() {
|
|
429
|
+
return status().features.search_index ? "ok" : "muted";
|
|
430
|
+
}
|
|
431
|
+
}), null);
|
|
432
|
+
_$insert(_el$18, _$createComponent(R, {
|
|
433
|
+
get theme() {
|
|
434
|
+
return t();
|
|
435
|
+
},
|
|
436
|
+
label: "semantic_search",
|
|
437
|
+
get value() {
|
|
438
|
+
return status().features.semantic_search ? "on" : "off";
|
|
439
|
+
},
|
|
440
|
+
get tone() {
|
|
441
|
+
return status().features.semantic_search ? "ok" : "muted";
|
|
442
|
+
}
|
|
443
|
+
}), null);
|
|
444
|
+
_$insertNode(_el$30, _el$31);
|
|
445
|
+
_$insertNode(_el$30, _el$34);
|
|
446
|
+
_$setProp(_el$30, "flexDirection", "column");
|
|
447
|
+
_$setProp(_el$30, "flexGrow", 1);
|
|
448
|
+
_$setProp(_el$30, "flexBasis", 0);
|
|
449
|
+
_$insertNode(_el$31, _el$32);
|
|
450
|
+
_$insertNode(_el$32, _$createTextNode(`Semantic index`));
|
|
451
|
+
_$insert(_el$30, _$createComponent(R, {
|
|
452
|
+
get theme() {
|
|
453
|
+
return t();
|
|
454
|
+
},
|
|
455
|
+
label: "Status",
|
|
456
|
+
get value() {
|
|
457
|
+
return formatSemanticIndexStatus(status().semantic_index.status, status().semantic_index.stage);
|
|
458
|
+
},
|
|
459
|
+
get tone() {
|
|
460
|
+
return statusTone(status().semantic_index.status);
|
|
461
|
+
}
|
|
462
|
+
}), _el$34);
|
|
463
|
+
_$insert(_el$30, (() => {
|
|
464
|
+
var _c$0 = _$memo(() => !!formatSemanticRefreshing(status().semantic_index.refreshing_count));
|
|
465
|
+
return () => _c$0() ? (() => {
|
|
466
|
+
var _el$38 = _$createElement("box"),
|
|
467
|
+
_el$39 = _$createElement("text");
|
|
468
|
+
_$insertNode(_el$38, _el$39);
|
|
469
|
+
_$setProp(_el$38, "width", "100%");
|
|
470
|
+
_$insert(_el$39, () => formatSemanticRefreshing(status().semantic_index.refreshing_count));
|
|
471
|
+
_$effect(_$p => _$setProp(_el$39, "fg", t().textMuted, _$p));
|
|
472
|
+
return _el$38;
|
|
473
|
+
})() : null;
|
|
474
|
+
})(), _el$34);
|
|
475
|
+
_$insert(_el$30, _$createComponent(R, {
|
|
476
|
+
get theme() {
|
|
477
|
+
return t();
|
|
478
|
+
},
|
|
479
|
+
label: "Entries",
|
|
480
|
+
get value() {
|
|
481
|
+
return formatCountShort(status().semantic_index.entries);
|
|
482
|
+
}
|
|
483
|
+
}), _el$34);
|
|
484
|
+
_$insert(_el$30, (() => {
|
|
485
|
+
var _c$1 = _$memo(() => !!status().semantic_index.backend);
|
|
486
|
+
return () => _c$1() ? _$createComponent(R, {
|
|
487
|
+
get theme() {
|
|
488
|
+
return t();
|
|
489
|
+
},
|
|
490
|
+
label: "Backend",
|
|
491
|
+
get value() {
|
|
492
|
+
return status().semantic_index.backend;
|
|
493
|
+
},
|
|
494
|
+
tone: "muted"
|
|
495
|
+
}) : null;
|
|
496
|
+
})(), _el$34);
|
|
497
|
+
_$insert(_el$30, (() => {
|
|
498
|
+
var _c$10 = _$memo(() => !!status().semantic_index.model);
|
|
499
|
+
return () => _c$10() ? _$createComponent(R, {
|
|
500
|
+
get theme() {
|
|
501
|
+
return t();
|
|
502
|
+
},
|
|
503
|
+
label: "Model",
|
|
504
|
+
get value() {
|
|
505
|
+
return status().semantic_index.model;
|
|
506
|
+
},
|
|
507
|
+
tone: "muted"
|
|
508
|
+
}) : null;
|
|
509
|
+
})(), _el$34);
|
|
510
|
+
_$insert(_el$30, (() => {
|
|
511
|
+
var _c$11 = _$memo(() => status().semantic_index.dimension != null);
|
|
512
|
+
return () => _c$11() ? _$createComponent(R, {
|
|
513
|
+
get theme() {
|
|
514
|
+
return t();
|
|
515
|
+
},
|
|
516
|
+
label: "Dimension",
|
|
517
|
+
get value() {
|
|
518
|
+
return String(status().semantic_index.dimension);
|
|
519
|
+
},
|
|
520
|
+
tone: "muted"
|
|
521
|
+
}) : null;
|
|
522
|
+
})(), _el$34);
|
|
523
|
+
_$insert(_el$30, _$createComponent(R, {
|
|
524
|
+
get theme() {
|
|
525
|
+
return t();
|
|
526
|
+
},
|
|
527
|
+
label: "Disk",
|
|
528
|
+
get value() {
|
|
529
|
+
return formatBytes(status().disk.semantic_disk_bytes);
|
|
530
|
+
},
|
|
531
|
+
tone: "muted"
|
|
532
|
+
}), _el$34);
|
|
533
|
+
_$insertNode(_el$34, _el$35);
|
|
534
|
+
_$setProp(_el$34, "marginTop", 1);
|
|
535
|
+
_$insertNode(_el$35, _el$36);
|
|
536
|
+
_$insertNode(_el$36, _$createTextNode(`Current session`));
|
|
537
|
+
_$insert(_el$30, _$createComponent(R, {
|
|
538
|
+
get theme() {
|
|
539
|
+
return t();
|
|
540
|
+
},
|
|
541
|
+
label: "Tracked files",
|
|
542
|
+
get value() {
|
|
543
|
+
return String(status().session.tracked_files);
|
|
544
|
+
}
|
|
545
|
+
}), null);
|
|
546
|
+
_$insert(_el$30, _$createComponent(R, {
|
|
547
|
+
get theme() {
|
|
548
|
+
return t();
|
|
549
|
+
},
|
|
550
|
+
label: "Checkpoints",
|
|
551
|
+
get value() {
|
|
552
|
+
return String(status().session.checkpoints);
|
|
553
|
+
}
|
|
554
|
+
}), null);
|
|
555
|
+
_$insert(_el$30, _$createComponent(R, {
|
|
556
|
+
get theme() {
|
|
557
|
+
return t();
|
|
558
|
+
},
|
|
559
|
+
label: "All-session checkpoints",
|
|
560
|
+
get value() {
|
|
561
|
+
return String(status().checkpoints_total);
|
|
562
|
+
},
|
|
563
|
+
tone: "muted"
|
|
564
|
+
}), null);
|
|
565
|
+
_$effect(_p$ => {
|
|
566
|
+
var _v$5 = t().text,
|
|
567
|
+
_v$6 = t().text,
|
|
568
|
+
_v$7 = t().text,
|
|
569
|
+
_v$8 = t().text,
|
|
570
|
+
_v$9 = t().text;
|
|
571
|
+
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$19, "fg", _v$5, _p$.e));
|
|
572
|
+
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$23, "fg", _v$6, _p$.t));
|
|
573
|
+
_v$7 !== _p$.a && (_p$.a = _$setProp(_el$27, "fg", _v$7, _p$.a));
|
|
574
|
+
_v$8 !== _p$.o && (_p$.o = _$setProp(_el$31, "fg", _v$8, _p$.o));
|
|
575
|
+
_v$9 !== _p$.i && (_p$.i = _$setProp(_el$35, "fg", _v$9, _p$.i));
|
|
576
|
+
return _p$;
|
|
577
|
+
}, {
|
|
578
|
+
e: undefined,
|
|
579
|
+
t: undefined,
|
|
580
|
+
a: undefined,
|
|
581
|
+
o: undefined,
|
|
582
|
+
i: undefined
|
|
583
|
+
});
|
|
584
|
+
return _el$17;
|
|
585
|
+
})() : null;
|
|
586
|
+
})(), _el$9);
|
|
587
|
+
_$insert(_el$4, (() => {
|
|
588
|
+
var _c$6 = _$memo(() => !!status()?.semantic_index.stage);
|
|
589
|
+
return () => _c$6() ? (() => {
|
|
590
|
+
var _el$40 = _$createElement("box"),
|
|
591
|
+
_el$41 = _$createElement("text"),
|
|
592
|
+
_el$42 = _$createElement("b");
|
|
593
|
+
_$insertNode(_el$40, _el$41);
|
|
594
|
+
_$setProp(_el$40, "flexDirection", "column");
|
|
595
|
+
_$setProp(_el$40, "width", "100%");
|
|
596
|
+
_$setProp(_el$40, "marginTop", 1);
|
|
597
|
+
_$insertNode(_el$41, _el$42);
|
|
598
|
+
_$insertNode(_el$42, _$createTextNode(`Semantic build progress`));
|
|
599
|
+
_$insert(_el$40, _$createComponent(R, {
|
|
600
|
+
get theme() {
|
|
601
|
+
return t();
|
|
602
|
+
},
|
|
603
|
+
label: "Stage",
|
|
604
|
+
get value() {
|
|
605
|
+
return status().semantic_index.stage;
|
|
606
|
+
}
|
|
607
|
+
}), null);
|
|
608
|
+
_$insert(_el$40, (() => {
|
|
609
|
+
var _c$12 = _$memo(() => status().semantic_index.files != null);
|
|
610
|
+
return () => _c$12() ? _$createComponent(R, {
|
|
611
|
+
get theme() {
|
|
612
|
+
return t();
|
|
613
|
+
},
|
|
614
|
+
label: "Files seen",
|
|
615
|
+
get value() {
|
|
616
|
+
return formatCountShort(status().semantic_index.files);
|
|
617
|
+
}
|
|
618
|
+
}) : null;
|
|
619
|
+
})(), null);
|
|
620
|
+
_$insert(_el$40, (() => {
|
|
621
|
+
var _c$13 = _$memo(() => !!(status().semantic_index.entries_done != null || status().semantic_index.entries_total != null));
|
|
622
|
+
return () => _c$13() ? _$createComponent(R, {
|
|
623
|
+
get theme() {
|
|
624
|
+
return t();
|
|
625
|
+
},
|
|
626
|
+
label: "Progress",
|
|
627
|
+
get value() {
|
|
628
|
+
return `${formatCountShort(status().semantic_index.entries_done ?? null)} / ${formatCountShort(status().semantic_index.entries_total ?? null)}`;
|
|
629
|
+
}
|
|
630
|
+
}) : null;
|
|
631
|
+
})(), null);
|
|
632
|
+
_$effect(_$p => _$setProp(_el$41, "fg", t().text, _$p));
|
|
633
|
+
return _el$40;
|
|
634
|
+
})() : null;
|
|
635
|
+
})(), _el$9);
|
|
636
|
+
_$insert(_el$4, (() => {
|
|
637
|
+
var _c$7 = _$memo(() => !!status()?.semantic_index.error);
|
|
638
|
+
return () => _c$7() ? (() => {
|
|
639
|
+
var _el$44 = _$createElement("box"),
|
|
640
|
+
_el$45 = _$createElement("text"),
|
|
641
|
+
_el$46 = _$createTextNode(`⚠ `);
|
|
642
|
+
_$insertNode(_el$44, _el$45);
|
|
643
|
+
_$setProp(_el$44, "marginTop", 1);
|
|
644
|
+
_$setProp(_el$44, "width", "100%");
|
|
645
|
+
_$insertNode(_el$45, _el$46);
|
|
646
|
+
_$insert(_el$45, () => status().semantic_index.error, null);
|
|
647
|
+
_$effect(_$p => _$setProp(_el$45, "fg", t().error, _$p));
|
|
648
|
+
return _el$44;
|
|
649
|
+
})() : null;
|
|
650
|
+
})(), _el$9);
|
|
651
|
+
_$insert(_el$4, (() => {
|
|
652
|
+
var _c$8 = _$memo(() => compressionAggregateRows().length > 0);
|
|
653
|
+
return () => _c$8() ? (() => {
|
|
654
|
+
var _el$47 = _$createElement("box"),
|
|
655
|
+
_el$48 = _$createElement("text"),
|
|
656
|
+
_el$49 = _$createElement("b");
|
|
657
|
+
_$insertNode(_el$47, _el$48);
|
|
658
|
+
_$setProp(_el$47, "flexDirection", "column");
|
|
659
|
+
_$setProp(_el$47, "width", "100%");
|
|
660
|
+
_$setProp(_el$47, "marginTop", 1);
|
|
661
|
+
_$insertNode(_el$48, _el$49);
|
|
662
|
+
_$insertNode(_el$49, _$createTextNode(`Compression`));
|
|
663
|
+
_$insert(_el$47, () => compressionAggregateRows().map(row => row.kind === "scope" ? (() => {
|
|
664
|
+
var _el$51 = _$createElement("box"),
|
|
665
|
+
_el$52 = _$createElement("text");
|
|
666
|
+
_$insertNode(_el$51, _el$52);
|
|
667
|
+
_$setProp(_el$51, "width", "100%");
|
|
668
|
+
_$insert(_el$52, () => row.label);
|
|
669
|
+
_$effect(_$p => _$setProp(_el$52, "fg", t().text, _$p));
|
|
670
|
+
return _el$51;
|
|
671
|
+
})() : _$createComponent(R, {
|
|
672
|
+
get theme() {
|
|
673
|
+
return t();
|
|
674
|
+
},
|
|
675
|
+
get label() {
|
|
676
|
+
return row.label;
|
|
677
|
+
},
|
|
678
|
+
get value() {
|
|
679
|
+
return row.value;
|
|
680
|
+
},
|
|
681
|
+
tone: "muted"
|
|
682
|
+
})), null);
|
|
683
|
+
_$effect(_$p => _$setProp(_el$48, "fg", t().text, _$p));
|
|
684
|
+
return _el$47;
|
|
685
|
+
})() : null;
|
|
686
|
+
})(), _el$9);
|
|
687
|
+
_$insert(_el$4, (() => {
|
|
688
|
+
var _c$9 = _$memo(() => !!status()?.status_bar);
|
|
689
|
+
return () => _c$9() ? (() => {
|
|
690
|
+
var _el$53 = _$createElement("box"),
|
|
691
|
+
_el$54 = _$createElement("text"),
|
|
692
|
+
_el$55 = _$createElement("b");
|
|
693
|
+
_$insertNode(_el$53, _el$54);
|
|
694
|
+
_$setProp(_el$53, "flexDirection", "column");
|
|
695
|
+
_$setProp(_el$53, "width", "100%");
|
|
696
|
+
_$setProp(_el$53, "marginTop", 1);
|
|
697
|
+
_$insertNode(_el$54, _el$55);
|
|
698
|
+
_$insert(_el$55, () => status().status_bar.tier2_stale ? "Code Health ~" : "Code Health");
|
|
699
|
+
_$insert(_el$53, _$createComponent(R, {
|
|
700
|
+
get theme() {
|
|
701
|
+
return t();
|
|
702
|
+
},
|
|
703
|
+
label: "Errors",
|
|
704
|
+
get value() {
|
|
705
|
+
return formatCountShort(status().status_bar.errors);
|
|
706
|
+
},
|
|
707
|
+
get tone() {
|
|
708
|
+
return status().status_bar.errors > 0 ? "err" : "muted";
|
|
709
|
+
}
|
|
710
|
+
}), null);
|
|
711
|
+
_$insert(_el$53, _$createComponent(R, {
|
|
712
|
+
get theme() {
|
|
713
|
+
return t();
|
|
714
|
+
},
|
|
715
|
+
label: "Warnings",
|
|
716
|
+
get value() {
|
|
717
|
+
return formatCountShort(status().status_bar.warnings);
|
|
718
|
+
},
|
|
719
|
+
get tone() {
|
|
720
|
+
return status().status_bar.warnings > 0 ? "warn" : "muted";
|
|
721
|
+
}
|
|
722
|
+
}), null);
|
|
723
|
+
_$insert(_el$53, _$createComponent(R, {
|
|
724
|
+
get theme() {
|
|
725
|
+
return t();
|
|
726
|
+
},
|
|
727
|
+
label: "Dead Code",
|
|
728
|
+
get value() {
|
|
729
|
+
return formatCountShort(status().status_bar.dead_code);
|
|
730
|
+
},
|
|
731
|
+
tone: "muted"
|
|
732
|
+
}), null);
|
|
733
|
+
_$insert(_el$53, _$createComponent(R, {
|
|
734
|
+
get theme() {
|
|
735
|
+
return t();
|
|
736
|
+
},
|
|
737
|
+
label: "Unused Exports",
|
|
738
|
+
get value() {
|
|
739
|
+
return formatCountShort(status().status_bar.unused_exports);
|
|
740
|
+
},
|
|
741
|
+
tone: "muted"
|
|
742
|
+
}), null);
|
|
743
|
+
_$insert(_el$53, _$createComponent(R, {
|
|
744
|
+
get theme() {
|
|
745
|
+
return t();
|
|
746
|
+
},
|
|
747
|
+
label: "Duplicates",
|
|
748
|
+
get value() {
|
|
749
|
+
return formatCountShort(status().status_bar.duplicates);
|
|
750
|
+
},
|
|
751
|
+
tone: "muted"
|
|
752
|
+
}), null);
|
|
753
|
+
_$insert(_el$53, _$createComponent(R, {
|
|
754
|
+
get theme() {
|
|
755
|
+
return t();
|
|
756
|
+
},
|
|
757
|
+
label: "TODOs",
|
|
758
|
+
get value() {
|
|
759
|
+
return formatCountShort(status().status_bar.todos);
|
|
760
|
+
},
|
|
761
|
+
tone: "muted"
|
|
762
|
+
}), null);
|
|
763
|
+
_$effect(_$p => _$setProp(_el$54, "fg", t().text, _$p));
|
|
764
|
+
return _el$53;
|
|
765
|
+
})() : null;
|
|
766
|
+
})(), _el$9);
|
|
767
|
+
_$insertNode(_el$9, _el$0);
|
|
768
|
+
_$setProp(_el$9, "marginTop", 1);
|
|
769
|
+
_$setProp(_el$9, "justifyContent", "flex-end");
|
|
770
|
+
_$setProp(_el$9, "width", "100%");
|
|
771
|
+
_$insertNode(_el$0, _$createTextNode(`Enter or Esc to close`));
|
|
772
|
+
_$effect(_p$ => {
|
|
773
|
+
var _v$3 = t().accent,
|
|
774
|
+
_v$4 = t().textMuted;
|
|
775
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$3, _p$.e));
|
|
776
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$0, "fg", _v$4, _p$.t));
|
|
777
|
+
return _p$;
|
|
778
|
+
}, {
|
|
779
|
+
e: undefined,
|
|
780
|
+
t: undefined
|
|
781
|
+
});
|
|
782
|
+
return _el$4;
|
|
783
|
+
})();
|
|
784
|
+
};
|
|
785
|
+
async function showStatusDialog(api) {
|
|
786
|
+
const sessionID = getSessionId(api);
|
|
787
|
+
if (!sessionID) {
|
|
788
|
+
api.ui.toast({
|
|
789
|
+
message: "No active session",
|
|
790
|
+
variant: "warning",
|
|
791
|
+
duration: 5000
|
|
792
|
+
});
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const directory = api.state.path.directory ?? "";
|
|
796
|
+
if (!directory) {
|
|
797
|
+
api.ui.toast({
|
|
798
|
+
message: "No project directory",
|
|
799
|
+
variant: "warning",
|
|
800
|
+
duration: 5000
|
|
801
|
+
});
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const client = getRpcClient(directory);
|
|
805
|
+
|
|
806
|
+
// Prime the dialog with one initial fetch so we don't show a blank
|
|
807
|
+
// skeleton — the component then listens for pushed invalidations.
|
|
808
|
+
let initial = null;
|
|
809
|
+
let initialError = null;
|
|
810
|
+
try {
|
|
811
|
+
const response = await client.call("status", {
|
|
812
|
+
sessionID
|
|
813
|
+
}, {
|
|
814
|
+
accept: statusAcceptGate(directory)
|
|
815
|
+
});
|
|
816
|
+
if (response.success !== false) {
|
|
817
|
+
initial = coerceAftStatus(response);
|
|
818
|
+
} else {
|
|
819
|
+
initialError = "AFT bridge returned an error response.";
|
|
820
|
+
}
|
|
821
|
+
} catch {
|
|
822
|
+
initialError = "AFT is starting up. Status will refresh automatically...";
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// The dialog host already wraps every replace()'d element in its own centered
|
|
826
|
+
// frame and binds Esc/Ctrl-C to close it. Rendering our own dialog frame inside
|
|
827
|
+
// that host frame would misplace the content and focus; pass the bare component
|
|
828
|
+
// so the host frames it once.
|
|
829
|
+
// Do not pass an onClose that calls dialog.clear(). The dialog system calls
|
|
830
|
+
// every entry's onClose on Esc, and clear() would re-trigger them, causing
|
|
831
|
+
// infinite recursion. The host itself pops the dialog; StatusDialog cleans up
|
|
832
|
+
// its own subscriptions in onCleanup. `replace` resets size to "medium", so
|
|
833
|
+
// request "large" after the call.
|
|
834
|
+
api.ui.dialog.replace(() => _$createComponent(StatusDialog, {
|
|
835
|
+
api: api,
|
|
836
|
+
client: client,
|
|
837
|
+
directory: directory,
|
|
838
|
+
sessionID: sessionID,
|
|
839
|
+
initial: initial,
|
|
840
|
+
initialError: initialError
|
|
841
|
+
}));
|
|
842
|
+
api.ui.dialog.setSize("large");
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Register the AFT status palette command, preferring the v1.14.42+ keymap API
|
|
847
|
+
* and falling back to the legacy `api.command.register` for older hosts.
|
|
848
|
+
*
|
|
849
|
+
* No slash entry is registered here. The server config hook owns the single
|
|
850
|
+
* `/aft-status` slash registration, while this palette command keeps Ctrl+P
|
|
851
|
+
* discovery in the TUI.
|
|
852
|
+
*/
|
|
853
|
+
function registerStatusCommand(api) {
|
|
854
|
+
const apiAny = api;
|
|
855
|
+
if (typeof apiAny.keymap?.registerLayer === "function") {
|
|
856
|
+
apiAny.keymap.registerLayer({
|
|
857
|
+
commands: [{
|
|
858
|
+
namespace: "palette",
|
|
859
|
+
name: "aft.status",
|
|
860
|
+
title: "AFT: Status",
|
|
861
|
+
category: "AFT",
|
|
862
|
+
run() {
|
|
863
|
+
void showStatusDialog(api);
|
|
864
|
+
}
|
|
865
|
+
}],
|
|
866
|
+
bindings: []
|
|
867
|
+
});
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (typeof apiAny.command?.register === "function") {
|
|
871
|
+
apiAny.command.register(() => [{
|
|
872
|
+
title: "AFT: Status",
|
|
873
|
+
value: "aft.status",
|
|
874
|
+
category: "AFT",
|
|
875
|
+
onSelect() {
|
|
876
|
+
void showStatusDialog(api);
|
|
877
|
+
}
|
|
878
|
+
}]);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Neither API surface is present. The TUI host can still load; users can
|
|
883
|
+
// still see AFT status through the sidebar and server-owned slash command.
|
|
884
|
+
}
|
|
885
|
+
async function showStartupNotifications(api) {
|
|
886
|
+
const directory = api.state.path.directory ?? "";
|
|
887
|
+
if (!directory) return;
|
|
888
|
+
const client = getRpcClient(directory);
|
|
889
|
+
|
|
890
|
+
// Check for feature announcements
|
|
891
|
+
try {
|
|
892
|
+
const announcement = await client.call("get-announcement", {});
|
|
893
|
+
if (announcement.show && announcement.version && announcement.features?.length) {
|
|
894
|
+
const featureText = announcement.features.map(f => ` • ${f}`).join("\n");
|
|
895
|
+
// Blank-line separator distinguishes the persistent footer (Discord
|
|
896
|
+
// invite, etc.) from the version-specific bullets.
|
|
897
|
+
const hasFooter = typeof announcement.footer === "string" && announcement.footer.trim().length > 0;
|
|
898
|
+
const message = hasFooter ? `What's new:\n\n${featureText}\n\n${announcement.footer}` : `What's new:\n\n${featureText}`;
|
|
899
|
+
api.ui.dialog.replace(() => _$createComponent(api.ui.DialogAlert, {
|
|
900
|
+
get title() {
|
|
901
|
+
return `AFT v${announcement.version}`;
|
|
902
|
+
},
|
|
903
|
+
message: message,
|
|
904
|
+
onConfirm: () => {
|
|
905
|
+
// Mark as announced so it doesn't show again
|
|
906
|
+
void client.call("mark-announced", {});
|
|
907
|
+
}
|
|
908
|
+
}), () => {
|
|
909
|
+
void client.call("mark-announced", {});
|
|
910
|
+
});
|
|
911
|
+
return; // Show one dialog at a time
|
|
912
|
+
}
|
|
913
|
+
} catch {
|
|
914
|
+
// RPC server not ready yet — skip announcements
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Check for warnings
|
|
918
|
+
try {
|
|
919
|
+
const result = await client.call("get-warnings", {});
|
|
920
|
+
if (result.warnings?.length) {
|
|
921
|
+
const warningText = result.warnings.join("\n\n");
|
|
922
|
+
api.ui.dialog.replace(() => _$createComponent(api.ui.DialogAlert, {
|
|
923
|
+
title: "AFT Warning",
|
|
924
|
+
message: warningText,
|
|
925
|
+
onConfirm: () => {}
|
|
926
|
+
}), () => {});
|
|
927
|
+
}
|
|
928
|
+
} catch {
|
|
929
|
+
// RPC server not ready — skip warnings
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const tui = async api => {
|
|
933
|
+
// Sidebar slot: live status of search index, semantic index, and disk
|
|
934
|
+
// usage. See ./sidebar.tsx for the panel itself. Registered before the
|
|
935
|
+
// command palette entry so the sidebar is available immediately when the
|
|
936
|
+
// user opens their first session.
|
|
937
|
+
try {
|
|
938
|
+
api.slots.register(await createAftSidebarSlot(api, packageVersion));
|
|
939
|
+
} catch {
|
|
940
|
+
// Older OpenCode TUI hosts may not implement api.slots; fall through and
|
|
941
|
+
// keep the palette command / server-owned slash command working.
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// OpenCode 1.14.42 removed `api.command.register` entirely
|
|
945
|
+
// (anomalyco/opencode PR #26053). Later patches reinstated it as a
|
|
946
|
+
// deprecated shim that translates to `api.keymap.registerLayer`. To work
|
|
947
|
+
// across the whole 1.14.x line — including the brief 1.14.42 / 1.14.43
|
|
948
|
+
// window where neither the legacy API nor a shim was present — we prefer
|
|
949
|
+
// `api.keymap.registerLayer` and fall back to `api.command.register` only
|
|
950
|
+
// when the keymap surface is missing (older hosts that predate keymap).
|
|
951
|
+
// See https://github.com/cortexkit/aft/issues/33.
|
|
952
|
+
registerStatusCommand(api);
|
|
953
|
+
|
|
954
|
+
// Receive server → TUI dialog requests and status invalidations over one
|
|
955
|
+
// persistent WebSocket. This avoids repeated loopback HTTP connection setup,
|
|
956
|
+
// which caused idle CPU usage.
|
|
957
|
+
const handleNotification = async message => {
|
|
958
|
+
const requestedSessionId = getSessionId(api);
|
|
959
|
+
if (!requestedSessionId) return false;
|
|
960
|
+
if (message.sessionId && message.sessionId !== requestedSessionId) return false;
|
|
961
|
+
if (message.type !== "action") return false;
|
|
962
|
+
if (message.payload?.action !== "show-status-dialog") return false;
|
|
963
|
+
await showStatusDialog(api);
|
|
964
|
+
return true;
|
|
965
|
+
};
|
|
966
|
+
startAftTuiSocket({
|
|
967
|
+
getDirectory: () => api.state.path.directory ?? "",
|
|
968
|
+
getSessionId: () => getSessionId(api),
|
|
969
|
+
onNotification: handleNotification
|
|
970
|
+
});
|
|
971
|
+
const socketScopeUnsubs = [api.event?.on?.("message.updated", () => refreshAftTuiSocketScope()), api.event?.on?.("session.updated", () => refreshAftTuiSocketScope())].filter(Boolean);
|
|
972
|
+
api.lifecycle?.onDispose?.(() => {
|
|
973
|
+
stopAftTuiSocket();
|
|
974
|
+
for (const unsub of socketScopeUnsubs) {
|
|
975
|
+
try {
|
|
976
|
+
unsub();
|
|
977
|
+
} catch {
|
|
978
|
+
// Ignore unsubscribe errors during cleanup; socket shutdown is handled separately.
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
for (const client of rpcClients.values()) client.reset();
|
|
982
|
+
rpcClients.clear();
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// Show startup notifications — RPC server is already running by the time TUI loads
|
|
986
|
+
void showStartupNotifications(api);
|
|
987
|
+
};
|
|
988
|
+
const id = "aft-opencode";
|
|
989
|
+
export default {
|
|
990
|
+
id,
|
|
991
|
+
tui
|
|
992
|
+
};
|