@mirrowel/opencode-subagent-explorer 0.1.0-dev.1
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/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +20 -0
- package/dist/palette-category.d.ts +30 -0
- package/dist/palette-category.js +72 -0
- package/dist/selfwire.d.ts +57 -0
- package/dist/selfwire.js +321 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +16 -0
- package/dist/sessions.d.ts +78 -0
- package/dist/sessions.js +299 -0
- package/dist/tui-host.d.ts +161 -0
- package/dist/tui-host.js +9 -0
- package/dist/tui.d.ts +24 -0
- package/dist/tui.js +1962 -0
- package/dist/v2-server.d.ts +10 -0
- package/dist/v2-server.js +14 -0
- package/dist/v2-tui.d.ts +24 -0
- package/dist/v2-tui.js +357 -0
- package/dist/v2-types.d.ts +270 -0
- package/dist/v2-types.js +10 -0
- package/dist/wizard.d.ts +67 -0
- package/dist/wizard.js +1580 -0
- package/package.json +97 -0
- package/src/index.ts +23 -0
- package/src/palette-category.ts +83 -0
- package/src/selfwire.ts +311 -0
- package/src/server.ts +17 -0
- package/src/sessions.ts +318 -0
- package/src/tui-host.ts +154 -0
- package/src/tui.tsx +75 -0
- package/src/v2-server.ts +17 -0
- package/src/v2-tui.ts +360 -0
- package/src/v2-types.ts +243 -0
- package/src/wizard.tsx +844 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode v2 server setup for subagent-explorer.
|
|
3
|
+
*
|
|
4
|
+
* Dormant by design: the explorer is TUI-driven and needs no server-side
|
|
5
|
+
* hooks. v2 auto-discovers the ./tui export from server-declared plugins,
|
|
6
|
+
* so this setup only exists to make the plugin loadable from the v2
|
|
7
|
+
* `plugins` config (and to keep the dual-target entry shape uniform).
|
|
8
|
+
*/
|
|
9
|
+
import type { V2ServerSetup } from "./v2-types.js";
|
|
10
|
+
export declare function createV2ServerSetup(): V2ServerSetup;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode v2 server setup for subagent-explorer.
|
|
3
|
+
*
|
|
4
|
+
* Dormant by design: the explorer is TUI-driven and needs no server-side
|
|
5
|
+
* hooks. v2 auto-discovers the ./tui export from server-declared plugins,
|
|
6
|
+
* so this setup only exists to make the plugin loadable from the v2
|
|
7
|
+
* `plugins` config (and to keep the dual-target entry shape uniform).
|
|
8
|
+
*/
|
|
9
|
+
export function createV2ServerSetup() {
|
|
10
|
+
return async (_context) => {
|
|
11
|
+
// Intentionally empty: no transforms, no hooks, no registrations.
|
|
12
|
+
return () => undefined;
|
|
13
|
+
};
|
|
14
|
+
}
|
package/dist/v2-tui.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode v2 TUI plugin implementation: builds a `TuiHostApi` adapter over
|
|
3
|
+
* the v2 TUI plugin context so the unchanged explorer wizard (wizard.tsx)
|
|
4
|
+
* runs on both hosts, and registers the palette/slash command.
|
|
5
|
+
*
|
|
6
|
+
* Dialog bridging: the v1 api exposes dialog components (DialogSelect, …)
|
|
7
|
+
* that RETURN JSX rendered inside an enclosing `dialog.replace(render)`
|
|
8
|
+
* entry. The v2 api only offers promise-based dialogs that open their own
|
|
9
|
+
* host entry. The bridge reconciles the two: `replace()` invokes the render
|
|
10
|
+
* thunk synchronously — if it returns renderable content the v2 dialog shows
|
|
11
|
+
* it as one entry with the caller's onClose; if it returned nothing (because
|
|
12
|
+
* a DialogX bridge already opened the host's own dialog), the caller's
|
|
13
|
+
* onClose is parked and invoked when that nested dialog is dismissed without
|
|
14
|
+
* a result (esc), which is exactly when v1 would have fired it.
|
|
15
|
+
*/
|
|
16
|
+
import type { TuiHostApi } from "./tui-host.js";
|
|
17
|
+
import type { V2TuiContext, V2TuiSetup } from "./v2-types.js";
|
|
18
|
+
/** Builds the v1-shaped `TuiHostApi` facade over a v2 TUI plugin context. */
|
|
19
|
+
export declare function buildV2HostApi(context: V2TuiContext): TuiHostApi;
|
|
20
|
+
/**
|
|
21
|
+
* v2 TUI plugin setup: registers the palette/slash command (one reactive
|
|
22
|
+
* keymap layer).
|
|
23
|
+
*/
|
|
24
|
+
export declare function createV2TuiSetup(): V2TuiSetup;
|
package/dist/v2-tui.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode v2 TUI plugin implementation: builds a `TuiHostApi` adapter over
|
|
3
|
+
* the v2 TUI plugin context so the unchanged explorer wizard (wizard.tsx)
|
|
4
|
+
* runs on both hosts, and registers the palette/slash command.
|
|
5
|
+
*
|
|
6
|
+
* Dialog bridging: the v1 api exposes dialog components (DialogSelect, …)
|
|
7
|
+
* that RETURN JSX rendered inside an enclosing `dialog.replace(render)`
|
|
8
|
+
* entry. The v2 api only offers promise-based dialogs that open their own
|
|
9
|
+
* host entry. The bridge reconciles the two: `replace()` invokes the render
|
|
10
|
+
* thunk synchronously — if it returns renderable content the v2 dialog shows
|
|
11
|
+
* it as one entry with the caller's onClose; if it returned nothing (because
|
|
12
|
+
* a DialogX bridge already opened the host's own dialog), the caller's
|
|
13
|
+
* onClose is parked and invoked when that nested dialog is dismissed without
|
|
14
|
+
* a result (esc), which is exactly when v1 would have fired it.
|
|
15
|
+
*/
|
|
16
|
+
import { createSignal } from "solid-js";
|
|
17
|
+
import { declarePaletteCategory, schedulePaletteReconcile } from "./palette-category.js";
|
|
18
|
+
import { mainMenu } from "./wizard.js";
|
|
19
|
+
function pickThemeToken(source, ...path) {
|
|
20
|
+
let current = source;
|
|
21
|
+
for (const key of path) {
|
|
22
|
+
if (!current || typeof current !== "object")
|
|
23
|
+
return undefined;
|
|
24
|
+
current = current[key];
|
|
25
|
+
}
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The wire `location` query uses `workspace`; the plugin-facing LocationRef
|
|
30
|
+
* uses `workspaceID`. Translate at the boundary.
|
|
31
|
+
*/
|
|
32
|
+
function wireLocation(ref) {
|
|
33
|
+
if (!ref)
|
|
34
|
+
return undefined;
|
|
35
|
+
return ref.workspaceID !== undefined ? { directory: ref.directory, workspace: ref.workspaceID } : { directory: ref.directory };
|
|
36
|
+
}
|
|
37
|
+
/** Wraps the v2 client so session.* calls carry the current location query
|
|
38
|
+
* (v2 generated methods are flat; absent location keys default to the TUI's
|
|
39
|
+
* own location, which is what the explorer wants). */
|
|
40
|
+
function withLocationQualifiedSessions(client, location) {
|
|
41
|
+
if (!client || typeof client !== "object")
|
|
42
|
+
return client;
|
|
43
|
+
const session = client.session;
|
|
44
|
+
if (!session || typeof session !== "object")
|
|
45
|
+
return client;
|
|
46
|
+
const wrapped = {};
|
|
47
|
+
for (const method of ["list", "active", "status", "messages", "delete", "remove", "get", "wait"]) {
|
|
48
|
+
const fn = session[method];
|
|
49
|
+
if (typeof fn !== "function")
|
|
50
|
+
continue;
|
|
51
|
+
const bound = fn.bind(session);
|
|
52
|
+
wrapped[method] = (input) => {
|
|
53
|
+
const base = input && typeof input === "object" ? input : {};
|
|
54
|
+
const loc = location();
|
|
55
|
+
const merged = { ...loc, ...base };
|
|
56
|
+
return bound(merged);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { ...(client ?? {}), session: { ...session, ...wrapped } };
|
|
60
|
+
}
|
|
61
|
+
function buildV2Theme(context) {
|
|
62
|
+
// Read theme/mode live inside the getter so mid-session theme switches
|
|
63
|
+
// reach the wizard (the host resolves a fresh token set per change).
|
|
64
|
+
return {
|
|
65
|
+
get current() {
|
|
66
|
+
const theme = context.theme;
|
|
67
|
+
const dark = context.themeMode === "dark";
|
|
68
|
+
const text = pickThemeToken(theme, "text", "default");
|
|
69
|
+
return {
|
|
70
|
+
text,
|
|
71
|
+
textMuted: pickThemeToken(theme, "text", "subdued") ?? text,
|
|
72
|
+
background: pickThemeToken(theme, "background", "default"),
|
|
73
|
+
backgroundPanel: pickThemeToken(theme, "background", "surface", "offset") ?? pickThemeToken(theme, "background", "default"),
|
|
74
|
+
primary: pickThemeToken(theme, "text", "action", "primary", "selected") ?? text,
|
|
75
|
+
secondary: pickThemeToken(theme, "hue", dark ? "interactive" : "neutral", dark ? 300 : 700) ?? text,
|
|
76
|
+
accent: pickThemeToken(theme, "hue", "accent", dark ? 200 : 800) ?? text,
|
|
77
|
+
success: pickThemeToken(theme, "text", "feedback", "success", "default") ?? text,
|
|
78
|
+
warning: pickThemeToken(theme, "text", "feedback", "warning", "default") ?? text,
|
|
79
|
+
error: pickThemeToken(theme, "text", "feedback", "error", "default") ?? text,
|
|
80
|
+
info: pickThemeToken(theme, "text", "feedback", "info", "default") ?? text,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function convertKeymapLayer(layer) {
|
|
86
|
+
const bindingsByName = new Map();
|
|
87
|
+
for (const binding of layer.bindings ?? []) {
|
|
88
|
+
if (!binding?.cmd)
|
|
89
|
+
continue;
|
|
90
|
+
const keys = bindingsByName.get(binding.cmd) ?? [];
|
|
91
|
+
keys.push(binding.key);
|
|
92
|
+
bindingsByName.set(binding.cmd, keys);
|
|
93
|
+
}
|
|
94
|
+
const commands = (layer.commands ?? []).map((command) => ({
|
|
95
|
+
id: command.name,
|
|
96
|
+
title: command.title ?? command.name,
|
|
97
|
+
description: command.desc,
|
|
98
|
+
...(bindingsByName.get(command.name)?.length ? { bind: bindingsByName.get(command.name).join(",") } : {}),
|
|
99
|
+
run: (_input, event) => command.run({ event: event ? { preventDefault: () => event.preventDefault?.(), stopPropagation: () => event.stopPropagation?.() } : undefined }),
|
|
100
|
+
}));
|
|
101
|
+
return { mode: "global", priority: layer.priority, commands };
|
|
102
|
+
}
|
|
103
|
+
/** Builds the v1-shaped `TuiHostApi` facade over a v2 TUI plugin context. */
|
|
104
|
+
export function buildV2HostApi(context) {
|
|
105
|
+
const disposeFns = [];
|
|
106
|
+
const locationRef = () => {
|
|
107
|
+
try {
|
|
108
|
+
return context.location
|
|
109
|
+
? context.location
|
|
110
|
+
: context.data.location.default();
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const wireLoc = () => wireLocation(locationRef());
|
|
117
|
+
// --- kv: the explorer has no persisted settings; in-memory stub ---
|
|
118
|
+
const kvMemory = {};
|
|
119
|
+
const kv = {
|
|
120
|
+
get(key, fallback) {
|
|
121
|
+
return (kvMemory[key] === undefined ? fallback : kvMemory[key]);
|
|
122
|
+
},
|
|
123
|
+
set(key, value) {
|
|
124
|
+
kvMemory[key] = value;
|
|
125
|
+
},
|
|
126
|
+
ready: true,
|
|
127
|
+
};
|
|
128
|
+
// --- v1-shaped live state over the v2 data stores ---
|
|
129
|
+
const state = {
|
|
130
|
+
get config() {
|
|
131
|
+
// Hidden agents stay IN the record (flagged): the explorer shows
|
|
132
|
+
// hidden/background subagent sessions, unlike selection UIs.
|
|
133
|
+
const agents = context.data.location.agent.list() ?? [];
|
|
134
|
+
const agentRecord = {};
|
|
135
|
+
for (const agent of agents) {
|
|
136
|
+
agentRecord[agent.id] = {
|
|
137
|
+
name: agent.name,
|
|
138
|
+
mode: agent.mode,
|
|
139
|
+
hidden: agent.hidden,
|
|
140
|
+
color: agent.color,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { agent: agentRecord };
|
|
144
|
+
},
|
|
145
|
+
get provider() {
|
|
146
|
+
return [];
|
|
147
|
+
},
|
|
148
|
+
get path() {
|
|
149
|
+
const dir = locationRef()?.directory ?? process.cwd();
|
|
150
|
+
return { config: dir, directory: dir, worktree: dir };
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
// --- dialog bridging (see module doc comment) ---
|
|
154
|
+
const dialog = context.ui.dialog;
|
|
155
|
+
let nestedOnClose;
|
|
156
|
+
const settleNested = (dismissed) => {
|
|
157
|
+
const onClose = nestedOnClose;
|
|
158
|
+
nestedOnClose = undefined;
|
|
159
|
+
if (dismissed)
|
|
160
|
+
onClose?.();
|
|
161
|
+
};
|
|
162
|
+
const api = {
|
|
163
|
+
hostVersion: 2,
|
|
164
|
+
app: { version: context.app?.version },
|
|
165
|
+
client: withLocationQualifiedSessions(context.client, wireLoc),
|
|
166
|
+
kv,
|
|
167
|
+
lifecycle: {
|
|
168
|
+
onDispose(fn) {
|
|
169
|
+
disposeFns.push(fn);
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
mode: {
|
|
173
|
+
push(mode) {
|
|
174
|
+
try {
|
|
175
|
+
return context.keymap.mode.push(mode);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return () => undefined;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
renderer: {
|
|
183
|
+
get root() {
|
|
184
|
+
return context.renderer?.root;
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
state,
|
|
188
|
+
theme: buildV2Theme(context),
|
|
189
|
+
ui: {
|
|
190
|
+
toast: (input) => {
|
|
191
|
+
try {
|
|
192
|
+
context.ui.toast.show({ title: input.title, message: input.message, variant: input.variant, duration: input.duration });
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
/* toasts must never crash the explorer */
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
dialog: {
|
|
199
|
+
replace(renderer, onClose) {
|
|
200
|
+
nestedOnClose = undefined;
|
|
201
|
+
let content;
|
|
202
|
+
try {
|
|
203
|
+
content = typeof renderer === "function" ? renderer() : renderer;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
content = undefined;
|
|
207
|
+
}
|
|
208
|
+
if (content === undefined || content === null) {
|
|
209
|
+
// A DialogX bridge opened the host's own dialog; esc/dismiss
|
|
210
|
+
// paths settle through it instead of a dialog entry here.
|
|
211
|
+
nestedOnClose = onClose;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const element = content;
|
|
215
|
+
dialog.show(() => element, onClose);
|
|
216
|
+
},
|
|
217
|
+
clear() {
|
|
218
|
+
nestedOnClose = undefined;
|
|
219
|
+
dialog.clear();
|
|
220
|
+
},
|
|
221
|
+
setSize(size) {
|
|
222
|
+
try {
|
|
223
|
+
dialog.set({ size });
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
/* size hints are best-effort */
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
DialogSelect(props) {
|
|
231
|
+
void dialog
|
|
232
|
+
.select({
|
|
233
|
+
title: props.title,
|
|
234
|
+
placeholder: props.placeholder,
|
|
235
|
+
current: props.current,
|
|
236
|
+
options: props.options.map((option) => ({
|
|
237
|
+
title: option.title,
|
|
238
|
+
value: option.value,
|
|
239
|
+
description: option.description,
|
|
240
|
+
category: option.category,
|
|
241
|
+
disabled: option.disabled,
|
|
242
|
+
})),
|
|
243
|
+
})
|
|
244
|
+
.then((value) => {
|
|
245
|
+
if (value === undefined) {
|
|
246
|
+
// v1 fires the enclosing dialog's onClose on esc.
|
|
247
|
+
settleNested(true);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
settleNested(false);
|
|
251
|
+
// v1's onSelect receives the full option object; the v2 dialog
|
|
252
|
+
// resolves with the value, so recover the option it picked.
|
|
253
|
+
const option = props.options.find((item) => item.value === value);
|
|
254
|
+
if (option)
|
|
255
|
+
props.onSelect(option);
|
|
256
|
+
})
|
|
257
|
+
.catch(() => settleNested(true));
|
|
258
|
+
},
|
|
259
|
+
DialogPrompt(props) {
|
|
260
|
+
void dialog
|
|
261
|
+
.prompt({ title: props.title, placeholder: props.placeholder, value: props.value })
|
|
262
|
+
.then((value) => {
|
|
263
|
+
settleNested(value === undefined);
|
|
264
|
+
if (value === undefined)
|
|
265
|
+
props.onCancel?.();
|
|
266
|
+
else
|
|
267
|
+
props.onConfirm(value);
|
|
268
|
+
})
|
|
269
|
+
.catch(() => {
|
|
270
|
+
settleNested(true);
|
|
271
|
+
props.onCancel?.();
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
DialogConfirm(props) {
|
|
275
|
+
void dialog
|
|
276
|
+
.confirm({ title: props.title, message: props.message, label: { confirm: props.confirmLabel } })
|
|
277
|
+
.then((result) => {
|
|
278
|
+
settleNested(result !== true);
|
|
279
|
+
if (result === true)
|
|
280
|
+
props.onConfirm();
|
|
281
|
+
else
|
|
282
|
+
props.onCancel?.();
|
|
283
|
+
})
|
|
284
|
+
.catch(() => {
|
|
285
|
+
settleNested(true);
|
|
286
|
+
props.onCancel?.();
|
|
287
|
+
});
|
|
288
|
+
},
|
|
289
|
+
DialogAlert(props) {
|
|
290
|
+
void dialog
|
|
291
|
+
.alert({ title: props.title, message: props.message })
|
|
292
|
+
.then(() => {
|
|
293
|
+
settleNested(false);
|
|
294
|
+
props.onConfirm?.();
|
|
295
|
+
})
|
|
296
|
+
.catch(() => {
|
|
297
|
+
settleNested(true);
|
|
298
|
+
props.onConfirm?.();
|
|
299
|
+
});
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
keymap: {
|
|
303
|
+
registerLayer(layer) {
|
|
304
|
+
// v2 layers are reactive: the input function re-evaluates when the
|
|
305
|
+
// signal flips, so "unregistering" disables the layer. (The layer
|
|
306
|
+
// computation itself lives until the host disposes the plugin — v2
|
|
307
|
+
// has no per-layer unregister — but a disabled layer is inert.)
|
|
308
|
+
const [active, setActive] = createSignal(true);
|
|
309
|
+
try {
|
|
310
|
+
context.keymap.layer(() => active()
|
|
311
|
+
? convertKeymapLayer(layer)
|
|
312
|
+
: { mode: "global", enabled: false, commands: [], bindings: [] });
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return () => undefined;
|
|
316
|
+
}
|
|
317
|
+
return () => {
|
|
318
|
+
setActive(false);
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
return api;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* v2 TUI plugin setup: registers the palette/slash command (one reactive
|
|
327
|
+
* keymap layer).
|
|
328
|
+
*/
|
|
329
|
+
export function createV2TuiSetup() {
|
|
330
|
+
return async (context) => {
|
|
331
|
+
const api = buildV2HostApi(context);
|
|
332
|
+
const command = {
|
|
333
|
+
id: "subagent-explorer.open",
|
|
334
|
+
title: "Subagent Explorer: Open",
|
|
335
|
+
description: "Explore and delete subagent sessions",
|
|
336
|
+
category: "",
|
|
337
|
+
group: "",
|
|
338
|
+
palette: true,
|
|
339
|
+
slash: { name: "subagent-explorer" },
|
|
340
|
+
run: () => void mainMenu(api),
|
|
341
|
+
};
|
|
342
|
+
command.category = command.group = declarePaletteCategory("Subagent Explorer", command);
|
|
343
|
+
schedulePaletteReconcile();
|
|
344
|
+
const [paletteActive, setPaletteActive] = createSignal(true);
|
|
345
|
+
try {
|
|
346
|
+
context.keymap.layer(() => paletteActive()
|
|
347
|
+
? { mode: "global", commands: [command] }
|
|
348
|
+
: { mode: "global", enabled: false, commands: [] });
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
/* command registration failure must not break the plugin */
|
|
352
|
+
}
|
|
353
|
+
return () => {
|
|
354
|
+
setPaletteActive(false);
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural types for the OpenCode v2 plugin context subset used by
|
|
3
|
+
* this plugin. Hand-written on purpose: the v2 plugin SDK is still beta, the
|
|
4
|
+
* published `@opencode-ai/plugin` npm package resolves to the v1 shape, and a
|
|
5
|
+
* v2 plugin module must not import the plugin SDK at runtime anyway
|
|
6
|
+
* (`Plugin.define` is an identity function — a plain `{ id, setup }` default
|
|
7
|
+
* export is the whole contract). Keep these in sync with the beta repo
|
|
8
|
+
* (packages/plugin/src/promise + src/tui) when the API settles.
|
|
9
|
+
*/
|
|
10
|
+
export type V2ModelRef = {
|
|
11
|
+
providerID: string;
|
|
12
|
+
id: string;
|
|
13
|
+
variant?: string;
|
|
14
|
+
};
|
|
15
|
+
export type V2AgentInfo = {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
model?: V2ModelRef;
|
|
19
|
+
request: {
|
|
20
|
+
settings: Record<string, unknown>;
|
|
21
|
+
headers: Record<string, string>;
|
|
22
|
+
body: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
system?: string | undefined;
|
|
25
|
+
description?: string | undefined;
|
|
26
|
+
mode: "subagent" | "primary" | "all";
|
|
27
|
+
hidden: boolean;
|
|
28
|
+
color?: string | undefined;
|
|
29
|
+
steps?: number | undefined;
|
|
30
|
+
permissions: unknown[];
|
|
31
|
+
};
|
|
32
|
+
export type V2AgentEditor = {
|
|
33
|
+
list(): readonly V2AgentInfo[];
|
|
34
|
+
get(id: string): V2AgentInfo | undefined;
|
|
35
|
+
default(id: string | undefined): void;
|
|
36
|
+
update(id: string, update: (agent: V2AgentInfo) => void): void;
|
|
37
|
+
remove(id: string): void;
|
|
38
|
+
};
|
|
39
|
+
export type V2Registration = {
|
|
40
|
+
dispose: () => Promise<void> | void;
|
|
41
|
+
};
|
|
42
|
+
export type V2Transform<Input> = (callback: (input: Input) => void) => Promise<V2Registration>;
|
|
43
|
+
export type V2ExecuteBeforeEvent = {
|
|
44
|
+
tool: string;
|
|
45
|
+
sessionID: string;
|
|
46
|
+
agent: string;
|
|
47
|
+
messageID: string;
|
|
48
|
+
id: string;
|
|
49
|
+
input: unknown;
|
|
50
|
+
};
|
|
51
|
+
export type V2ExecuteAfterEvent = {
|
|
52
|
+
tool: string;
|
|
53
|
+
sessionID: string;
|
|
54
|
+
agent: string;
|
|
55
|
+
messageID: string;
|
|
56
|
+
id: string;
|
|
57
|
+
input: unknown;
|
|
58
|
+
} & ({
|
|
59
|
+
status: "completed";
|
|
60
|
+
result: {
|
|
61
|
+
output?: unknown;
|
|
62
|
+
content?: unknown;
|
|
63
|
+
metadata?: Record<string, unknown>;
|
|
64
|
+
};
|
|
65
|
+
} | {
|
|
66
|
+
status: "error";
|
|
67
|
+
error: unknown;
|
|
68
|
+
});
|
|
69
|
+
export type V2SystemPart = {
|
|
70
|
+
type: string;
|
|
71
|
+
text?: string;
|
|
72
|
+
};
|
|
73
|
+
export type V2SessionContextEvent = {
|
|
74
|
+
sessionID: string;
|
|
75
|
+
agent: string;
|
|
76
|
+
model: V2ModelRef;
|
|
77
|
+
system: V2SystemPart[];
|
|
78
|
+
messages: unknown[];
|
|
79
|
+
tools: Record<string, unknown>;
|
|
80
|
+
generation: Record<string, unknown>;
|
|
81
|
+
providerOptions: Record<string, unknown>;
|
|
82
|
+
};
|
|
83
|
+
export type V2SessionInfo = {
|
|
84
|
+
id?: string;
|
|
85
|
+
parentID?: string;
|
|
86
|
+
agent?: string;
|
|
87
|
+
model?: V2ModelRef;
|
|
88
|
+
};
|
|
89
|
+
export type V2PluginContext = {
|
|
90
|
+
agent: {
|
|
91
|
+
transform: V2Transform<V2AgentEditor>;
|
|
92
|
+
reload(): Promise<void> | void;
|
|
93
|
+
};
|
|
94
|
+
tool: {
|
|
95
|
+
hook(name: "execute.before", callback: (event: V2ExecuteBeforeEvent) => Promise<void> | void): Promise<V2Registration>;
|
|
96
|
+
hook(name: "execute.after", callback: (event: V2ExecuteAfterEvent) => Promise<void> | void): Promise<V2Registration>;
|
|
97
|
+
};
|
|
98
|
+
session: {
|
|
99
|
+
hook(name: "context", callback: (event: V2SessionContextEvent) => Promise<void> | void): Promise<V2Registration>;
|
|
100
|
+
get(input: {
|
|
101
|
+
sessionID: string;
|
|
102
|
+
}): Promise<unknown>;
|
|
103
|
+
};
|
|
104
|
+
app?: {
|
|
105
|
+
name?: string;
|
|
106
|
+
version?: string;
|
|
107
|
+
channel?: string;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
export type V2ServerSetup = (context: V2PluginContext) => Promise<(() => void) | void> | (() => void) | void;
|
|
111
|
+
export type V2TuiKeyEvent = {
|
|
112
|
+
preventDefault?(): void;
|
|
113
|
+
stopPropagation?(): void;
|
|
114
|
+
[key: string]: unknown;
|
|
115
|
+
};
|
|
116
|
+
export type V2KeymapCommand = {
|
|
117
|
+
id?: string;
|
|
118
|
+
title?: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
group?: string;
|
|
121
|
+
enabled?: boolean | (() => boolean);
|
|
122
|
+
bind?: false | string;
|
|
123
|
+
palette?: true;
|
|
124
|
+
slash?: {
|
|
125
|
+
name: string;
|
|
126
|
+
aliases?: string[];
|
|
127
|
+
arguments?: true;
|
|
128
|
+
};
|
|
129
|
+
suggested?: boolean | (() => boolean);
|
|
130
|
+
run: (input?: string, event?: V2TuiKeyEvent) => void | false | Promise<void>;
|
|
131
|
+
};
|
|
132
|
+
export type V2KeymapLayer = {
|
|
133
|
+
mode?: string;
|
|
134
|
+
enabled?: boolean | (() => boolean);
|
|
135
|
+
priority?: number;
|
|
136
|
+
commands?: readonly V2KeymapCommand[];
|
|
137
|
+
bindings?: readonly string[];
|
|
138
|
+
};
|
|
139
|
+
export type V2DialogSelectOption<Value> = {
|
|
140
|
+
title: string;
|
|
141
|
+
value: Value;
|
|
142
|
+
description?: string;
|
|
143
|
+
category?: string;
|
|
144
|
+
disabled?: boolean;
|
|
145
|
+
};
|
|
146
|
+
export type V2LocationRef = {
|
|
147
|
+
directory: string;
|
|
148
|
+
workspaceID?: string;
|
|
149
|
+
};
|
|
150
|
+
export type V2AgentListEntry = V2AgentInfo;
|
|
151
|
+
export type V2ProviderListEntry = {
|
|
152
|
+
id: string;
|
|
153
|
+
name: string;
|
|
154
|
+
activation?: string;
|
|
155
|
+
canonical?: string;
|
|
156
|
+
};
|
|
157
|
+
export type V2ModelListEntry = {
|
|
158
|
+
id?: string;
|
|
159
|
+
modelID?: string;
|
|
160
|
+
providerID: string;
|
|
161
|
+
name?: string;
|
|
162
|
+
variants?: Array<{
|
|
163
|
+
id: string;
|
|
164
|
+
} | string>;
|
|
165
|
+
enabled?: boolean;
|
|
166
|
+
status?: string;
|
|
167
|
+
};
|
|
168
|
+
export type V2TuiContext = {
|
|
169
|
+
options: Readonly<Record<string, unknown>>;
|
|
170
|
+
location?: unknown;
|
|
171
|
+
app: {
|
|
172
|
+
version?: string;
|
|
173
|
+
channel?: string;
|
|
174
|
+
};
|
|
175
|
+
renderer: {
|
|
176
|
+
root?: unknown;
|
|
177
|
+
};
|
|
178
|
+
client: any;
|
|
179
|
+
data: {
|
|
180
|
+
location: {
|
|
181
|
+
default(): V2LocationRef;
|
|
182
|
+
sync(location?: V2LocationRef): Promise<void>;
|
|
183
|
+
invalidate(location?: V2LocationRef): void;
|
|
184
|
+
agent: V2LocationCollection<V2AgentListEntry>;
|
|
185
|
+
provider: V2LocationCollection<V2ProviderListEntry>;
|
|
186
|
+
model: V2LocationCollection<V2ModelListEntry>;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
keymap: {
|
|
190
|
+
layer(input: () => V2KeymapLayer): void;
|
|
191
|
+
mode: {
|
|
192
|
+
current(): string;
|
|
193
|
+
push(mode: string): () => void;
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
storage: {
|
|
197
|
+
store<Value extends object>(key: string, options: {
|
|
198
|
+
initial: Value;
|
|
199
|
+
}): readonly [Value, (mutation: (draft: Value) => void) => Promise<void>];
|
|
200
|
+
};
|
|
201
|
+
theme: V2ResolvedTheme;
|
|
202
|
+
themeMode: "dark" | "light";
|
|
203
|
+
ui: {
|
|
204
|
+
dialog: {
|
|
205
|
+
show(render: () => unknown, onClose?: () => void): void;
|
|
206
|
+
set(options: {
|
|
207
|
+
size?: string;
|
|
208
|
+
centered?: boolean;
|
|
209
|
+
}): void;
|
|
210
|
+
clear(): void;
|
|
211
|
+
alert(options: {
|
|
212
|
+
title: string;
|
|
213
|
+
message: string;
|
|
214
|
+
}): Promise<void>;
|
|
215
|
+
confirm(options: {
|
|
216
|
+
title: string;
|
|
217
|
+
message: string;
|
|
218
|
+
label?: {
|
|
219
|
+
confirm?: string;
|
|
220
|
+
cancel?: string;
|
|
221
|
+
};
|
|
222
|
+
}): Promise<boolean | undefined>;
|
|
223
|
+
prompt(options: {
|
|
224
|
+
title: string;
|
|
225
|
+
description?: string;
|
|
226
|
+
placeholder?: string;
|
|
227
|
+
value?: string;
|
|
228
|
+
}): Promise<string | undefined>;
|
|
229
|
+
select<Value>(options: {
|
|
230
|
+
title: string;
|
|
231
|
+
placeholder?: string;
|
|
232
|
+
current?: Value;
|
|
233
|
+
options: readonly V2DialogSelectOption<Value>[];
|
|
234
|
+
}): Promise<Value | undefined>;
|
|
235
|
+
};
|
|
236
|
+
toast: {
|
|
237
|
+
show(options: {
|
|
238
|
+
title?: string;
|
|
239
|
+
message: string;
|
|
240
|
+
variant?: string;
|
|
241
|
+
duration?: number;
|
|
242
|
+
}): void;
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
export type V2LocationCollection<Value> = {
|
|
247
|
+
list(location?: V2LocationRef): readonly Value[] | undefined;
|
|
248
|
+
sync(location?: V2LocationRef): Promise<void>;
|
|
249
|
+
invalidate(location?: V2LocationRef): void;
|
|
250
|
+
};
|
|
251
|
+
export type V2ResolvedTheme = {
|
|
252
|
+
text: {
|
|
253
|
+
default: unknown;
|
|
254
|
+
subdued: unknown;
|
|
255
|
+
feedback: Record<string, {
|
|
256
|
+
default: unknown;
|
|
257
|
+
}>;
|
|
258
|
+
action: Record<string, Record<string, unknown>>;
|
|
259
|
+
};
|
|
260
|
+
background: {
|
|
261
|
+
default: unknown;
|
|
262
|
+
surface: {
|
|
263
|
+
offset: unknown;
|
|
264
|
+
overlay: unknown;
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
hue: Record<string, Record<string | number, unknown>>;
|
|
268
|
+
categorical?: ReadonlyArray<unknown>;
|
|
269
|
+
};
|
|
270
|
+
export type V2TuiSetup = (context: V2TuiContext) => Promise<(() => void) | void> | (() => void) | void;
|
package/dist/v2-types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural types for the OpenCode v2 plugin context subset used by
|
|
3
|
+
* this plugin. Hand-written on purpose: the v2 plugin SDK is still beta, the
|
|
4
|
+
* published `@opencode-ai/plugin` npm package resolves to the v1 shape, and a
|
|
5
|
+
* v2 plugin module must not import the plugin SDK at runtime anyway
|
|
6
|
+
* (`Plugin.define` is an identity function — a plain `{ id, setup }` default
|
|
7
|
+
* export is the whole contract). Keep these in sync with the beta repo
|
|
8
|
+
* (packages/plugin/src/promise + src/tui) when the API settles.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|