opencode-subagent-magazine 1.6.0-beta.3 → 1.6.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.
@@ -1,39 +1,39 @@
1
- import type { SubEntry } from "../core/types"
2
-
3
- export type SubEntryPatch = Omit<SubEntry, "startedAt" | "endedAt"> & {
4
- startedAt?: number
5
- }
6
-
7
- export function findSubEntryKey(
8
- entries: ReadonlyMap<string, SubEntry>,
9
- id: string,
10
- sessionId?: string,
11
- ) {
12
- if (sessionId) {
13
- for (const [key, entry] of entries) {
14
- if (entry.sessionId === sessionId) return key
15
- }
16
- }
17
- return entries.has(id) ? id : undefined
18
- }
19
-
20
- export function upsertSubEntry(
21
- entries: ReadonlyMap<string, SubEntry>,
22
- partial: SubEntryPatch,
23
- now = Date.now(),
24
- ) {
25
- const key = findSubEntryKey(entries, partial.id, partial.sessionId) ?? partial.id
26
- const existing = entries.get(key)
27
- const next = new Map(entries)
28
- if (key !== partial.id) next.delete(partial.id)
29
-
30
- const ended = partial.status === "done" || partial.status === "error" || partial.status === "cancelled"
31
- next.set(key, {
32
- ...(existing ?? { startedAt: now }),
33
- ...partial,
34
- id: existing?.id ?? partial.id,
35
- startedAt: existing?.startedAt || partial.startedAt || now,
36
- endedAt: ended ? (existing?.endedAt || now) : undefined,
37
- })
38
- return next
39
- }
1
+ import type { SubEntry } from "../core/types"
2
+
3
+ export type SubEntryPatch = Omit<SubEntry, "startedAt" | "endedAt"> & {
4
+ startedAt?: number
5
+ }
6
+
7
+ export function findSubEntryKey(
8
+ entries: ReadonlyMap<string, SubEntry>,
9
+ id: string,
10
+ sessionId?: string,
11
+ ) {
12
+ if (sessionId) {
13
+ for (const [key, entry] of entries) {
14
+ if (entry.sessionId === sessionId) return key
15
+ }
16
+ }
17
+ return entries.has(id) ? id : undefined
18
+ }
19
+
20
+ export function upsertSubEntry(
21
+ entries: ReadonlyMap<string, SubEntry>,
22
+ partial: SubEntryPatch,
23
+ now = Date.now(),
24
+ ) {
25
+ const key = findSubEntryKey(entries, partial.id, partial.sessionId) ?? partial.id
26
+ const existing = entries.get(key)
27
+ const next = new Map(entries)
28
+ if (key !== partial.id) next.delete(partial.id)
29
+
30
+ const ended = partial.status === "done" || partial.status === "error" || partial.status === "cancelled"
31
+ next.set(key, {
32
+ ...(existing ?? { startedAt: now }),
33
+ ...partial,
34
+ id: existing?.id ?? partial.id,
35
+ startedAt: existing?.startedAt || partial.startedAt || now,
36
+ endedAt: ended ? (existing?.endedAt || now) : undefined,
37
+ })
38
+ return next
39
+ }
@@ -1,75 +1,75 @@
1
- import type { KVApi } from "../core/kv"
2
- import type { UsageReader, TodoStats } from "../core/usage"
3
- import type { Lang, SortOrder, ScrollMode } from "../core/types"
4
-
5
- /** Minimal session shape the panel reads from the host SDK. */
6
- export interface SessionLike {
7
- id?: string
8
- parentID?: string
9
- agent?: string
10
- cost?: number
11
- }
12
-
13
- export interface SessionStatusLike {
14
- type: string
15
- }
16
-
17
- /**
18
- * Standardized panel events. Adapter layers (V1/V2) translate host events
19
- * into these shapes so the panel component never touches host APIs directly.
20
- */
21
- export type PanelEventType = "part.updated" | "message.updated" | "session.idle" | "session.error"
22
- export type PanelEventScope = "session" | "global"
23
-
24
- export interface PanelEvent {
25
- type: PanelEventType
26
- /** Global V2 events need explicit session ownership checks. */
27
- scope?: PanelEventScope
28
- /** part payload for part.updated; session properties for idle/error. */
29
- payload?: Record<string, unknown>
30
- }
31
-
32
- export interface ToastOptions {
33
- variant?: "success" | "warning" | "error"
34
- title?: string
35
- duration?: number
36
- }
37
-
38
- /**
39
- * The full surface the SubAgentPanel component consumes.
40
- * V1 and V2 each implement this against their host SDK; the panel is agnostic.
41
- * Theme is slot-scoped in both hosts, so it is passed as a panel prop,
42
- * not part of the API.
43
- */
44
- export interface PanelApi {
45
- kv: KVApi
46
- usage: UsageReader
47
- session: {
48
- get(sid: string): SessionLike | undefined
49
- status(sid: string): SessionStatusLike | undefined
50
- /** Raw message list for a session (scan / error extraction). */
51
- messages(sid: string): unknown[] | undefined
52
- /** Raw parts of a message (scan). */
53
- part(messageID: string): unknown[] | undefined
54
- }
55
- event: {
56
- on(type: PanelEventType, cb: (e: PanelEvent) => void): () => void
57
- }
58
- client: {
59
- abort(input: { sessionID: string }): Promise<void>
60
- }
61
- route: {
62
- navigateSession(sessionID: string): void
63
- }
64
- ui: {
65
- toast(message: string, opts?: ToastOptions): void
66
- }
67
- settings: {
68
- lang: () => Lang
69
- maxEntries: () => number
70
- sortOrder: () => SortOrder
71
- scrollMode: () => ScrollMode
72
- }
73
- }
74
-
75
- export type { TodoStats }
1
+ import type { KVApi } from "../core/kv"
2
+ import type { UsageReader, TodoStats } from "../core/usage"
3
+ import type { Lang, SortOrder, ScrollMode } from "../core/types"
4
+
5
+ /** Minimal session shape the panel reads from the host SDK. */
6
+ export interface SessionLike {
7
+ id?: string
8
+ parentID?: string
9
+ agent?: string
10
+ cost?: number
11
+ }
12
+
13
+ export interface SessionStatusLike {
14
+ type: string
15
+ }
16
+
17
+ /**
18
+ * Standardized panel events. Adapter layers (V1/V2) translate host events
19
+ * into these shapes so the panel component never touches host APIs directly.
20
+ */
21
+ export type PanelEventType = "part.updated" | "message.updated" | "session.idle" | "session.error"
22
+ export type PanelEventScope = "session" | "global"
23
+
24
+ export interface PanelEvent {
25
+ type: PanelEventType
26
+ /** Global V2 events need explicit session ownership checks. */
27
+ scope?: PanelEventScope
28
+ /** part payload for part.updated; session properties for idle/error. */
29
+ payload?: Record<string, unknown>
30
+ }
31
+
32
+ export interface ToastOptions {
33
+ variant?: "success" | "warning" | "error"
34
+ title?: string
35
+ duration?: number
36
+ }
37
+
38
+ /**
39
+ * The full surface the SubAgentPanel component consumes.
40
+ * V1 and V2 each implement this against their host SDK; the panel is agnostic.
41
+ * Theme is slot-scoped in both hosts, so it is passed as a panel prop,
42
+ * not part of the API.
43
+ */
44
+ export interface PanelApi {
45
+ kv: KVApi
46
+ usage: UsageReader
47
+ session: {
48
+ get(sid: string): SessionLike | undefined
49
+ status(sid: string): SessionStatusLike | undefined
50
+ /** Raw message list for a session (scan / error extraction). */
51
+ messages(sid: string): unknown[] | undefined
52
+ /** Raw parts of a message (scan). */
53
+ part(messageID: string): unknown[] | undefined
54
+ }
55
+ event: {
56
+ on(type: PanelEventType, cb: (e: PanelEvent) => void): () => void
57
+ }
58
+ client: {
59
+ abort(input: { sessionID: string }): Promise<void>
60
+ }
61
+ route: {
62
+ navigateSession(sessionID: string): void
63
+ }
64
+ ui: {
65
+ toast(message: string, opts?: ToastOptions): void
66
+ }
67
+ settings: {
68
+ lang: () => Lang
69
+ maxEntries: () => number
70
+ sortOrder: () => SortOrder
71
+ scrollMode: () => ScrollMode
72
+ }
73
+ }
74
+
75
+ export type { TodoStats }
@@ -1,10 +1,10 @@
1
- import type { SessionLike } from "./panel-api"
2
-
3
- /** Whether an end event belongs to a child tracked by the current panel. */
4
- export function isDirectChildSession(
5
- currentSessionId: string,
6
- eventSessionId: string,
7
- eventSession: SessionLike | undefined,
8
- ): boolean {
9
- return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId
10
- }
1
+ import type { SessionLike } from "./panel-api"
2
+
3
+ /** Whether an end event belongs to a child tracked by the current panel. */
4
+ export function isDirectChildSession(
5
+ currentSessionId: string,
6
+ eventSessionId: string,
7
+ eventSession: SessionLike | undefined,
8
+ ): boolean {
9
+ return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId
10
+ }
package/src/server.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { Plugin, PluginModule } from "@opencode-ai/plugin"
2
-
3
- const server: Plugin = async () => ({})
4
-
5
- const mod: PluginModule = {
6
- id: "opencode-subagent-magazine",
7
- server,
8
- }
9
-
10
- export default mod
1
+ import type { Plugin, PluginModule } from "@opencode-ai/plugin"
2
+
3
+ const server: Plugin = async () => ({})
4
+
5
+ const mod: PluginModule = {
6
+ id: "opencode-subagent-magazine",
7
+ server,
8
+ }
9
+
10
+ export default mod
@@ -245,6 +245,19 @@ export function makeCommands(context: Context, api: PanelApi, signals: SharedSig
245
245
  } catch {}
