opencode-subagent-magazine 1.6.0-beta.2 → 1.6.0-beta.4

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/src/v2/index.tsx CHANGED
@@ -1,98 +1,102 @@
1
- /** @jsxImportSource @opentui/solid */
2
-
3
- import { createSignal } from "solid-js"
4
- import type { Context, PluginModule } from "./types"
5
- import { createPanelApi } from "./v2-panel-api"
6
- import { makeCommands } from "./commands"
7
- import { mapTheme } from "./theme"
8
- import { SubAgentPanel } from "../panel/SubAgentPanel"
9
- import type { PanelApi } from "../panel/panel-api"
10
- import type { Lang, SortOrder, ScrollMode, SharedSignals } from "../core/types"
11
- import { SETTING_KEYS } from "../core/kv"
12
- import { LANG_META, detectLang } from "../i18n"
13
-
14
- /** 面板根组件:keymap.layer 必须在组件渲染上下文注册(setup 内调用报
15
- * Keymap.Provider missing),再渲染共享 SubAgentPanel。 */
16
- function PluginRoot(props: {
17
- context: Context
18
- api: PanelApi
19
- signals: SharedSignals
20
- sessionID: string
21
- }) {
22
- props.context.keymap.layer(() => ({
23
- mode: "global" as const,
24
- commands: makeCommands(props.context, props.api, props.signals),
25
- }))
26
- return (
27
- <SubAgentPanel
28
- api={props.api}
29
- theme={mapTheme(props.context.theme)}
30
- lang={props.signals.lang}
31
- maxEntries={props.signals.maxEntries}
32
- sortOrder={props.signals.sortOrder}
33
- scrollMode={props.signals.scrollMode}
34
- sessionId={props.sessionID}
35
- />
36
- )
37
- }
38
-
39
- /** V2 入口:setup 创建共享信号 + PanelApi + 命令 layer + 侧边栏槽位。
40
- * 行为对齐 V1 tui()(信号初始值从 KV 恢复;侧边栏渲染共享 SubAgentPanel)。 */
41
- const mod: PluginModule & { server: () => Promise<Record<string, never>> } = {
42
- id: "opencode-subagent-magazine",
43
- setup(context: Context) {
44
- // settings 惰性 getter(闭包引用信号变量——运行时已赋值)
45
- let lang!: () => Lang
46
- let maxEntries!: () => number
47
- let sortOrder!: () => SortOrder
48
- let scrollMode!: () => ScrollMode
49
- const api = createPanelApi(context, {
50
- lang: () => lang(),
51
- maxEntries: () => maxEntries(),
52
- sortOrder: () => sortOrder(),
53
- scrollMode: () => scrollMode(),
54
- })
55
-
56
- // 信号初始值从 KV 恢复(对齐 V1 tui())
57
- const storedLang = String(api.kv.get(SETTING_KEYS.lang, ""))
58
- const initialLang: Lang =
59
- LANG_META.some((m) => m.code === storedLang) ? (storedLang as Lang) : detectLang()
60
- const [langSignal, setLang] = createSignal<Lang>(initialLang)
61
- const [maxSignal, setMaxEntries] = createSignal<number>(Number(api.kv.get(SETTING_KEYS.maxEntries, "10")) || 10)
62
- const [orderSignal, setSortOrder] = createSignal<SortOrder>(
63
- String(api.kv.get(SETTING_KEYS.order, "desc")) === "asc" ? "asc" : "desc",
64
- )
65
- const [scrollSignal, setScrollMode] = createSignal<ScrollMode>(
66
- String(api.kv.get(SETTING_KEYS.scrollMode, "wheel")) === "click" ? "click" : "wheel",
67
- )
68
- lang = langSignal
69
- maxEntries = maxSignal
70
- sortOrder = orderSignal
71
- scrollMode = scrollSignal
72
-
73
- const signals: SharedSignals = {
74
- lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "",
75
- }
76
-
77
- // 命令 layer(组件内注册——见 PluginRoot)
78
- // 侧边栏面板(共享 SubAgentPanel——V1/V2 同一组件)
79
- context.ui.slot({
80
- prepend: "sidebar.content",
81
- render: (props) => {
82
- signals.sessionId = String(props.sessionID ?? "")
83
- return (
84
- <PluginRoot
85
- context={context}
86
- api={api}
87
- signals={signals}
88
- sessionID={String(props.sessionID ?? "")}
89
- />
90
- )
91
- },
92
- })
93
- },
94
- // V1 server 空实现(兼容标记):v2 加载 setup,V1 检测需要 server 字段识别为插件
95
- server: async () => ({}),
96
- }
97
-
98
- export default mod
1
+ /** @jsxImportSource @opentui/solid */
2
+
3
+ import { createSignal } from "solid-js"
4
+ import type { Context, PluginModule } from "./types"
5
+ import { createPanelApi } from "./v2-panel-api"
6
+ import { makeCommands } from "./commands"
7
+ import { mapTheme } from "./theme"
8
+ import { SubAgentPanel } from "../panel/SubAgentPanel"
9
+ import type { PanelApi } from "../panel/panel-api"
10
+ import type { Lang, SortOrder, ScrollMode, SharedSignals } from "../core/types"
11
+ import { SETTING_KEYS } from "../core/kv"
12
+ import { LANG_META, detectLang } from "../i18n"
13
+
14
+ /** 面板根组件:keymap.layer 必须在组件渲染上下文注册(setup 内调用报
15
+ * Keymap.Provider missing),再渲染共享 SubAgentPanel。 */
16
+ function PluginRoot(props: {
17
+ context: Context
18
+ api: PanelApi
19
+ signals: SharedSignals
20
+ sessionID: string
21
+ }) {
22
+ props.context.keymap.layer(() => ({
23
+ mode: "global" as const,
24
+ commands: makeCommands(props.context, props.api, props.signals),
25
+ }))
26
+ return (
27
+ <SubAgentPanel
28
+ api={props.api}
29
+ theme={mapTheme(props.context.theme)}
30
+ lang={props.signals.lang}
31
+ maxEntries={props.signals.maxEntries}
32
+ sortOrder={props.signals.sortOrder}
33
+ scrollMode={props.signals.scrollMode}
34
+ borderVisible={props.signals.borderVisible}
35
+ sessionId={props.sessionID}
36
+ />
37
+ )
38
+ }
39
+
40
+ /** V2 入口:setup 创建共享信号 + PanelApi + 命令 layer + 侧边栏槽位。
41
+ * 行为对齐 V1 tui()(信号初始值从 KV 恢复;侧边栏渲染共享 SubAgentPanel)。 */
42
+ const mod: PluginModule & { server: () => Promise<Record<string, never>> } = {
43
+ id: "opencode-subagent-magazine",
44
+ setup(context: Context) {
45
+ // settings 惰性 getter(闭包引用信号变量——运行时已赋值)
46
+ let lang!: () => Lang
47
+ let maxEntries!: () => number
48
+ let sortOrder!: () => SortOrder
49
+ let scrollMode!: () => ScrollMode
50
+ const api = createPanelApi(context, {
51
+ lang: () => lang(),
52
+ maxEntries: () => maxEntries(),
53
+ sortOrder: () => sortOrder(),
54
+ scrollMode: () => scrollMode(),
55
+ })
56
+
57
+ // 信号初始值从 KV 恢复(对齐 V1 tui())
58
+ const storedLang = String(api.kv.get(SETTING_KEYS.lang, ""))
59
+ const initialLang: Lang =
60
+ LANG_META.some((m) => m.code === storedLang) ? (storedLang as Lang) : detectLang()
61
+ const [langSignal, setLang] = createSignal<Lang>(initialLang)
62
+ const [maxSignal, setMaxEntries] = createSignal<number>(Number(api.kv.get(SETTING_KEYS.maxEntries, "10")) || 10)
63
+ const [orderSignal, setSortOrder] = createSignal<SortOrder>(
64
+ String(api.kv.get(SETTING_KEYS.order, "desc")) === "asc" ? "asc" : "desc",
65
+ )
66
+ const [scrollSignal, setScrollMode] = createSignal<ScrollMode>(
67
+ String(api.kv.get(SETTING_KEYS.scrollMode, "wheel")) === "click" ? "click" : "wheel",
68
+ )
69
+ const [borderSignal, setBorderVisible] = createSignal<boolean>(
70
+ (api.kv.get(SETTING_KEYS.border, false) as boolean) === true,
71
+ )
72
+ lang = langSignal
73
+ maxEntries = maxSignal
74
+ sortOrder = orderSignal
75
+ scrollMode = scrollSignal
76
+
77
+ const signals: SharedSignals = {
78
+ lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible: borderSignal, setBorderVisible, sessionId: "",
79
+ }
80
+
81
+ // 命令 layer(组件内注册——见 PluginRoot)
82
+ // 侧边栏面板(共享 SubAgentPanel——V1/V2 同一组件)
83
+ context.ui.slot({
84
+ prepend: "sidebar.content",
85
+ render: (props) => {
86
+ signals.sessionId = String(props.sessionID ?? "")
87
+ return (
88
+ <PluginRoot
89
+ context={context}
90
+ api={api}
91
+ signals={signals}
92
+ sessionID={String(props.sessionID ?? "")}
93
+ />
94
+ )
95
+ },
96
+ })
97
+ },
98
+ // V1 server 空实现(兼容标记):v2 加载 setup,V1 检测需要 server 字段识别为插件
99
+ server: async () => ({}),
100
+ }
101
+
102
+ export default mod
package/src/v2/theme.ts CHANGED
@@ -1,14 +1,14 @@
1
- import type { Context } from "./types"
2
-
3
- /** V2 theme → V1 形状映射(组件按 primary/text/textMuted/… 字段消费)。 */
4
- export function mapTheme(theme: Context["theme"]): Record<string, unknown> {
5
- return {
6
- primary: theme.hue.interactive[300],
7
- text: theme.text.default,
8
- textMuted: theme.text.subdued,
9
- success: theme.text.feedback.success.default,
10
- warning: theme.text.feedback.warning.default,
11
- error: theme.text.feedback.error.default,
12
- border: theme.text.subdued,
13
- }
14
- }
1
+ import type { Context } from "./types"
2
+
3
+ /** V2 theme → V1 形状映射(组件按 primary/text/textMuted/… 字段消费)。 */
4
+ export function mapTheme(theme: Context["theme"]): Record<string, unknown> {
5
+ return {
6
+ primary: theme.hue.interactive[300],
7
+ text: theme.text.default,
8
+ textMuted: theme.text.subdued,
9
+ success: theme.text.feedback.success.default,
10
+ warning: theme.text.feedback.warning.default,
11
+ error: theme.text.feedback.error.default,
12
+ border: theme.text.subdued,
13
+ }
14
+ }
package/src/v2/types.ts CHANGED
@@ -1,165 +1,165 @@
1
- /**
2
- * V2 (opencode2) TUI plugin API — 最小本地类型(实验版)。
3
- * 运行时由 opencode2 提供,此处仅用于本地类型检查;
4
- * 结构对应 v2 分支 packages/plugin/src/tui/context.ts。
5
- */
6
-
7
- export interface App {
8
- readonly version: string
9
- readonly channel: string
10
- }
11
-
12
- export interface Theme {
13
- readonly hue: {
14
- readonly interactive: { readonly 300: string }
15
- readonly accent: { readonly 500: string }
16
- }
17
- readonly text: {
18
- readonly default: string
19
- readonly subdued: string
20
- readonly feedback: {
21
- readonly success: { readonly default: string }
22
- readonly error: { readonly default: string }
23
- readonly warning: { readonly default: string }
24
- }
25
- }
26
- }
27
-
28
- export interface TokenUsage {
29
- input?: number
30
- output?: number
31
- reasoning?: number
32
- cache?: { read?: number; write?: number }
33
- }
34
-
35
- export interface MessageInfo {
36
- readonly id: string
37
- readonly type: string
38
- readonly agent?: string
39
- readonly model?: string
40
- readonly time: { readonly created: number; readonly completed?: number }
41
- readonly cost?: unknown
42
- readonly tokens?: TokenUsage
43
- readonly content?: unknown[]
44
- }
45
-
46
- export interface SessionInfo {
47
- readonly id: string
48
- readonly title?: string
49
- readonly time?: { readonly created: number; readonly updated: number }
50
- readonly model?: string
51
- readonly agent?: string
52
- readonly parentID?: string
53
- readonly cost?: number
54
- readonly tokens?: TokenUsage
55
- }
56
-
57
- export interface Data {
58
- readonly session: {
59
- get(sessionID: string): SessionInfo | undefined
60
- list(): SessionInfo[]
61
- cost(sessionID: string): number
62
- status(sessionID: string): string
63
- interrupt(sessionID: string): Promise<void>
64
- readonly message: {
65
- list(sessionID: string): MessageInfo[]
66
- sync(sessionID: string): Promise<void>
67
- }
68
- }
69
- readonly location: {
70
- readonly provider: { list(location?: unknown): unknown[] }
71
- readonly model: { list(location?: unknown): unknown[] }
72
- readonly mcp: { readonly server: { list(location?: unknown): unknown[] } }
73
- }
74
- readonly on: (type: string, handler: (event: unknown) => void) => () => void
75
- readonly listen: (handler: (event: unknown) => void) => () => void
76
- }
77
-
78
- export interface Storage {
79
- store<Value extends object>(
80
- key: string,
81
- options: { readonly initial: Value },
82
- ): readonly [Value, (mutation: (draft: Value) => void) => Promise<void>]
83
- memory<Value extends object>(
84
- key: string,
85
- options: { readonly initial: Value },
86
- ): readonly [Value, (mutation: (draft: Value) => void) => void]
87
- }
88
-
89
- export type SlotClaim = {
90
- readonly render: (input: Record<string, any>) => unknown
91
- } & (
92
- | { readonly append: string; readonly prepend?: never; readonly before?: never; readonly after?: never; readonly replace?: never }
93
- | { readonly prepend: string; readonly append?: never; readonly before?: never; readonly after?: never; readonly replace?: never }
94
- | { readonly before: string; readonly append?: never; readonly prepend?: never; readonly after?: never; readonly replace?: never }
95
- | { readonly after: string; readonly append?: never; readonly prepend?: never; readonly before?: never; readonly replace?: never }
96
- | { readonly replace: string; readonly append?: never; readonly prepend?: never; readonly before?: never; readonly after?: never }
97
- )
98
-
99
- export interface KeymapCommand {
100
- readonly id?: string
101
- readonly title?: string
102
- readonly description?: string
103
- readonly group?: string
104
- readonly palette?: true
105
- readonly slash?: { readonly name: string; readonly aliases?: string[]; readonly arguments?: true }
106
- readonly namespace?: string
107
- readonly name?: string
108
- readonly desc?: string
109
- readonly category?: string
110
- readonly slashName?: string
111
- readonly slashAliases?: string[]
112
- readonly run: (input?: string) => void | false | Promise<void>
113
- }
114
-
115
- /** 最小 client 面(运行时由 V2 提供——取消走 session.interrupt)。 */
116
- export interface ClientLike {
117
- readonly session: {
118
- interrupt(input: { sessionID: string; continue?: boolean }): Promise<unknown>
119
- }
120
- }
121
-
122
- export interface Context {
123
- readonly app: App
124
- readonly options: Record<string, any>
125
- readonly location: { readonly directory?: string } | undefined
126
- readonly renderer: { readonly terminalWidth: number }
127
- readonly theme: Theme
128
- readonly data: Data
129
- readonly client: ClientLike
130
- readonly storage: Storage
131
- readonly ui: {
132
- slot(claim: SlotClaim): () => void
133
- readonly toast: {
134
- show(options: { readonly message: string; readonly title?: string; readonly variant?: string }): void
135
- }
136
- readonly dialog: {
137
- prompt(options: { readonly title: string; readonly message?: string; readonly placeholder?: string }): Promise<string | undefined>
138
- select<Value>(options: {
139
- readonly title: string
140
- readonly placeholder?: string
141
- readonly options: readonly {
142
- readonly title: string
143
- readonly value: Value
144
- readonly description?: string
145
- readonly category?: string
146
- readonly disabled?: boolean
147
- }[]
148
- readonly current?: Value
149
- }): Promise<Value | undefined>
150
- }
151
- readonly router: {
152
- current(): { readonly type?: string; readonly sessionID?: string; readonly params?: Record<string, unknown> }
153
- navigate(route: { type: string; sessionID?: string; params?: Record<string, unknown> }): void
154
- }
155
- }
156
- readonly keymap: {
157
- layer(input: () => { readonly commands?: readonly KeymapCommand[] }): void
158
- shortcuts(id: string): readonly string[]
159
- }
160
- }
161
-
162
- export type PluginModule = {
163
- readonly id: string
164
- readonly setup: (context: Context) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>
165
- }
1
+ /**
2
+ * V2 (opencode2) TUI plugin API — 最小本地类型(实验版)。
3
+ * 运行时由 opencode2 提供,此处仅用于本地类型检查;
4
+ * 结构对应 v2 分支 packages/plugin/src/tui/context.ts。
5
+ */
6
+
7
+ export interface App {
8
+ readonly version: string
9
+ readonly channel: string
10
+ }
11
+
12
+ export interface Theme {
13
+ readonly hue: {
14
+ readonly interactive: { readonly 300: string }
15
+ readonly accent: { readonly 500: string }
16
+ }
17
+ readonly text: {
18
+ readonly default: string
19
+ readonly subdued: string
20
+ readonly feedback: {
21
+ readonly success: { readonly default: string }
22
+ readonly error: { readonly default: string }
23
+ readonly warning: { readonly default: string }
24
+ }
25
+ }
26
+ }
27
+
28
+ export interface TokenUsage {
29
+ input?: number
30
+ output?: number
31
+ reasoning?: number
32
+ cache?: { read?: number; write?: number }
33
+ }
34
+
35
+ export interface MessageInfo {
36
+ readonly id: string
37
+ readonly type: string
38
+ readonly agent?: string
39
+ readonly model?: string
40
+ readonly time: { readonly created: number; readonly completed?: number }
41
+ readonly cost?: unknown
42
+ readonly tokens?: TokenUsage
43
+ readonly content?: unknown[]
44
+ }
45
+
46
+ export interface SessionInfo {
47
+ readonly id: string
48
+ readonly title?: string
49
+ readonly time?: { readonly created: number; readonly updated: number }
50
+ readonly model?: string
51
+ readonly agent?: string
52
+ readonly parentID?: string
53
+ readonly cost?: number
54
+ readonly tokens?: TokenUsage
55
+ }
56
+
57
+ export interface Data {
58
+ readonly session: {
59
+ get(sessionID: string): SessionInfo | undefined
60
+ list(): SessionInfo[]
61
+ cost(sessionID: string): number
62
+ status(sessionID: string): string
63
+ interrupt(sessionID: string): Promise<void>
64
+ readonly message: {
65
+ list(sessionID: string): MessageInfo[]
66
+ sync(sessionID: string): Promise<void>
67
+ }
68
+ }
69
+ readonly location: {
70
+ readonly provider: { list(location?: unknown): unknown[] }
71
+ readonly model: { list(location?: unknown): unknown[] }
72
+ readonly mcp: { readonly server: { list(location?: unknown): unknown[] } }
73
+ }
74
+ readonly on: (type: string, handler: (event: unknown) => void) => () => void
75
+ readonly listen: (handler: (event: unknown) => void) => () => void
76
+ }
77
+
78
+ export interface Storage {
79
+ store<Value extends object>(
80
+ key: string,
81
+ options: { readonly initial: Value },
82
+ ): readonly [Value, (mutation: (draft: Value) => void) => Promise<void>]
83
+ memory<Value extends object>(
84
+ key: string,
85
+ options: { readonly initial: Value },
86
+ ): readonly [Value, (mutation: (draft: Value) => void) => void]
87
+ }
88
+
89
+ export type SlotClaim = {
90
+ readonly render: (input: Record<string, any>) => unknown
91
+ } & (
92
+ | { readonly append: string; readonly prepend?: never; readonly before?: never; readonly after?: never; readonly replace?: never }
93
+ | { readonly prepend: string; readonly append?: never; readonly before?: never; readonly after?: never; readonly replace?: never }
94
+ | { readonly before: string; readonly append?: never; readonly prepend?: never; readonly after?: never; readonly replace?: never }
95
+ | { readonly after: string; readonly append?: never; readonly prepend?: never; readonly before?: never; readonly replace?: never }
96
+ | { readonly replace: string; readonly append?: never; readonly prepend?: never; readonly before?: never; readonly after?: never }
97
+ )
98
+
99
+ export interface KeymapCommand {
100
+ readonly id?: string
101
+ readonly title?: string
102
+ readonly description?: string
103
+ readonly group?: string
104
+ readonly palette?: true
105
+ readonly slash?: { readonly name: string; readonly aliases?: string[]; readonly arguments?: true }
106
+ readonly namespace?: string
107
+ readonly name?: string
108
+ readonly desc?: string
109
+ readonly category?: string
110
+ readonly slashName?: string
111
+ readonly slashAliases?: string[]
112
+ readonly run: (input?: string) => void | false | Promise<void>
113
+ }
114
+
115
+ /** 最小 client 面(运行时由 V2 提供——取消走 session.interrupt)。 */
116
+ export interface ClientLike {
117
+ readonly session: {
118
+ interrupt(input: { sessionID: string; continue?: boolean }): Promise<unknown>
119
+ }
120
+ }
121
+
122
+ export interface Context {
123
+ readonly app: App
124
+ readonly options: Record<string, any>
125
+ readonly location: { readonly directory?: string } | undefined
126
+ readonly renderer: { readonly terminalWidth: number }
127
+ readonly theme: Theme
128
+ readonly data: Data
129
+ readonly client: ClientLike
130
+ readonly storage: Storage
131
+ readonly ui: {
132
+ slot(claim: SlotClaim): () => void
133
+ readonly toast: {
134
+ show(options: { readonly message: string; readonly title?: string; readonly variant?: string }): void
135
+ }
136
+ readonly dialog: {
137
+ prompt(options: { readonly title: string; readonly message?: string; readonly placeholder?: string }): Promise<string | undefined>
138
+ select<Value>(options: {
139
+ readonly title: string
140
+ readonly placeholder?: string
141
+ readonly options: readonly {
142
+ readonly title: string
143
+ readonly value: Value
144
+ readonly description?: string
145
+ readonly category?: string
146
+ readonly disabled?: boolean
147
+ }[]
148
+ readonly current?: Value
149
+ }): Promise<Value | undefined>
150
+ }
151
+ readonly router: {
152
+ current(): { readonly type?: string; readonly sessionID?: string; readonly params?: Record<string, unknown> }
153
+ navigate(route: { type: string; sessionID?: string; params?: Record<string, unknown> }): void
154
+ }
155
+ }
156
+ readonly keymap: {
157
+ layer(input: () => { readonly commands?: readonly KeymapCommand[] }): void
158
+ shortcuts(id: string): readonly string[]
159
+ }
160
+ }
161
+
162
+ export type PluginModule = {
163
+ readonly id: string
164
+ readonly setup: (context: Context) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>
165
+ }
package/tui/index.js CHANGED
@@ -1,11 +1,11 @@
1
- // tui/index.js——V1/V2 共用入口(双格式)
2
- // - V1(opencode):exports["./tui"] → 本文件——readV1Plugin 读 tui 字段(V1 面板)
3
- // - V2(opencode2):exports["./tui"] → 本文件——isPlugin 读 setup 字段(V2 面板)
4
- import tuiMod from "./../dist/tui.js"
5
- import v2Mod from "./../dist/v2.js"
6
-
7
- export default {
8
- id: "opencode-subagent-magazine",
9
- tui: tuiMod.tui,
10
- setup: v2Mod.setup,
11
- }
1
+ // tui/index.js——V1/V2 共用入口(双格式)
2
+ // - V1(opencode):exports["./tui"] → 本文件——readV1Plugin 读 tui 字段(V1 面板)
3
+ // - V2(opencode2):exports["./tui"] → 本文件——isPlugin 读 setup 字段(V2 面板)
4
+ import tuiMod from "./../dist/tui.js"
5
+ import v2Mod from "./../dist/v2.js"
6
+
7
+ export default {
8
+ id: "opencode-subagent-magazine",
9
+ tui: tuiMod.tui,
10
+ setup: v2Mod.setup,
11
+ }