opencode-subagent-magazine 1.5.2 → 1.6.0-beta.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.
Files changed (60) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +192 -192
  3. package/dist/_version.d.ts +1 -1
  4. package/dist/_version.js +1 -1
  5. package/dist/core/color.d.ts +20 -0
  6. package/dist/core/color.js +68 -0
  7. package/dist/core/format.d.ts +5 -0
  8. package/dist/core/format.js +68 -0
  9. package/dist/core/index.d.ts +6 -0
  10. package/dist/core/index.js +6 -0
  11. package/dist/core/kv.d.ts +20 -0
  12. package/dist/core/kv.js +30 -0
  13. package/dist/core/state-machine.d.ts +17 -0
  14. package/dist/core/state-machine.js +27 -0
  15. package/dist/core/types.d.ts +51 -0
  16. package/dist/core/types.js +2 -0
  17. package/dist/core/usage.d.ts +15 -0
  18. package/dist/core/usage.js +1 -0
  19. package/dist/index.js +148 -1484
  20. package/dist/panel/SubAgentPanel.d.ts +13 -0
  21. package/dist/panel/SubAgentPanel.js +1237 -0
  22. package/dist/panel/panel-api.d.ts +67 -0
  23. package/dist/panel/panel-api.js +1 -0
  24. package/dist/panel/store.d.ts +5 -0
  25. package/dist/panel/store.js +5 -0
  26. package/dist/tui.js +388 -292
  27. package/dist/v2/commands.d.ts +7 -0
  28. package/dist/v2/commands.js +263 -0
  29. package/dist/v2/index.d.ts +8 -0
  30. package/dist/v2/index.js +62 -0
  31. package/dist/v2/theme.d.ts +3 -0
  32. package/dist/v2/theme.js +12 -0
  33. package/dist/v2/types.d.ts +231 -0
  34. package/dist/v2/types.js +6 -0
  35. package/dist/v2/v2-panel-api.d.ts +12 -0
  36. package/dist/v2/v2-panel-api.js +350 -0
  37. package/dist/v2.js +3008 -0
  38. package/install.mjs +103 -103
  39. package/package.json +64 -63
  40. package/src/_version.ts +1 -1
  41. package/src/clipboard.ts +134 -134
  42. package/src/core/color.ts +70 -0
  43. package/src/core/format.ts +61 -0
  44. package/src/core/index.ts +6 -0
  45. package/src/core/kv.ts +36 -0
  46. package/src/core/state-machine.ts +46 -0
  47. package/src/core/types.ts +58 -0
  48. package/src/core/usage.ts +16 -0
  49. package/src/i18n.ts +261 -261
  50. package/src/index.tsx +124 -1750
  51. package/src/panel/SubAgentPanel.tsx +1465 -0
  52. package/src/panel/panel-api.ts +72 -0
  53. package/src/panel/store.ts +8 -0
  54. package/src/server.ts +10 -10
  55. package/src/v2/commands.ts +251 -0
  56. package/src/v2/index.tsx +98 -0
  57. package/src/v2/theme.ts +14 -0
  58. package/src/v2/types.ts +165 -0
  59. package/src/v2/v2-panel-api.ts +306 -0
  60. package/tui/index.js +11 -0