246
246
  },
247
247
  },
248
+ {
249
+ id: "opencode-subagent-magazine.subagent.border",
250
+ title: "SubAgent Magazine: Border",
251
+ description: "Show or hide the panel border",
252
+ slash: { name: "subagent-border" },
253
+ palette: true,
254
+ run: () => {
255
+ const cur = Boolean(kv.get(SETTING_KEYS.border, false))
256
+ kv.set(SETTING_KEYS.border, !cur)
257
+ signals.setBorderVisible(!cur)
258
+ api.ui.toast(!cur ? t("borderShown") : t("borderHidden"))
259
+ },
260
+ },
248
261
  ]
249
262
  }
250
263
 
package/src/v2/index.tsx CHANGED
@@ -31,6 +31,7 @@ function PluginRoot(props: {
31
31
  maxEntries={props.signals.maxEntries}
32
32
  sortOrder={props.signals.sortOrder}
33
33
  scrollMode={props.signals.scrollMode}
34
+ borderVisible={props.signals.borderVisible}
34
35
  sessionId={props.sessionID}
35
36
  />
36
37
  )
@@ -65,13 +66,16 @@ const mod: PluginModule & { server: () => Promise<Record<string, never>> } = {
65
66
  const [scrollSignal, setScrollMode] = createSignal<ScrollMode>(
66
67
  String(api.kv.get(SETTING_KEYS.scrollMode, "wheel")) === "click" ? "click" : "wheel",
67
68
  )
69
+ const [borderSignal, setBorderVisible] = createSignal<boolean>(
70
+ (api.kv.get(SETTING_KEYS.border, false) as boolean) === true,
71
+ )
68
72
  lang = langSignal
69
73
  maxEntries = maxSignal
70
74
  sortOrder = orderSignal
71
75
  scrollMode = scrollSignal
72
76
 
73
77
  const signals: SharedSignals = {
74
- lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "",
78
+ lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible: borderSignal, setBorderVisible, sessionId: "",
75
79
  }
76
80
 
77
81
  // 命令 layer(组件内注册——见 PluginRoot)