@cortexkit/aft-opencode 0.56.2 → 0.57.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/bg-notifications.d.ts +1 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/entry/server.js +2650 -267
- package/dist/index.js +420 -228
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/permissions/v2-service.d.ts +30 -0
- package/dist/permissions/v2-service.d.ts.map +1 -0
- package/dist/permissions/v2.d.ts +121 -9
- package/dist/permissions/v2.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/status.d.ts +8 -1
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/subc-tool-schemas.d.ts +11 -0
- package/dist/subc-tool-schemas.d.ts.map +1 -1
- package/dist/tool-registration.d.ts.map +1 -1
- package/dist/tools/definitions/v2.d.ts +6 -0
- package/dist/tools/definitions/v2.d.ts.map +1 -1
- package/dist/tools/hoisted/v2.d.ts +12 -2
- package/dist/tools/hoisted/v2.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tui/v2-status.d.ts +8 -0
- package/dist/tui/v2-status.d.ts.map +1 -1
- package/index.js +14 -0
- package/package.json +14 -10
- package/server.js +8 -0
- package/src/shared/rpc-server.ts +5 -0
- package/src/shared/status.ts +16 -9
- package/src/tui/index.tsx +1 -0
- package/src/tui/sidebar-view.tsx +722 -0
- package/src/tui/sidebar.tsx +42 -560
- package/src/tui/v2-status.ts +8 -1
- package/src/tui/v2.tsx +173 -30
- package/src/tui-compiled/index.tsx +1 -1
- package/src/tui-compiled/sidebar-view.tsx +815 -0
- package/src/tui-compiled/sidebar.tsx +45 -693
- package/src/tui-compiled/v2-status.ts +8 -1
- package/src/tui-compiled/v2.tsx +163 -54
- package/tui.js +7 -0
package/src/tui/v2.tsx
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
// @ts-nocheck
|
|
3
3
|
|
|
4
4
|
import { createEffect, createSignal, onCleanup } from "solid-js";
|
|
5
|
+
import { version as packageVersion } from "../../package.json";
|
|
5
6
|
import {
|
|
6
7
|
type AftStatusSnapshot,
|
|
7
8
|
coerceAftStatus,
|
|
8
9
|
formatStatusDialogMessage,
|
|
9
10
|
} from "../shared/status";
|
|
10
11
|
import { AftRpc } from "./aft-rpc";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
AftSidebarPanel,
|
|
14
|
+
createSidebarPreferences,
|
|
15
|
+
type SidebarColor,
|
|
16
|
+
type SidebarPalette,
|
|
17
|
+
} from "./sidebar-view";
|
|
18
|
+
import { formatAftStatusSegment } from "./v2-status";
|
|
12
19
|
|
|
13
20
|
type AftRpcClient = {
|
|
14
21
|
getStatus(
|
|
@@ -24,9 +31,57 @@ type AftRpcClient = {
|
|
|
24
31
|
};
|
|
25
32
|
};
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The colour tokens the sidebar needs out of the host's `ResolvedTheme` — the
|
|
36
|
+
* type `Context.theme` carries, exported by `@opencode/theme/tui`. That
|
|
37
|
+
* package is an optional peer of `@opencode/plugin` and is not installed
|
|
38
|
+
* alongside this plugin, so the shape is written out here instead of imported;
|
|
39
|
+
* the key names are the theme package's own. They are nested token groups,
|
|
40
|
+
* unlike the flat colour names the slot-plugin host in ./sidebar.tsx reads.
|
|
41
|
+
* Every level is optional so a host that ships a partial theme degrades to the
|
|
42
|
+
* fallback palette below instead of throwing during a render.
|
|
43
|
+
*
|
|
44
|
+
* What a host really hands over is recorded, token for token, in
|
|
45
|
+
* test/load-matrix/ga-host-theme.json; that record is what this type and the
|
|
46
|
+
* mapping below are written against, and the load matrix compares it to a live
|
|
47
|
+
* host on every run.
|
|
48
|
+
*/
|
|
49
|
+
type GaFeedbackKind = "error" | "warning" | "success" | "info";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* One step of a hue scale. The steps are ordered by how far the shade stands
|
|
53
|
+
* off the page rather than by lightness: 100 contrasts with the page most and
|
|
54
|
+
* 900 blends into it, in both theme modes. So the host's dark theme resolves
|
|
55
|
+
* accent 100 to its lightest purple and its light theme resolves accent 100 to
|
|
56
|
+
* its darkest orange, and a step chosen for one mode keeps its role in the
|
|
57
|
+
* other.
|
|
58
|
+
*/
|
|
59
|
+
type GaHueStep = "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900";
|
|
60
|
+
|
|
61
|
+
type GaResolvedTheme = {
|
|
62
|
+
hue?: { accent?: Partial<Record<GaHueStep, SidebarColor>> };
|
|
63
|
+
text?: {
|
|
64
|
+
base?: SidebarColor;
|
|
65
|
+
muted?: SidebarColor;
|
|
66
|
+
feedback?: Partial<Record<GaFeedbackKind, { base?: SidebarColor }>>;
|
|
67
|
+
};
|
|
68
|
+
background?: { base?: SidebarColor };
|
|
69
|
+
border?: { base?: SidebarColor };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The accent shade the AFT badge is filled with. A low step is what keeps the
|
|
74
|
+
* fill standing off the page in both theme modes (see GaHueStep), and 200 is
|
|
75
|
+
* the step the host itself draws its own accent-coloured UI in.
|
|
76
|
+
*/
|
|
77
|
+
const BADGE_ACCENT_STEP: GaHueStep = "200";
|
|
78
|
+
|
|
27
79
|
type V2TuiContext = {
|
|
28
80
|
location?: unknown;
|
|
29
81
|
client: { rpc(definition: typeof AftRpc): AftRpcClient };
|
|
82
|
+
/** Resolved for the active theme mode by the host; see GaResolvedTheme. */
|
|
83
|
+
theme?: GaResolvedTheme;
|
|
84
|
+
themeMode?: "dark" | "light";
|
|
30
85
|
keymap: {
|
|
31
86
|
layer(input: () => { commands: Array<Record<string, unknown>>; bindings: string[] }): void;
|
|
32
87
|
};
|
|
@@ -34,12 +89,76 @@ type V2TuiContext = {
|
|
|
34
89
|
dialog: { alert(input: { title: string; message: string }): Promise<void> };
|
|
35
90
|
router: { current(): { type: string; sessionID?: string } };
|
|
36
91
|
slot(claim: {
|
|
37
|
-
append: "prompt.footer.status" | "sidebar.content";
|
|
92
|
+
append: "app" | "prompt.footer.status" | "sidebar.content";
|
|
38
93
|
render(input: { sessionID?: string }): unknown;
|
|
39
94
|
}): () => void;
|
|
40
95
|
};
|
|
41
96
|
};
|
|
42
97
|
|
|
98
|
+
// Last resort for a host that declares a theme mode but hands over no resolved
|
|
99
|
+
// theme. Inheriting the terminal default instead is what made the panel white
|
|
100
|
+
// on white in light mode, so there has to be a legible colour for every role
|
|
101
|
+
// even when the theme is missing.
|
|
102
|
+
const FALLBACK_PALETTES: Record<"dark" | "light", SidebarPalette> = {
|
|
103
|
+
dark: {
|
|
104
|
+
text: "#e6edf3",
|
|
105
|
+
textMuted: "#9198a1",
|
|
106
|
+
success: "#3fb950",
|
|
107
|
+
warning: "#d29922",
|
|
108
|
+
error: "#f85149",
|
|
109
|
+
accent: "#1f6feb",
|
|
110
|
+
background: "#0d1117",
|
|
111
|
+
border: "#3d444d",
|
|
112
|
+
},
|
|
113
|
+
light: {
|
|
114
|
+
text: "#1f2328",
|
|
115
|
+
textMuted: "#59636e",
|
|
116
|
+
success: "#1a7f37",
|
|
117
|
+
warning: "#9a6700",
|
|
118
|
+
error: "#cf222e",
|
|
119
|
+
accent: "#0969da",
|
|
120
|
+
background: "#ffffff",
|
|
121
|
+
border: "#d1d9e0",
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Host theme → the panel's palette. The host resolves its theme for the active
|
|
127
|
+
* mode before handing it over, so light mode arrives as light colours here;
|
|
128
|
+
* what matters is that every text line asks for one of them instead of
|
|
129
|
+
* inheriting the terminal default.
|
|
130
|
+
*
|
|
131
|
+
* `success` falls back to the accent exactly as ./sidebar.tsx's mapping does.
|
|
132
|
+
*/
|
|
133
|
+
export function resolveV2Palette(
|
|
134
|
+
theme: GaResolvedTheme | undefined,
|
|
135
|
+
themeMode: "dark" | "light" | undefined,
|
|
136
|
+
): SidebarPalette {
|
|
137
|
+
const fallback = FALLBACK_PALETTES[themeMode === "light" ? "light" : "dark"];
|
|
138
|
+
if (!theme) return { ...fallback };
|
|
139
|
+
|
|
140
|
+
// The accent comes out of the hue scales, not out of `background.action`.
|
|
141
|
+
// The action groups describe how the host paints a button, and this theme
|
|
142
|
+
// paints one as bare text: its `background.action.primary.base` is a fully
|
|
143
|
+
// transparent colour. Filling the badge with that drew no badge at all, and
|
|
144
|
+
// nothing caught it, because a present-but-transparent colour never reaches
|
|
145
|
+
// the fallback below.
|
|
146
|
+
const accent = theme.hue?.accent?.[BADGE_ACCENT_STEP] ?? fallback.accent;
|
|
147
|
+
const feedback = theme.text?.feedback;
|
|
148
|
+
return {
|
|
149
|
+
text: theme.text?.base ?? fallback.text,
|
|
150
|
+
textMuted: theme.text?.muted ?? fallback.textMuted,
|
|
151
|
+
success: feedback?.success?.base ?? accent,
|
|
152
|
+
warning: feedback?.warning?.base ?? fallback.warning,
|
|
153
|
+
error: feedback?.error?.base ?? fallback.error,
|
|
154
|
+
accent,
|
|
155
|
+
background: theme.background?.base ?? fallback.background,
|
|
156
|
+
// This theme has one border colour; ./sidebar.tsx's host distinguishes an
|
|
157
|
+
// active one, and the panel only ever draws the active border.
|
|
158
|
+
border: theme.border?.base ?? fallback.border,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
43
162
|
function eventMatchesSession(
|
|
44
163
|
eventSessionID: string | undefined,
|
|
45
164
|
sessionID: string | undefined,
|
|
@@ -125,17 +244,16 @@ function FooterStatus(props: { context: V2TuiContext; rpc: AftRpcClient; session
|
|
|
125
244
|
|
|
126
245
|
function SidebarStatus(props: { context: V2TuiContext; rpc: AftRpcClient; sessionID: string }) {
|
|
127
246
|
const status = useAftStatus(props.context, props.rpc, () => props.sessionID);
|
|
128
|
-
const
|
|
247
|
+
const preferences = createSidebarPreferences();
|
|
129
248
|
return (
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
</box>
|
|
249
|
+
<AftSidebarPanel
|
|
250
|
+
palette={resolveV2Palette(props.context.theme, props.context.themeMode)}
|
|
251
|
+
snapshot={status()}
|
|
252
|
+
prefs={preferences.prefs()}
|
|
253
|
+
collapsed={preferences.collapsed()}
|
|
254
|
+
onToggleCollapsed={preferences.toggleCollapsed}
|
|
255
|
+
pluginVersion={packageVersion}
|
|
256
|
+
/>
|
|
139
257
|
);
|
|
140
258
|
}
|
|
141
259
|
|
|
@@ -163,10 +281,53 @@ async function showStatusDialog(
|
|
|
163
281
|
}
|
|
164
282
|
}
|
|
165
283
|
|
|
284
|
+
// `keymap.layer` is a Solid primitive, not a plain registration call: it reads
|
|
285
|
+
// the host's keymap context and creates a layer owned by the component that
|
|
286
|
+
// calls it. The host hands that primitive to plugins unbound, and it runs the
|
|
287
|
+
// plugin's `setup` from a detached async continuation, outside both the Solid
|
|
288
|
+
// owner and the provider tree — so calling it from `setup` throws
|
|
289
|
+
// "Keymap.Provider is missing" and takes the whole TUI feature down with it.
|
|
290
|
+
// Rendering this component instead puts the call inside the provider tree,
|
|
291
|
+
// which is where the contract allows it. The component renders nothing; it
|
|
292
|
+
// exists only to own the layer, and is mounted through the `app` slot below.
|
|
293
|
+
function AftStatusCommands(props: { context: V2TuiContext; rpc: AftRpcClient }) {
|
|
294
|
+
props.context.keymap.layer(() => ({
|
|
295
|
+
// A layer without a mode is limited to the host's base input mode, and the
|
|
296
|
+
// command palette runs as a dialog, which switches the mode away from base.
|
|
297
|
+
// "global" opts out of that limit so the palette entry stays reachable
|
|
298
|
+
// while the palette itself is open.
|
|
299
|
+
mode: "global",
|
|
300
|
+
commands: [
|
|
301
|
+
{
|
|
302
|
+
id: "aft.status",
|
|
303
|
+
title: "AFT: Status",
|
|
304
|
+
description: "Show AFT status, index health, and cache usage",
|
|
305
|
+
group: "AFT",
|
|
306
|
+
palette: true,
|
|
307
|
+
// The host dispatches slash entries unless `arguments: true` is set;
|
|
308
|
+
// omitting it prevents this text from remaining in the prompt or being
|
|
309
|
+
// submitted to the model.
|
|
310
|
+
slash: { name: "aft-status" },
|
|
311
|
+
run: () => showStatusDialog(props.context, props.rpc, activeSessionID(props.context)),
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
bindings: [],
|
|
315
|
+
}));
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
166
319
|
export async function setupV2Tui(context: V2TuiContext): Promise<() => void> {
|
|
167
320
|
const rpc = context.client.rpc(AftRpc);
|
|
168
321
|
const controller = new AbortController();
|
|
169
322
|
const slotCleanups = [
|
|
323
|
+
// `app` is the one slot the host keeps mounted for the whole TUI session,
|
|
324
|
+
// independent of the current route, so a command owned by a component
|
|
325
|
+
// rendered here stays reachable everywhere the palette and slash
|
|
326
|
+
// completion are.
|
|
327
|
+
context.ui.slot({
|
|
328
|
+
append: "app",
|
|
329
|
+
render: () => <AftStatusCommands context={context} rpc={rpc} />,
|
|
330
|
+
}),
|
|
170
331
|
context.ui.slot({
|
|
171
332
|
append: "prompt.footer.status",
|
|
172
333
|
render: (input) => <FooterStatus context={context} rpc={rpc} sessionID={input.sessionID} />,
|
|
@@ -183,24 +344,6 @@ export async function setupV2Tui(context: V2TuiContext): Promise<() => void> {
|
|
|
183
344
|
{ signal: controller.signal },
|
|
184
345
|
);
|
|
185
346
|
|
|
186
|
-
context.keymap.layer(() => ({
|
|
187
|
-
commands: [
|
|
188
|
-
{
|
|
189
|
-
id: "aft.status",
|
|
190
|
-
title: "AFT: Status",
|
|
191
|
-
description: "Show AFT status, index health, and cache usage",
|
|
192
|
-
group: "AFT",
|
|
193
|
-
palette: true,
|
|
194
|
-
// V2's KeymapCommand contract (plugin/tui/context.ts:379-385) dispatches
|
|
195
|
-
// slash entries unless `arguments: true`; omitting it prevents this text
|
|
196
|
-
// from remaining in the prompt or being submitted to the model.
|
|
197
|
-
slash: { name: "aft-status" },
|
|
198
|
-
run: () => showStatusDialog(context, rpc, activeSessionID(context)),
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
bindings: [],
|
|
202
|
-
}));
|
|
203
|
-
|
|
204
347
|
return () => {
|
|
205
348
|
controller.abort();
|
|
206
349
|
stopDialogEvents();
|
|
@@ -467,7 +467,7 @@ const StatusDialog = props => {
|
|
|
467
467
|
},
|
|
468
468
|
label: "Status",
|
|
469
469
|
get value() {
|
|
470
|
-
return formatSemanticIndexStatus(status().semantic_index.status, status().semantic_index.stage);
|
|
470
|
+
return formatSemanticIndexStatus(status().semantic_index.status, status().semantic_index.stage, status().semantic_index.error);
|
|
471
471
|
},
|
|
472
472
|
get tone() {
|
|
473
473
|
return statusTone(status().semantic_index.status);
|