@@ -0,0 +1,7 @@
1
+ import type { Context, KeymapCommand } from "./types";
2
+ import type { PanelApi } from "../panel/panel-api";
3
+ import type { SharedSignals } from "../core/types";
4
+ import { KV_PREFIX } from "../core/kv";
5
+ /** V2 命令(对齐 V1 的 9 个斜杠命令——promise 式对话框)。 */
6
+ export declare function makeCommands(context: Context, api: PanelApi, signals: SharedSignals): KeymapCommand[];
7
+ export { KV_PREFIX };
@@ -0,0 +1,263 @@
1
+ import { KV_PREFIX, SETTING_KEYS, loadSessionData, saveSessionData, readTTLDays } from "../core/kv";
2
+ import { PLUGIN_VERSION } from "../_version";
3
+ import { LANG_META, createT } from "../i18n";
4
+ import { globalEntryCache, setClearTick } from "../panel/store";
5
+ /** V2 命令(对齐 V1 的 9 个斜杠命令——promise 式对话框)。 */
6
+ export function makeCommands(context, api, signals) {
7
+ const t = createT(() => signals.lang());
8
+ const kv = api.kv;
9
+ const clampMax = (n) => Math.max(1, Math.min(50, n));
10
+ const resolveParent = (sid) => {
11
+ try {
12
+ const session = api.session.get(sid);
13
+ const parentID = session?.parentID;
14
+ if (parentID)
15
+ return { parentSid: parentID, isChild: true };
16
+ }
17
+ catch { }
18
+ return { parentSid: sid, isChild: false };
19
+ };
20
+ return [
21
+ {
22
+ id: "opencode-subagent-magazine.subagent.lang",
23
+ title: "SubAgent Magazine: Language",
24
+ description: "Switch display language (中文 / English / 日本語 / 한국어)",
25
+ slash: { name: "subagent-lang" },
26
+ palette: true,
27
+ run: async () => {
28
+ const lang = await context.ui.dialog.select({
29
+ title: "Language / 语言",
30
+ options: LANG_META.map((m) => ({ title: m.label, value: m.code })),
31
+ });
32
+ if (!lang)
33
+ return;
34
+ signals.setLang(lang);
35
+ kv.set(SETTING_KEYS.lang, lang);
36
+ api.ui.toast("Language: " + (LANG_META.find((m) => m.code === lang)?.label ?? lang));
37
+ },
38
+ },
39
+ {
40
+ id: "opencode-subagent-magazine.subagent.order",
41
+ title: "SubAgent Magazine: Sort Order",
42
+ description: "Set sub-agent entry sort order (desc / asc)",
43
+ slash: { name: "subagent-order" },
44
+ palette: true,
45
+ run: async () => {
46
+ const order = await context.ui.dialog.select({
47
+ title: "Sort Order / 排序方式",
48
+ options: [
49
+ { title: t("order.desc"), value: "desc" },
50
+ { title: t("order.asc"), value: "asc" },
51
+ ],
52
+ });
53
+ if (!order)
54
+ return;
55
+ signals.setSortOrder(order);
56
+ kv.set(SETTING_KEYS.order, order);
57
+ api.ui.toast(order === "desc" ? t("order.desc") : t("order.asc"));
58
+ },
59
+ },
60
+ {
61
+ id: "opencode-subagent-magazine.subagent.scroll",
62
+ title: "SubAgent Magazine: Scroll Mode",
63
+ description: "Set scroll mode (wheel / click)",
64
+ slash: { name: "subagent-scroll" },
65
+ palette: true,
66
+ run: async () => {
67
+ const mode = await context.ui.dialog.select({
68
+ title: "Scroll Mode / 滚动模式",
69
+ options: [
70
+ { title: t("scroll.wheel"), value: "wheel" },
71
+ { title: t("scroll.click"), value: "click" },
72
+ ],
73
+ });
74
+ if (!mode)
75
+ return;
76
+ signals.setScrollMode(mode);
77
+ kv.set(SETTING_KEYS.scrollMode, mode);
78
+ api.ui.toast(mode === "wheel" ? t("scroll.wheel") : t("scroll.click"));
79
+ },
80
+ },
81
+ {
82
+ id: "opencode-subagent-magazine.subagent.max",
83
+ title: "SubAgent Magazine: Max Entries",
84
+ description: "Set max visible sub-agent entries in sidebar",
85
+ slash: { name: "subagent-max" },
86
+ palette: true,
87
+ run: async () => {
88
+ const val = await context.ui.dialog.prompt({
89
+ title: "Max Visible Entries",
90
+ message: "Number of entries to show in the sidebar (1–50)",
91
+ placeholder: String(signals.maxEntries()),
92
+ });
93
+ if (val === undefined)
94
+ return;
95
+ const n = clampMax(parseInt(val, 10) || 10);
96
+ signals.setMaxEntries(n);
97
+ kv.set(SETTING_KEYS.maxEntries, n);
98
+ api.ui.toast(`Max entries: ${n}`);
99
+ },
100
+ },
101
+ {
102
+ id: "opencode-subagent-magazine.subagent.version",
103
+ title: "SubAgent Magazine: Version",
104
+ description: "Show plugin version",
105
+ slash: { name: "subagent-version" },
106
+ palette: true,
107
+ run: () => {
108
+ api.ui.toast(`opencode-subagent-magazine v${PLUGIN_VERSION}`);
109
+ },
110
+ },
111
+ {
112
+ id: "opencode-subagent-magazine.subagent.session",
113
+ title: "SubAgent Magazine: Session",
114
+ description: "Show current session ID",
115
+ slash: { name: "subagent-session" },
116
+ palette: true,
117
+ run: () => {
118
+ api.ui.toast(`Session: ${signals.sessionId}`);
119
+ },
120
+ },
121
+ {
122
+ id: "opencode-subagent-magazine.subagent.clear-running",
123
+ title: "SubAgent Magazine: Clear Running",
124
+ description: "Mark all running sub-agent entries as done (for stuck/zombie entries)",
125
+ slash: { name: "subagent-clear-running" },
126
+ palette: true,
127
+ run: () => {
128
+ const sid = signals.sessionId;
129
+ const entries = globalEntryCache.get(sid);
130
+ if (!entries || entries.size === 0) {
131
+ api.ui.toast(signals.lang() === "zh" ? "暂无子代理条目" : "No sub-agent entries found");
132
+ return;
133
+ }
134
+ let count = 0;
135
+ for (const [, entry] of entries) {
136
+ if (entry.status === "running" || entry.status === "cancel_requested") {
137
+ entry.status = "done";
138
+ entry.endedAt = Date.now();
139
+ count++;
140
+ }
141
+ }
142
+ if (count > 0) {
143
+ try {
144
+ const data = loadSessionData(kv);
145
+ const { parentSid, isChild } = resolveParent(sid);
146
+ if (isChild) {
147
+ if (!data[parentSid])
148
+ data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
149
+ if (!data[parentSid].children)
150
+ data[parentSid].children = {};
151
+ if (!data[parentSid].children[sid])
152
+ data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] };
153
+ data[parentSid].children[sid] = { ...data[parentSid].children[sid], entries: [...entries.values()] };
154
+ }
155
+ else {
156
+ data[sid] = {
157
+ ts: Date.now(),
158
+ entries: [...entries.values()],
159
+ scroll: data[sid]?.scroll ?? 0,
160
+ expanded: data[sid]?.expanded ?? "",
161
+ children: data[sid]?.children ?? {},
162
+ };
163
+ }
164
+ saveSessionData(kv, data);
165
+ }
166
+ catch { }
167
+ api.ui.toast(signals.lang() === "zh"
168
+ ? `已标记 ${count} 个运行中的条目为完成`
169
+ : `Marked ${count} running entries as done`);
170
+ }
171
+ else {
172
+ api.ui.toast(signals.lang() === "zh" ? "没有需要清理的运行中条目" : "No running entries to clear");
173
+ }
174
+ },
175
+ },
176
+ {
177
+ id: "opencode-subagent-magazine.subagent.ttl",
178
+ title: "SubAgent Magazine: TTL",
179
+ description: "Set session data retention period (days before auto-cleanup)",
180
+ slash: { name: "subagent-ttl" },
181
+ palette: true,
182
+ run: async () => {
183
+ const curDays = readTTLDays(kv);
184
+ const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`;
185
+ const days = await context.ui.dialog.select({
186
+ title: `${t("ttl.label")} (${curLabel})`,
187
+ options: [
188
+ { title: t("ttl.3d"), value: 3 },
189
+ { title: t("ttl.7d"), value: 7 },
190
+ { title: t("ttl.14d"), value: 14 },
191
+ { title: t("ttl.30d"), value: 30 },
192
+ { title: t("ttl.unlimited"), value: 0 },
193
+ ],
194
+ });
195
+ if (days === undefined)
196
+ return;
197
+ kv.set(SETTING_KEYS.ttlDays, String(days));
198
+ api.ui.toast(days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast", { n: days }));
199
+ },
200
+ },
201
+ {
202
+ id: "opencode-subagent-magazine.subagent.clear-entries",
203
+ title: "SubAgent Magazine: Clear Entries",
204
+ description: "Delete all sub-agent records for the current session (cannot be undone)",
205
+ slash: { name: "subagent-clear-entries" },
206
+ palette: true,
207
+ run: async () => {
208
+ const sid = signals.sessionId;
209
+ const sessionObj = api.session.get(sid);
210
+ const parentID = sessionObj?.parentID;
211
+ const cached = globalEntryCache.get(sid);
212
+ let runningCount = 0;
213
+ if (cached) {
214
+ for (const [, e] of cached) {
215
+ if (e.status === "running")
216
+ runningCount++;
217
+ }
218
+ }
219
+ const msg = runningCount > 0 ? t("clear.prompt_running", { n: runningCount }) : t("clear.prompt");
220
+ const choice = await context.ui.dialog.select({
221
+ title: t("clear.title"),
222
+ options: [
223
+ { title: t("clear.title"), value: "yes" },
224
+ { title: t("cancel.label"), value: "no" },
225
+ ],
226
+ });
227
+ if (choice !== "yes")
228
+ return;
229
+ try {
230
+ const data = loadSessionData(kv);
231
+ let count = 0;
232
+ if (parentID) {
233
+ if (data[parentID]?.children?.[sid]) {
234
+ const child = data[parentID].children[sid];
235
+ const ids = child.entries?.map((e) => e.id) ?? [];
236
+ count = ids.length;
237
+ child.entries = [];
238
+ child.scroll = 0;
239
+ child.expanded = "";
240
+ child.clearedIds = [...new Set([...(child.clearedIds ?? []), ...ids])];
241
+ }
242
+ }
243
+ else {
244
+ count = data[sid]?.entries?.length ?? 0;
245
+ if (data[sid]) {
246
+ const ids = data[sid].entries?.map((e) => e.id) ?? [];
247
+ data[sid].entries = [];
248
+ data[sid].scroll = 0;
249
+ data[sid].expanded = "";
250
+ data[sid].clearedIds = [...new Set([...(data[sid].clearedIds ?? []), ...ids])];
251
+ }
252
+ }
253
+ saveSessionData(kv, data);
254
+ globalEntryCache.delete(sid);
255
+ setClearTick((v) => v + 1);
256
+ api.ui.toast(t("clear.done", { n: count }));
257
+ }
258
+ catch { }
259
+ },
260
+ },
261
+ ];
262
+ }
263
+ export { KV_PREFIX };
@@ -0,0 +1,8 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { PluginModule } from "./types";
3
+ /** V2 入口:setup 创建共享信号 + PanelApi + 命令 layer + 侧边栏槽位。
4
+ * 行为对齐 V1 tui()(信号初始值从 KV 恢复;侧边栏渲染共享 SubAgentPanel)。 */
5
+ declare const mod: PluginModule & {
6
+ server: () => Promise<Record<string, never>>;
7
+ };
8
+ export default mod;
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "@opentui/solid/jsx-runtime";
2
+ /** @jsxImportSource @opentui/solid */
3
+ import { createSignal } from "solid-js";
4
+ import { createPanelApi } from "./v2-panel-api";
5
+ import { makeCommands } from "./commands";
6
+ import { mapTheme } from "./theme";
7
+ import { SubAgentPanel } from "../panel/SubAgentPanel";
8
+ import { SETTING_KEYS } from "../core/kv";
9
+ import { LANG_META, detectLang } from "../i18n";
10
+ /** 面板根组件:keymap.layer 必须在组件渲染上下文注册(setup 内调用报
11
+ * Keymap.Provider missing),再渲染共享 SubAgentPanel。 */
12
+ function PluginRoot(props) {
13
+ props.context.keymap.layer(() => ({
14
+ mode: "global",
15
+ commands: makeCommands(props.context, props.api, props.signals),
16
+ }));
17
+ return (_jsx(SubAgentPanel, { api: props.api, theme: mapTheme(props.context.theme), lang: props.signals.lang, maxEntries: props.signals.maxEntries, sortOrder: props.signals.sortOrder, scrollMode: props.signals.scrollMode, sessionId: props.sessionID }));
18
+ }
19
+ /** V2 入口:setup 创建共享信号 + PanelApi + 命令 layer + 侧边栏槽位。
20
+ * 行为对齐 V1 tui()(信号初始值从 KV 恢复;侧边栏渲染共享 SubAgentPanel)。 */
21
+ const mod = {
22
+ id: "opencode-subagent-magazine",
23
+ setup(context) {
24
+ // settings 惰性 getter(闭包引用信号变量——运行时已赋值)
25
+ let lang;
26
+ let maxEntries;
27
+ let sortOrder;
28
+ let scrollMode;
29
+ const api = createPanelApi(context, {
30
+ lang: () => lang(),
31
+ maxEntries: () => maxEntries(),
32
+ sortOrder: () => sortOrder(),
33
+ scrollMode: () => scrollMode(),
34
+ });
35
+ // 信号初始值从 KV 恢复(对齐 V1 tui())
36
+ const storedLang = String(api.kv.get(SETTING_KEYS.lang, ""));
37
+ const initialLang = LANG_META.some((m) => m.code === storedLang) ? storedLang : detectLang();
38
+ const [langSignal, setLang] = createSignal(initialLang);
39
+ const [maxSignal, setMaxEntries] = createSignal(Number(api.kv.get(SETTING_KEYS.maxEntries, "10")) || 10);
40
+ const [orderSignal, setSortOrder] = createSignal(String(api.kv.get(SETTING_KEYS.order, "desc")) === "asc" ? "asc" : "desc");
41
+ const [scrollSignal, setScrollMode] = createSignal(String(api.kv.get(SETTING_KEYS.scrollMode, "wheel")) === "click" ? "click" : "wheel");
42
+ lang = langSignal;
43
+ maxEntries = maxSignal;
44
+ sortOrder = orderSignal;
45
+ scrollMode = scrollSignal;
46
+ const signals = {
47
+ lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "",
48
+ };
49
+ // 命令 layer(组件内注册——见 PluginRoot)
50
+ // 侧边栏面板(共享 SubAgentPanel——V1/V2 同一组件)
51
+ context.ui.slot({
52
+ prepend: "sidebar.content",
53
+ render: (props) => {
54
+ signals.sessionId = String(props.sessionID ?? "");
55
+ return (_jsx(PluginRoot, { context: context, api: api, signals: signals, sessionID: String(props.sessionID ?? "") }));
56
+ },
57
+ });
58
+ },
59
+ // V1 server 空实现(兼容标记):v2 加载 setup,V1 检测需要 server 字段识别为插件
60
+ server: async () => ({}),
61
+ };
62
+ export default mod;
@@ -0,0 +1,3 @@
1
+ import type { Context } from "./types";
2
+ /** V2 theme → V1 形状映射(组件按 primary/text/textMuted/… 字段消费)。 */
3
+ export declare function mapTheme(theme: Context["theme"]): Record<string, unknown>;
@@ -0,0 +1,12 @@
1
+ /** V2 theme → V1 形状映射(组件按 primary/text/textMuted/… 字段消费)。 */
2
+ export function mapTheme(theme) {
3
+ return {
4
+ primary: theme.hue.interactive[300],
5
+ text: theme.text.default,
6
+ textMuted: theme.text.subdued,
7
+ success: theme.text.feedback.success.default,
8
+ warning: theme.text.feedback.warning.default,
9
+ error: theme.text.feedback.error.default,
10
+ border: theme.text.subdued,
11
+ };
12
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * V2 (opencode2) TUI plugin API — 最小本地类型(实验版)。
3
+ * 运行时由 opencode2 提供,此处仅用于本地类型检查;
4
+ * 结构对应 v2 分支 packages/plugin/src/tui/context.ts。
5
+ */
6
+ export interface App {
7
+ readonly version: string;
8
+ readonly channel: string;
9
+ }
10
+ export interface Theme {
11
+ readonly hue: {
12
+ readonly interactive: {
13
+ readonly 300: string;
14
+ };
15
+ readonly accent: {
16
+ readonly 500: string;
17
+ };
18
+ };
19
+ readonly text: {
20
+ readonly default: string;
21
+ readonly subdued: string;
22
+ readonly feedback: {
23
+ readonly success: {
24
+ readonly default: string;
25
+ };
26
+ readonly error: {
27
+ readonly default: string;
28
+ };
29
+ readonly warning: {
30
+ readonly default: string;
31
+ };
32
+ };
33
+ };
34
+ }
35
+ export interface TokenUsage {
36
+ input?: number;
37
+ output?: number;
38
+ reasoning?: number;
39
+ cache?: {
40
+ read?: number;
41
+ write?: number;
42
+ };
43
+ }
44
+ export interface MessageInfo {
45
+ readonly id: string;
46
+ readonly type: string;
47
+ readonly agent?: string;
48
+ readonly model?: string;
49
+ readonly time: {
50
+ readonly created: number;
51
+ readonly completed?: number;
52
+ };
53
+ readonly cost?: unknown;
54
+ readonly tokens?: TokenUsage;
55
+ readonly content?: unknown[];
56
+ }
57
+ export interface SessionInfo {
58
+ readonly id: string;
59
+ readonly title?: string;
60
+ readonly time?: {
61
+ readonly created: number;
62
+ readonly updated: number;
63
+ };
64
+ readonly model?: string;
65
+ readonly agent?: string;
66
+ readonly parentID?: string;
67
+ readonly cost?: number;
68
+ readonly tokens?: TokenUsage;
69
+ }
70
+ export interface Data {
71
+ readonly session: {
72
+ get(sessionID: string): SessionInfo | undefined;
73
+ list(): SessionInfo[];
74
+ cost(sessionID: string): number;
75
+ status(sessionID: string): string;
76
+ interrupt(sessionID: string): Promise<void>;
77
+ readonly message: {
78
+ list(sessionID: string): MessageInfo[];
79
+ sync(sessionID: string): Promise<void>;
80
+ };
81
+ };
82
+ readonly location: {
83
+ readonly provider: {
84
+ list(location?: unknown): unknown[];
85
+ };
86
+ readonly model: {
87
+ list(location?: unknown): unknown[];
88
+ };
89
+ readonly mcp: {
90
+ readonly server: {
91
+ list(location?: unknown): unknown[];
92
+ };
93
+ };
94
+ };
95
+ readonly on: (type: string, handler: (event: unknown) => void) => () => void;
96
+ readonly listen: (handler: (event: unknown) => void) => () => void;
97
+ }
98
+ export interface Storage {
99
+ store<Value extends object>(key: string, options: {
100
+ readonly initial: Value;
101
+ }): readonly [Value, (mutation: (draft: Value) => void) => Promise<void>];
102
+ memory<Value extends object>(key: string, options: {
103
+ readonly initial: Value;
104
+ }): readonly [Value, (mutation: (draft: Value) => void) => void];
105
+ }
106
+ export type SlotClaim = {
107
+ readonly render: (input: Record<string, any>) => unknown;
108
+ } & ({
109
+ readonly append: string;
110
+ readonly prepend?: never;
111
+ readonly before?: never;
112
+ readonly after?: never;
113
+ readonly replace?: never;
114
+ } | {
115
+ readonly prepend: string;
116
+ readonly append?: never;
117
+ readonly before?: never;
118
+ readonly after?: never;
119
+ readonly replace?: never;
120
+ } | {
121
+ readonly before: string;
122
+ readonly append?: never;
123
+ readonly prepend?: never;
124
+ readonly after?: never;
125
+ readonly replace?: never;
126
+ } | {
127
+ readonly after: string;
128
+ readonly append?: never;
129
+ readonly prepend?: never;
130
+ readonly before?: never;
131
+ readonly replace?: never;
132
+ } | {
133
+ readonly replace: string;
134
+ readonly append?: never;
135
+ readonly prepend?: never;
136
+ readonly before?: never;
137
+ readonly after?: never;
138
+ });
139
+ export interface KeymapCommand {
140
+ readonly id?: string;
141
+ readonly title?: string;
142
+ readonly description?: string;
143
+ readonly group?: string;
144
+ readonly palette?: true;
145
+ readonly slash?: {
146
+ readonly name: string;
147
+ readonly aliases?: string[];
148
+ readonly arguments?: true;
149
+ };
150
+ readonly namespace?: string;
151
+ readonly name?: string;
152
+ readonly desc?: string;
153
+ readonly category?: string;
154
+ readonly slashName?: string;
155
+ readonly slashAliases?: string[];
156
+ readonly run: (input?: string) => void | false | Promise<void>;
157
+ }
158
+ /** 最小 client 面(运行时由 V2 提供——取消走 session.interrupt)。 */
159
+ export interface ClientLike {
160
+ readonly session: {
161
+ interrupt(input: {
162
+ sessionID: string;
163
+ continue?: boolean;
164
+ }): Promise<unknown>;
165
+ };
166
+ }
167
+ export interface Context {
168
+ readonly app: App;
169
+ readonly options: Record<string, any>;
170
+ readonly location: {
171
+ readonly directory?: string;
172
+ } | undefined;
173
+ readonly renderer: {
174
+ readonly terminalWidth: number;
175
+ };
176
+ readonly theme: Theme;
177
+ readonly data: Data;
178
+ readonly client: ClientLike;
179
+ readonly storage: Storage;
180
+ readonly ui: {
181
+ slot(claim: SlotClaim): () => void;
182
+ readonly toast: {
183
+ show(options: {
184
+ readonly message: string;
185
+ readonly title?: string;
186
+ readonly variant?: string;
187
+ }): void;
188
+ };
189
+ readonly dialog: {
190
+ prompt(options: {
191
+ readonly title: string;
192
+ readonly message?: string;
193
+ readonly placeholder?: string;
194
+ }): Promise<string | undefined>;
195
+ select<Value>(options: {
196
+ readonly title: string;
197
+ readonly placeholder?: string;
198
+ readonly options: readonly {
199
+ readonly title: string;
200
+ readonly value: Value;
201
+ readonly description?: string;
202
+ readonly category?: string;
203
+ readonly disabled?: boolean;
204
+ }[];
205
+ readonly current?: Value;
206
+ }): Promise<Value | undefined>;
207
+ };
208
+ readonly router: {
209
+ current(): {
210
+ readonly type?: string;
211
+ readonly sessionID?: string;
212
+ readonly params?: Record<string, unknown>;
213
+ };
214
+ navigate(route: {
215
+ type: string;
216
+ sessionID?: string;
217
+ params?: Record<string, unknown>;
218
+ }): void;
219
+ };
220
+ };
221
+ readonly keymap: {
222
+ layer(input: () => {
223
+ readonly commands?: readonly KeymapCommand[];
224
+ }): void;
225
+ shortcuts(id: string): readonly string[];
226
+ };
227
+ }
228
+ export type PluginModule = {
229
+ readonly id: string;
230
+ readonly setup: (context: Context) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>;
231
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * V2 (opencode2) TUI plugin API — 最小本地类型(实验版)。
3
+ * 运行时由 opencode2 提供,此处仅用于本地类型检查;
4
+ * 结构对应 v2 分支 packages/plugin/src/tui/context.ts。
5
+ */
6
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { Context } from "./types";
2
+ import type { PanelApi } from "../panel/panel-api";
3
+ /**
4
+ * V2 (opencode2) context → PanelApi 适配实现。
5
+ * 数据流(功能对齐 V1):
6
+ * - usage:messages 反向遍历(同 V1 readSessionTokens/Cost/Model 逻辑);todo 无 V2 对应 → undefined
7
+ * - 事件:tool 事件 → V1 ToolPart(subagent 工具名归一化 + metadata 双写 session_id/sessionId);
8
+ * execution.succeeded/failed → session.idle/error
9
+ * - 取消:session.interrupt(V1 abort 的 V2 对应)
10
+ * - 路由:router.navigate({type:"session"})(V1 route.navigate 的 V2 对应)
11
+ */
12
+ export declare function createPanelApi(context: Context, settings: PanelApi["settings"]): PanelApi;