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/README.md +3 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/core/kv.d.ts +1 -0
- package/dist/core/kv.js +1 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +8 -0
- package/dist/index.js +17 -2
- package/dist/panel/SubAgentPanel.d.ts +1 -0
- package/dist/panel/SubAgentPanel.js +54 -37
- package/dist/panel/entry-map.d.ts +6 -0
- package/dist/panel/entry-map.js +25 -0
- package/dist/tui.js +128 -56
- package/dist/v2/commands.js +13 -0
- package/dist/v2/index.js +3 -2
- package/dist/v2.js +124 -56
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/color.ts +70 -70
- package/src/core/format.ts +61 -61
- package/src/core/index.ts +6 -6
- package/src/core/kv.ts +37 -36
- package/src/core/state-machine.ts +46 -46
- package/src/core/types.ts +2 -0
- package/src/core/usage.ts +16 -16
- package/src/i18n.ts +8 -0
- package/src/index.tsx +472 -454
- package/src/panel/SubAgentPanel.tsx +59 -37
- package/src/panel/entry-map.ts +39 -0
- package/src/panel/store.ts +8 -8
- package/src/v2/commands.ts +264 -251
- package/src/v2/index.tsx +102 -98
- package/src/v2/theme.ts +14 -14
- package/src/v2/types.ts +165 -165
- package/tui/index.js +11 -11
|
@@ -22,6 +22,7 @@ import { KV_PREFIX } from "../core/kv"
|
|
|
22
22
|
import type { PanelApi, PanelEvent } from "./panel-api"
|
|
23
23
|
import { globalEntryCache, clearTick } from "./store"
|
|
24
24
|
import { isDirectChildSession } from "./session-routing"
|
|
25
|
+
import { findSubEntryKey, upsertSubEntry } from "./entry-map"
|
|
25
26
|
|
|
26
27
|
/** Entry line left prefix: icon + space + status dot + space */
|
|
27
28
|
const LEFT_PAD = 4
|
|
@@ -38,6 +39,7 @@ export function SubAgentPanel(props: {
|
|
|
38
39
|
maxEntries: () => number
|
|
39
40
|
sortOrder: () => SortOrder
|
|
40
41
|
scrollMode: () => ScrollMode
|
|
42
|
+
borderVisible: () => boolean
|
|
41
43
|
sessionId: string
|
|
42
44
|
}): JSX.Element {
|
|
43
45
|
const t = createT(() => props.lang())
|
|
@@ -87,14 +89,16 @@ export function SubAgentPanel(props: {
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
const loadEntries = (sid: string): Map<string, SubEntry> => {
|
|
90
|
-
|
|
92
|
+
let m = new Map<string, SubEntry>()
|
|
91
93
|
try {
|
|
92
94
|
const { parentSid, isChild } = resolveParent(sid)
|
|
93
95
|
const rec = loadSessionData()[parentSid]
|
|
94
96
|
if (rec) {
|
|
95
97
|
const source = isChild ? rec.children?.[sid]?.entries : rec.entries
|
|
96
98
|
if (source) {
|
|
97
|
-
for (const e of source)
|
|
99
|
+
for (const e of source) {
|
|
100
|
+
m = upsertSubEntry(m, e)
|
|
101
|
+
}
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
} catch {}
|
|
@@ -256,20 +260,7 @@ export function SubAgentPanel(props: {
|
|
|
256
260
|
const upsertEntry = (
|
|
257
261
|
partial: Omit<SubEntry, "startedAt" | "endedAt"> & { startedAt?: number }
|
|
258
262
|
) => {
|
|
259
|
-
setEntryMap((prev) =>
|
|
260
|
-
const existing = prev.get(partial.id)
|
|
261
|
-
const next = new Map(prev)
|
|
262
|
-
const nowTs = Date.now()
|
|
263
|
-
const e = partial.status
|
|
264
|
-
const ended = e === "done" || e === "error" || e === "cancelled"
|
|
265
|
-
next.set(partial.id, {
|
|
266
|
-
...(existing ?? { startedAt: nowTs }),
|
|
267
|
-
...partial,
|
|
268
|
-
startedAt: existing?.startedAt || partial.startedAt || nowTs,
|
|
269
|
-
endedAt: ended ? (existing?.endedAt || nowTs) : undefined,
|
|
270
|
-
})
|
|
271
|
-
return next
|
|
272
|
-
})
|
|
263
|
+
setEntryMap((prev) => upsertSubEntry(prev, partial))
|
|
273
264
|
}
|
|
274
265
|
|
|
275
266
|
// ── cancel helpers ──
|
|
@@ -397,9 +388,21 @@ export function SubAgentPanel(props: {
|
|
|
397
388
|
if (rawStatus === "error") {
|
|
398
389
|
const id = `tool:${String(part.id ?? "")}`
|
|
399
390
|
if (!part.id) return
|
|
400
|
-
const
|
|
391
|
+
const stMeta = st?.metadata as Record<string, unknown> | undefined
|
|
392
|
+
const errorSid = stMeta?.session_id !== undefined ? String(stMeta.session_id)
|
|
393
|
+
: stMeta?.sessionId !== undefined ? String(stMeta.sessionId)
|
|
394
|
+
: undefined
|
|
395
|
+
const key = findSubEntryKey(entryMap(), id, errorSid)
|
|
396
|
+
const existing = key ? entryMap().get(key) : undefined
|
|
401
397
|
if (existing) {
|
|
402
|
-
upsertEntry({
|
|
398
|
+
upsertEntry({
|
|
399
|
+
id: existing.id,
|
|
400
|
+
title: existing.title,
|
|
401
|
+
agent: existing.agent,
|
|
402
|
+
prompt: existing.prompt,
|
|
403
|
+
sessionId: existing.sessionId ?? errorSid,
|
|
404
|
+
status: "error",
|
|
405
|
+
})
|
|
403
406
|
}
|
|
404
407
|
return
|
|
405
408
|
}
|
|
@@ -791,7 +794,12 @@ export function SubAgentPanel(props: {
|
|
|
791
794
|
|
|
792
795
|
const st = (part as any).state as Record<string, unknown> | undefined
|
|
793
796
|
const rawStatus = String(st?.status ?? "")
|
|
794
|
-
const
|
|
797
|
+
const scanStMeta = st?.metadata as Record<string, unknown> | undefined
|
|
798
|
+
const scanSubSid = scanStMeta?.session_id !== undefined ? String(scanStMeta.session_id)
|
|
799
|
+
: scanStMeta?.sessionId !== undefined ? String(scanStMeta.sessionId)
|
|
800
|
+
: undefined
|
|
801
|
+
const existingKey = findSubEntryKey(next, id, scanSubSid)
|
|
802
|
+
const exists = existingKey ? next.get(existingKey) : undefined
|
|
795
803
|
|
|
796
804
|
// 已手动清除的条目:scan 发现但不在内存 → 跳过重建
|
|
797
805
|
if (!exists && clearedIds.has(id)) continue
|
|
@@ -803,7 +811,7 @@ export function SubAgentPanel(props: {
|
|
|
803
811
|
// "error": only update existing, never create a new entry
|
|
804
812
|
if (rawStatus === "error") {
|
|
805
813
|
if (exists && exists.status === "running") {
|
|
806
|
-
next.set(id, { ...exists, status: "error", endedAt: Date.now() })
|
|
814
|
+
next.set(existingKey ?? id, { ...exists, status: "error", endedAt: Date.now() })
|
|
807
815
|
}
|
|
808
816
|
continue
|
|
809
817
|
}
|
|
@@ -819,7 +827,7 @@ export function SubAgentPanel(props: {
|
|
|
819
827
|
}
|
|
820
828
|
|
|
821
829
|
// Already settled → skip
|
|
822
|
-
if (exists && exists.status !== "running" && exists.status !== "cancel_requested") continue
|
|
830
|
+
if (exists && existingKey === id && exists.status !== "running" && exists.status !== "cancel_requested") continue
|
|
823
831
|
// Running entry with no explicit status improvement from part:
|
|
824
832
|
// try message-level heuristics first, then time-based fallback.
|
|
825
833
|
if (exists && status === "running") {
|
|
@@ -847,15 +855,12 @@ export function SubAgentPanel(props: {
|
|
|
847
855
|
const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40)
|
|
848
856
|
|
|
849
857
|
let tokens: number | undefined
|
|
850
|
-
const scanStMeta2 = st?.metadata as Record<string, unknown> | undefined
|
|
851
|
-
const scanSubSid = scanStMeta2?.session_id !== undefined ? String(scanStMeta2.session_id)
|
|
852
|
-
: scanStMeta2?.sessionId !== undefined ? String(scanStMeta2.sessionId)
|
|
853
|
-
: undefined
|
|
854
858
|
if (scanSubSid) tokens = props.api.usage.readSessionTokens(scanSubSid)
|
|
855
859
|
|
|
856
860
|
const ended = status === "done" // "error" handled above, never reaches here
|
|
857
|
-
|
|
858
|
-
|
|
861
|
+
const entryKey = existingKey ?? id
|
|
862
|
+
next.set(entryKey, {
|
|
863
|
+
id: exists?.id ?? id, title, agent, prompt,
|
|
859
864
|
// Preserve existing values (from handleSessionEnd / KV) — scan must not overwrite
|
|
860
865
|
tokens: exists?.tokens ?? tokens,
|
|
861
866
|
sessionId: exists?.sessionId ?? scanSubSid,
|
|
@@ -863,6 +868,7 @@ export function SubAgentPanel(props: {
|
|
|
863
868
|
startedAt: exists?.startedAt || Date.now(),
|
|
864
869
|
endedAt: ended ? (exists?.endedAt || Date.now()) : undefined,
|
|
865
870
|
})
|
|
871
|
+
if (entryKey !== id) next.delete(id)
|
|
866
872
|
}
|
|
867
873
|
}
|
|
868
874
|
}
|
|
@@ -1012,7 +1018,21 @@ export function SubAgentPanel(props: {
|
|
|
1012
1018
|
})
|
|
1013
1019
|
}
|
|
1014
1020
|
|
|
1015
|
-
|
|
1021
|
+
/** Horizontal space eaten by the panel border (1+1) and its padding (2+2). */
|
|
1022
|
+
const gutter = createMemo(() => (props.borderVisible() ? 6 : 0))
|
|
1023
|
+
|
|
1024
|
+
const sep = () => "\u2500".repeat(Math.max(1, panelWidth() - gutter()))
|
|
1025
|
+
|
|
1026
|
+
// The border toggling shifts the box dimensions, which may not reliably
|
|
1027
|
+
// re-fire onSizeChange across (re)mount cycles; re-sync panelWidth with the
|
|
1028
|
+
// live box so the content width stays correct.
|
|
1029
|
+
createEffect(() => {
|
|
1030
|
+
props.borderVisible()
|
|
1031
|
+
if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
|
|
1032
|
+
const w = Math.max(20, boxEl.width)
|
|
1033
|
+
setPanelWidth((prev) => (prev === w ? prev : w))
|
|
1034
|
+
}
|
|
1035
|
+
})
|
|
1016
1036
|
|
|
1017
1037
|
// ── expanded detail right-align ──
|
|
1018
1038
|
const expandedMaxLabelW = createMemo(() => {
|
|
@@ -1025,7 +1045,7 @@ export function SubAgentPanel(props: {
|
|
|
1025
1045
|
|
|
1026
1046
|
const expandedPad = (label: string) => Math.max(0, expandedMaxLabelW() - visualWidth(label + ": "))
|
|
1027
1047
|
|
|
1028
|
-
const expandedValAvail = () => Math.max(6, panelWidth() - INDENT - expandedMaxLabelW())
|
|
1048
|
+
const expandedValAvail = () => Math.max(6, panelWidth() - gutter() - INDENT - expandedMaxLabelW())
|
|
1029
1049
|
|
|
1030
1050
|
// ── header parts for colored spans ──
|
|
1031
1051
|
const summaryParts = createMemo(() => {
|
|
@@ -1059,7 +1079,7 @@ export function SubAgentPanel(props: {
|
|
|
1059
1079
|
if (!open()) return false
|
|
1060
1080
|
const icon = "\u25bc"
|
|
1061
1081
|
const need = visualWidth(icon) + 1 + visualWidth(t("panel.title")) + versionW + summaryCols()
|
|
1062
|
-
return need <= panelWidth()
|
|
1082
|
+
return need <= panelWidth() - gutter()
|
|
1063
1083
|
})
|
|
1064
1084
|
|
|
1065
1085
|
const leftCols = createMemo(() => {
|
|
@@ -1071,17 +1091,19 @@ export function SubAgentPanel(props: {
|
|
|
1071
1091
|
|
|
1072
1092
|
const spacerCols = createMemo(() => {
|
|
1073
1093
|
if (!anyEntry()) return 0
|
|
1074
|
-
return Math.max(0, panelWidth() - leftCols() - summaryCols())
|
|
1094
|
+
return Math.max(0, panelWidth() - gutter() - leftCols() - summaryCols())
|
|
1075
1095
|
})
|
|
1076
1096
|
|
|
1077
1097
|
const valueCols = (label: string) =>
|
|
1078
|
-
Math.max(4, panelWidth() - INDENT - visualWidth(label + ": "))
|
|
1098
|
+
Math.max(4, panelWidth() - gutter() - INDENT - visualWidth(label + ": "))
|
|
1079
1099
|
|
|
1080
1100
|
// ── render ──
|
|
1081
1101
|
return (
|
|
1082
1102
|
<box
|
|
1083
|
-
border={
|
|
1084
|
-
|
|
1103
|
+
border={props.borderVisible()}
|
|
1104
|
+
{...(props.borderVisible() ? { borderColor: pal().border } : {})}
|
|
1105
|
+
paddingTop={0} paddingBottom={0}
|
|
1106
|
+
paddingLeft={props.borderVisible() ? 2 : 0} paddingRight={props.borderVisible() ? 2 : 0}
|
|
1085
1107
|
flexDirection="column" gap={0}
|
|
1086
1108
|
ref={boxEl}
|
|
1087
1109
|
onSizeChange={() => {
|
|
@@ -1218,7 +1240,7 @@ export function SubAgentPanel(props: {
|
|
|
1218
1240
|
if (tk) w += visualWidth(tk)
|
|
1219
1241
|
return w
|
|
1220
1242
|
}
|
|
1221
|
-
const labelAvail = () => Math.max(6, panelWidth() - LEFT_PAD - suffixW())
|
|
1243
|
+
const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - suffixW())
|
|
1222
1244
|
const labelText = () => {
|
|
1223
1245
|
const max = labelAvail()
|
|
1224
1246
|
const text = entry.title || entry.agent
|
|
@@ -1360,7 +1382,7 @@ export function SubAgentPanel(props: {
|
|
|
1360
1382
|
const cancelLabel = () => ` ${t("cancel.label")}`
|
|
1361
1383
|
const dismissLabel = () => ` ${t("dismiss.label")}`
|
|
1362
1384
|
const rightW = (isRunning ? visualWidth(dismissLabel()) : 0) + (isRunning && entry.sessionId ? visualWidth(cancelLabel()) : 0)
|
|
1363
|
-
const spacerW = () => Math.max(1, panelWidth() - openW() - rightW - 2)
|
|
1385
|
+
const spacerW = () => Math.max(1, panelWidth() - gutter() - openW() - rightW - 2)
|
|
1364
1386
|
return (
|
|
1365
1387
|
<box flexDirection="row">
|
|
1366
1388
|
<Show when={entry.sessionId}>
|
|
@@ -1417,7 +1439,7 @@ export function SubAgentPanel(props: {
|
|
|
1417
1439
|
const right = props.sortOrder() === "desc"
|
|
1418
1440
|
? `\u2191 ${t("scroll.top")}`
|
|
1419
1441
|
: `\u2193 ${t("scroll.bottom")}`
|
|
1420
|
-
const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0
|
|
1442
|
+
const pad = showTop ? Math.max(1, panelWidth() - gutter() - visualWidth(left) - visualWidth(right)) : 0
|
|
1421
1443
|
return (
|
|
1422
1444
|
<box flexDirection="row">
|
|
1423
1445
|
<text
|
|
@@ -0,0 +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
|
+
}
|
package/src/panel/store.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createSignal } from "solid-js"
|
|
2
|
-
import type { SubEntry } from "../core/types"
|
|
3
|
-
|
|
4
|
-
/** 模块级缓存:各 session 的 entry 状态独立存储,不随当前视图切换而清除。 */
|
|
5
|
-
export const globalEntryCache = new Map<string, Map<string, SubEntry>>()
|
|
6
|
-
|
|
7
|
-
/** 模块级刷新信号:外部(如斜杠命令)触发清除后 +1,组件 scan 依赖它以重扫。 */
|
|
8
|
-
export const [clearTick, setClearTick] = createSignal(0)
|
|
1
|
+
import { createSignal } from "solid-js"
|
|
2
|
+
import type { SubEntry } from "../core/types"
|
|
3
|
+
|
|
4
|
+
/** 模块级缓存:各 session 的 entry 状态独立存储,不随当前视图切换而清除。 */
|
|
5
|
+
export const globalEntryCache = new Map<string, Map<string, SubEntry>>()
|
|
6
|
+
|
|
7
|
+
/** 模块级刷新信号:外部(如斜杠命令)触发清除后 +1,组件 scan 依赖它以重扫。 */
|
|
8
|
+
export const [clearTick, setClearTick] = createSignal(0)
|