@cortexkit/aft-opencode 0.56.1 → 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/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/entry/server.js +2682 -282
- package/dist/index.js +448 -244
- 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/entry/server-runtime.mjs +11 -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
|
@@ -22,7 +22,6 @@ export type AftSidebarSummary = {
|
|
|
22
22
|
semantic: string;
|
|
23
23
|
health: string;
|
|
24
24
|
};
|
|
25
|
-
|
|
26
25
|
function healthSummary(bar: StatusBar | undefined): string {
|
|
27
26
|
if (!bar) return "health pending";
|
|
28
27
|
const stale = bar.tier2_stale ? "~" : "";
|
|
@@ -33,6 +32,14 @@ function healthSummary(bar: StatusBar | undefined): string {
|
|
|
33
32
|
);
|
|
34
33
|
}
|
|
35
34
|
|
|
35
|
+
/**
|
|
36
|
+
* A one-line-per-field digest of the status snapshot.
|
|
37
|
+
*
|
|
38
|
+
* This is no longer what the sidebar draws: the sidebar now renders the shared
|
|
39
|
+
* panel in ./sidebar-view, which labels each section and reuses the status
|
|
40
|
+
* wording ./sidebar.tsx established. The digest is still pinned by
|
|
41
|
+
* test/tui/v2-status.test.ts.
|
|
42
|
+
*/
|
|
36
43
|
export function summarizeAftSidebar(status: AftStatusSnapshot | null): AftSidebarSummary {
|
|
37
44
|
if (!status || status.cache_role === "not_initialized") {
|
|
38
45
|
return {
|
package/src/tui-compiled/v2.tsx
CHANGED
|
@@ -1,17 +1,109 @@
|
|
|
1
1
|
import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
2
|
-
import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
3
|
-
import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
4
|
-
import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
5
|
-
import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
6
2
|
import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
7
3
|
import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
|
|
8
4
|
/** @jsxImportSource @opentui/solid */
|
|
9
5
|
// @ts-nocheck
|
|
10
6
|
|
|
11
7
|
import { createEffect, createSignal, onCleanup } from "opentui:runtime-module:solid-js";
|
|
8
|
+
import { version as packageVersion } from "../../package.json";
|
|
12
9
|
import { coerceAftStatus, formatStatusDialogMessage } from "../shared/status";
|
|
13
10
|
import { AftRpc } from "./aft-rpc";
|
|
14
|
-
import {
|
|
11
|
+
import { AftSidebarPanel, createSidebarPreferences } from "./sidebar-view";
|
|
12
|
+
import { formatAftStatusSegment } from "./v2-status";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The colour tokens the sidebar needs out of the host's `ResolvedTheme` — the
|
|
16
|
+
* type `Context.theme` carries, exported by `@opencode/theme/tui`. That
|
|
17
|
+
* package is an optional peer of `@opencode/plugin` and is not installed
|
|
18
|
+
* alongside this plugin, so the shape is written out here instead of imported;
|
|
19
|
+
* the key names are the theme package's own. They are nested token groups,
|
|
20
|
+
* unlike the flat colour names the slot-plugin host in ./sidebar.tsx reads.
|
|
21
|
+
* Every level is optional so a host that ships a partial theme degrades to the
|
|
22
|
+
* fallback palette below instead of throwing during a render.
|
|
23
|
+
*
|
|
24
|
+
* What a host really hands over is recorded, token for token, in
|
|
25
|
+
* test/load-matrix/ga-host-theme.json; that record is what this type and the
|
|
26
|
+
* mapping below are written against, and the load matrix compares it to a live
|
|
27
|
+
* host on every run.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* One step of a hue scale. The steps are ordered by how far the shade stands
|
|
32
|
+
* off the page rather than by lightness: 100 contrasts with the page most and
|
|
33
|
+
* 900 blends into it, in both theme modes. So the host's dark theme resolves
|
|
34
|
+
* accent 100 to its lightest purple and its light theme resolves accent 100 to
|
|
35
|
+
* its darkest orange, and a step chosen for one mode keeps its role in the
|
|
36
|
+
* other.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The accent shade the AFT badge is filled with. A low step is what keeps the
|
|
41
|
+
* fill standing off the page in both theme modes (see GaHueStep), and 200 is
|
|
42
|
+
* the step the host itself draws its own accent-coloured UI in.
|
|
43
|
+
*/
|
|
44
|
+
const BADGE_ACCENT_STEP = "200";
|
|
45
|
+
// Last resort for a host that declares a theme mode but hands over no resolved
|
|
46
|
+
// theme. Inheriting the terminal default instead is what made the panel white
|
|
47
|
+
// on white in light mode, so there has to be a legible colour for every role
|
|
48
|
+
// even when the theme is missing.
|
|
49
|
+
const FALLBACK_PALETTES = {
|
|
50
|
+
dark: {
|
|
51
|
+
text: "#e6edf3",
|
|
52
|
+
textMuted: "#9198a1",
|
|
53
|
+
success: "#3fb950",
|
|
54
|
+
warning: "#d29922",
|
|
55
|
+
error: "#f85149",
|
|
56
|
+
accent: "#1f6feb",
|
|
57
|
+
background: "#0d1117",
|
|
58
|
+
border: "#3d444d"
|
|
59
|
+
},
|
|
60
|
+
light: {
|
|
61
|
+
text: "#1f2328",
|
|
62
|
+
textMuted: "#59636e",
|
|
63
|
+
success: "#1a7f37",
|
|
64
|
+
warning: "#9a6700",
|
|
65
|
+
error: "#cf222e",
|
|
66
|
+
accent: "#0969da",
|
|
67
|
+
background: "#ffffff",
|
|
68
|
+
border: "#d1d9e0"
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Host theme → the panel's palette. The host resolves its theme for the active
|
|
74
|
+
* mode before handing it over, so light mode arrives as light colours here;
|
|
75
|
+
* what matters is that every text line asks for one of them instead of
|
|
76
|
+
* inheriting the terminal default.
|
|
77
|
+
*
|
|
78
|
+
* `success` falls back to the accent exactly as ./sidebar.tsx's mapping does.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveV2Palette(theme, themeMode) {
|
|
81
|
+
const fallback = FALLBACK_PALETTES[themeMode === "light" ? "light" : "dark"];
|
|
82
|
+
if (!theme) return {
|
|
83
|
+
...fallback
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// The accent comes out of the hue scales, not out of `background.action`.
|
|
87
|
+
// The action groups describe how the host paints a button, and this theme
|
|
88
|
+
// paints one as bare text: its `background.action.primary.base` is a fully
|
|
89
|
+
// transparent colour. Filling the badge with that drew no badge at all, and
|
|
90
|
+
// nothing caught it, because a present-but-transparent colour never reaches
|
|
91
|
+
// the fallback below.
|
|
92
|
+
const accent = theme.hue?.accent?.[BADGE_ACCENT_STEP] ?? fallback.accent;
|
|
93
|
+
const feedback = theme.text?.feedback;
|
|
94
|
+
return {
|
|
95
|
+
text: theme.text?.base ?? fallback.text,
|
|
96
|
+
textMuted: theme.text?.muted ?? fallback.textMuted,
|
|
97
|
+
success: feedback?.success?.base ?? accent,
|
|
98
|
+
warning: feedback?.warning?.base ?? fallback.warning,
|
|
99
|
+
error: feedback?.error?.base ?? fallback.error,
|
|
100
|
+
accent,
|
|
101
|
+
background: theme.background?.base ?? fallback.background,
|
|
102
|
+
// This theme has one border colour; ./sidebar.tsx's host distinguishes an
|
|
103
|
+
// active one, and the panel only ever draws the active border.
|
|
104
|
+
border: theme.border?.base ?? fallback.border
|
|
105
|
+
};
|
|
106
|
+
}
|
|
15
107
|
function eventMatchesSession(eventSessionID, sessionID) {
|
|
16
108
|
return eventSessionID === undefined || eventSessionID === sessionID;
|
|
17
109
|
}
|
|
@@ -81,37 +173,25 @@ function FooterStatus(props) {
|
|
|
81
173
|
}
|
|
82
174
|
function SidebarStatus(props) {
|
|
83
175
|
const status = useAftStatus(props.context, props.rpc, () => props.sessionID);
|
|
84
|
-
const
|
|
85
|
-
return (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
_$insert(_el$3, (() => {
|
|
104
|
-
var _c$ = _$memo(() => !!summary().version);
|
|
105
|
-
return () => _c$() ? ` v${summary().version}` : "";
|
|
106
|
-
})(), null);
|
|
107
|
-
_$insertNode(_el$5, _el$6);
|
|
108
|
-
_$insert(_el$5, () => summary().search, null);
|
|
109
|
-
_$insertNode(_el$7, _el$8);
|
|
110
|
-
_$insert(_el$7, () => summary().semantic, null);
|
|
111
|
-
_$insertNode(_el$9, _el$0);
|
|
112
|
-
_$insert(_el$9, () => summary().health, null);
|
|
113
|
-
return _el$2;
|
|
114
|
-
})();
|
|
176
|
+
const preferences = createSidebarPreferences();
|
|
177
|
+
return _$createComponent(AftSidebarPanel, {
|
|
178
|
+
get palette() {
|
|
179
|
+
return resolveV2Palette(props.context.theme, props.context.themeMode);
|
|
180
|
+
},
|
|
181
|
+
get snapshot() {
|
|
182
|
+
return status();
|
|
183
|
+
},
|
|
184
|
+
get prefs() {
|
|
185
|
+
return preferences.prefs();
|
|
186
|
+
},
|
|
187
|
+
get collapsed() {
|
|
188
|
+
return preferences.collapsed();
|
|
189
|
+
},
|
|
190
|
+
get onToggleCollapsed() {
|
|
191
|
+
return preferences.toggleCollapsed;
|
|
192
|
+
},
|
|
193
|
+
pluginVersion: packageVersion
|
|
194
|
+
});
|
|
115
195
|
}
|
|
116
196
|
function activeSessionID(context) {
|
|
117
197
|
const route = context.ui.router.current();
|
|
@@ -131,10 +211,56 @@ async function showStatusDialog(context, rpc, sessionID) {
|
|
|
131
211
|
});
|
|
132
212
|
}
|
|
133
213
|
}
|
|
214
|
+
|
|
215
|
+
// `keymap.layer` is a Solid primitive, not a plain registration call: it reads
|
|
216
|
+
// the host's keymap context and creates a layer owned by the component that
|
|
217
|
+
// calls it. The host hands that primitive to plugins unbound, and it runs the
|
|
218
|
+
// plugin's `setup` from a detached async continuation, outside both the Solid
|
|
219
|
+
// owner and the provider tree — so calling it from `setup` throws
|
|
220
|
+
// "Keymap.Provider is missing" and takes the whole TUI feature down with it.
|
|
221
|
+
// Rendering this component instead puts the call inside the provider tree,
|
|
222
|
+
// which is where the contract allows it. The component renders nothing; it
|
|
223
|
+
// exists only to own the layer, and is mounted through the `app` slot below.
|
|
224
|
+
function AftStatusCommands(props) {
|
|
225
|
+
props.context.keymap.layer(() => ({
|
|
226
|
+
// A layer without a mode is limited to the host's base input mode, and the
|
|
227
|
+
// command palette runs as a dialog, which switches the mode away from base.
|
|
228
|
+
// "global" opts out of that limit so the palette entry stays reachable
|
|
229
|
+
// while the palette itself is open.
|
|
230
|
+
mode: "global",
|
|
231
|
+
commands: [{
|
|
232
|
+
id: "aft.status",
|
|
233
|
+
title: "AFT: Status",
|
|
234
|
+
description: "Show AFT status, index health, and cache usage",
|
|
235
|
+
group: "AFT",
|
|
236
|
+
palette: true,
|
|
237
|
+
// The host dispatches slash entries unless `arguments: true` is set;
|
|
238
|
+
// omitting it prevents this text from remaining in the prompt or being
|
|
239
|
+
// submitted to the model.
|
|
240
|
+
slash: {
|
|
241
|
+
name: "aft-status"
|
|
242
|
+
},
|
|
243
|
+
run: () => showStatusDialog(props.context, props.rpc, activeSessionID(props.context))
|
|
244
|
+
}],
|
|
245
|
+
bindings: []
|
|
246
|
+
}));
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
134
249
|
export async function setupV2Tui(context) {
|
|
135
250
|
const rpc = context.client.rpc(AftRpc);
|
|
136
251
|
const controller = new AbortController();
|
|
137
|
-
const slotCleanups = [
|
|
252
|
+
const slotCleanups = [
|
|
253
|
+
// `app` is the one slot the host keeps mounted for the whole TUI session,
|
|
254
|
+
// independent of the current route, so a command owned by a component
|
|
255
|
+
// rendered here stays reachable everywhere the palette and slash
|
|
256
|
+
// completion are.
|
|
257
|
+
context.ui.slot({
|
|
258
|
+
append: "app",
|
|
259
|
+
render: () => _$createComponent(AftStatusCommands, {
|
|
260
|
+
context: context,
|
|
261
|
+
rpc: rpc
|
|
262
|
+
})
|
|
263
|
+
}), context.ui.slot({
|
|
138
264
|
append: "prompt.footer.status",
|
|
139
265
|
render: input => _$createComponent(FooterStatus, {
|
|
140
266
|
context: context,
|
|
@@ -156,23 +282,6 @@ export async function setupV2Tui(context) {
|
|
|
156
282
|
const stopDialogEvents = rpc.events.on("showStatusDialog", event => showStatusDialog(context, rpc, event.data.sessionID ?? activeSessionID(context)), {
|
|
157
283
|
signal: controller.signal
|
|
158
284
|
});
|
|
159
|
-
context.keymap.layer(() => ({
|
|
160
|
-
commands: [{
|
|
161
|
-
id: "aft.status",
|
|
162
|
-
title: "AFT: Status",
|
|
163
|
-
description: "Show AFT status, index health, and cache usage",
|
|
164
|
-
group: "AFT",
|
|
165
|
-
palette: true,
|
|
166
|
-
// V2's KeymapCommand contract (plugin/tui/context.ts:379-385) dispatches
|
|
167
|
-
// slash entries unless `arguments: true`; omitting it prevents this text
|
|
168
|
-
// from remaining in the prompt or being submitted to the model.
|
|
169
|
-
slash: {
|
|
170
|
-
name: "aft-status"
|
|
171
|
-
},
|
|
172
|
-
run: () => showStatusDialog(context, rpc, activeSessionID(context))
|
|
173
|
-
}],
|
|
174
|
-
bindings: []
|
|
175
|
-
}));
|
|
176
285
|
return () => {
|
|
177
286
|
controller.abort();
|
|
178
287
|
stopDialogEvents();
|
package/tui.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Root TUI entrypoint for directory-path plugin loading.
|
|
2
|
+
//
|
|
3
|
+
// OpenCode 2 resolves the TUI feature at `<dir>/tui` when the plugin target is
|
|
4
|
+
// a directory, the same literal join it uses for `server` and `index`, and
|
|
5
|
+
// with the same silent skip when the file is absent — the sidebar simply never
|
|
6
|
+
// appears. See index.js for why the exports map does not cover this shape.
|
|
7
|
+
export { default } from "./src/entry/tui.mjs";
|