@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.
Files changed (39) hide show
  1. package/dist/bg-notifications.d.ts +1 -0
  2. package/dist/bg-notifications.d.ts.map +1 -1
  3. package/dist/entry/server.js +2650 -267
  4. package/dist/index.js +420 -228
  5. package/dist/lsp-github-install.d.ts.map +1 -1
  6. package/dist/permissions/v2-service.d.ts +30 -0
  7. package/dist/permissions/v2-service.d.ts.map +1 -0
  8. package/dist/permissions/v2.d.ts +121 -9
  9. package/dist/permissions/v2.d.ts.map +1 -1
  10. package/dist/shared/rpc-server.d.ts.map +1 -1
  11. package/dist/shared/status.d.ts +8 -1
  12. package/dist/shared/status.d.ts.map +1 -1
  13. package/dist/subc-tool-schemas.d.ts +11 -0
  14. package/dist/subc-tool-schemas.d.ts.map +1 -1
  15. package/dist/tool-registration.d.ts.map +1 -1
  16. package/dist/tools/definitions/v2.d.ts +6 -0
  17. package/dist/tools/definitions/v2.d.ts.map +1 -1
  18. package/dist/tools/hoisted/v2.d.ts +12 -2
  19. package/dist/tools/hoisted/v2.d.ts.map +1 -1
  20. package/dist/tools/hoisted.d.ts.map +1 -1
  21. package/dist/tools/semantic.d.ts.map +1 -1
  22. package/dist/tui/v2-status.d.ts +8 -0
  23. package/dist/tui/v2-status.d.ts.map +1 -1
  24. package/index.js +14 -0
  25. package/package.json +14 -10
  26. package/server.js +8 -0
  27. package/src/shared/rpc-server.ts +5 -0
  28. package/src/shared/status.ts +16 -9
  29. package/src/tui/index.tsx +1 -0
  30. package/src/tui/sidebar-view.tsx +722 -0
  31. package/src/tui/sidebar.tsx +42 -560
  32. package/src/tui/v2-status.ts +8 -1
  33. package/src/tui/v2.tsx +173 -30
  34. package/src/tui-compiled/index.tsx +1 -1
  35. package/src/tui-compiled/sidebar-view.tsx +815 -0
  36. package/src/tui-compiled/sidebar.tsx +45 -693
  37. package/src/tui-compiled/v2-status.ts +8 -1
  38. package/src/tui-compiled/v2.tsx +163 -54
  39. 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 {
@@ -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 { formatAftStatusSegment, summarizeAftSidebar } from "./v2-status";
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 summary = () => summarizeAftSidebar(status());
85
- return (() => {
86
- var _el$2 = _$createElement("box"),
87
- _el$3 = _$createElement("text"),
88
- _el$4 = _$createElement("b"),
89
- _el$5 = _$createElement("text"),
90
- _el$6 = _$createTextNode(`Search: `),
91
- _el$7 = _$createElement("text"),
92
- _el$8 = _$createTextNode(`Semantic: `),
93
- _el$9 = _$createElement("text"),
94
- _el$0 = _$createTextNode(`Health: `);
95
- _$insertNode(_el$2, _el$3);
96
- _$insertNode(_el$2, _el$5);
97
- _$insertNode(_el$2, _el$7);
98
- _$insertNode(_el$2, _el$9);
99
- _$setProp(_el$2, "width", "100%");
100
- _$setProp(_el$2, "flexDirection", "column");
101
- _$insertNode(_el$3, _el$4);
102
- _$insert(_el$4, () => summary().title);
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 = [context.ui.slot({
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";