opencode-subagent-magazine 1.1.3 → 1.2.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.
- package/README.md +12 -4
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +169 -8
- package/dist/tui.js +412 -233
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +187 -9
package/package.json
CHANGED
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.
|
|
2
|
+
export const PLUGIN_VERSION="1.2.0";
|
package/src/index.tsx
CHANGED
|
@@ -45,6 +45,8 @@ interface SubEntry {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
type Lang = "zh" | "en"
|
|
48
|
+
type SortOrder = "desc" | "asc"
|
|
49
|
+
type ScrollMode = "wheel" | "click"
|
|
48
50
|
|
|
49
51
|
/** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
|
|
50
52
|
const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"])
|
|
@@ -68,10 +70,15 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
68
70
|
"cost.label": "费用",
|
|
69
71
|
"scroll.more": "更多",
|
|
70
72
|
"scroll.top": "回顶",
|
|
73
|
+
"scroll.bottom": "回底",
|
|
71
74
|
"dismiss.label": "标记完成",
|
|
72
75
|
"status.running": "运行中",
|
|
73
76
|
"status.done": "已完成",
|
|
74
77
|
"status.error": "错误",
|
|
78
|
+
"order.desc": "降序(最新在前)",
|
|
79
|
+
"order.asc": "升序(最早在前)",
|
|
80
|
+
"scroll.wheel": "滚轮翻页",
|
|
81
|
+
"scroll.click": "点击翻页",
|
|
75
82
|
},
|
|
76
83
|
en: {
|
|
77
84
|
"panel.title": "SubAgent",
|
|
@@ -87,10 +94,15 @@ const I18N: Record<Lang, Record<string, string>> = {
|
|
|
87
94
|
"cost.label": "cost",
|
|
88
95
|
"scroll.more": "more",
|
|
89
96
|
"scroll.top": "Top",
|
|
97
|
+
"scroll.bottom": "Bottom",
|
|
90
98
|
"dismiss.label": "dismiss",
|
|
91
99
|
"status.running": "running",
|
|
92
100
|
"status.done": "done",
|
|
93
101
|
"status.error": "error",
|
|
102
|
+
"order.desc": "Desc (newest first)",
|
|
103
|
+
"order.asc": "Asc (oldest first)",
|
|
104
|
+
"scroll.wheel": "Wheel Scroll",
|
|
105
|
+
"scroll.click": "Click Scroll",
|
|
94
106
|
},
|
|
95
107
|
}
|
|
96
108
|
|
|
@@ -255,6 +267,8 @@ function SubAgentPanel(props: {
|
|
|
255
267
|
api: TuiPluginApi
|
|
256
268
|
lang: () => Lang
|
|
257
269
|
maxEntries: () => number
|
|
270
|
+
sortOrder: () => SortOrder
|
|
271
|
+
scrollMode: () => ScrollMode
|
|
258
272
|
sessionId: string
|
|
259
273
|
}): JSX.Element {
|
|
260
274
|
const t = (key: string) => I18N[props.lang()][key] ?? key
|
|
@@ -385,6 +399,8 @@ function SubAgentPanel(props: {
|
|
|
385
399
|
const [hoveredOpen, setHoveredOpen] = createSignal<string | undefined>(undefined)
|
|
386
400
|
const [hoveredDismiss, setHoveredDismiss] = createSignal<string | undefined>(undefined)
|
|
387
401
|
const [hoveredTop, setHoveredTop] = createSignal(false)
|
|
402
|
+
const [hoveredMoreAbove, setHoveredMoreAbove] = createSignal(false)
|
|
403
|
+
const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false)
|
|
388
404
|
const [scrollOffset, setScrollOffset] = createSignal(
|
|
389
405
|
(() => { try { return loadSessionData()[props.sessionId]?.scroll ?? 0 } catch { return 0 } })()
|
|
390
406
|
)
|
|
@@ -1020,7 +1036,11 @@ function SubAgentPanel(props: {
|
|
|
1020
1036
|
// ── derived signals ──
|
|
1021
1037
|
// Stable list — only changes when entryMap changes
|
|
1022
1038
|
const entryList = createMemo(() => {
|
|
1023
|
-
|
|
1039
|
+
const entries = [...entryMap().values()]
|
|
1040
|
+
if (props.sortOrder() === "desc") {
|
|
1041
|
+
return entries.sort((a, b) => b.startedAt - a.startedAt)
|
|
1042
|
+
}
|
|
1043
|
+
return entries.sort((a, b) => a.startedAt - b.startedAt)
|
|
1024
1044
|
})
|
|
1025
1045
|
|
|
1026
1046
|
const max = props.maxEntries
|
|
@@ -1034,6 +1054,48 @@ function SubAgentPanel(props: {
|
|
|
1034
1054
|
const hiddenAbove = createMemo(() => clampedOffset())
|
|
1035
1055
|
const hiddenBelow = createMemo(() => Math.max(0, entryList().length - clampedOffset() - max()))
|
|
1036
1056
|
|
|
1057
|
+
// Drop hover state when ↑ more disappears (hiddenAbove hits zero)
|
|
1058
|
+
createEffect(() => {
|
|
1059
|
+
if (hiddenAbove() === 0) setHoveredMoreAbove(false)
|
|
1060
|
+
})
|
|
1061
|
+
|
|
1062
|
+
// Reset scroll on sort order change: jump to newest in view
|
|
1063
|
+
let sortInitialized = false
|
|
1064
|
+
createEffect(() => {
|
|
1065
|
+
props.sortOrder()
|
|
1066
|
+
if (!sortInitialized) { sortInitialized = true; return }
|
|
1067
|
+
const total = untrack(() => entryList().length)
|
|
1068
|
+
const m = untrack(() => max())
|
|
1069
|
+
const target = props.sortOrder() === "desc" ? 0 : Math.max(0, total - m)
|
|
1070
|
+
setScrollOffset(target)
|
|
1071
|
+
setTimeout(() => {
|
|
1072
|
+
try { persistScroll(props.sessionId, target) } catch {}
|
|
1073
|
+
}, 0)
|
|
1074
|
+
})
|
|
1075
|
+
|
|
1076
|
+
// When new entries arrive while viewing the newest end, keep the view at newest
|
|
1077
|
+
let prevEntryCount = 0
|
|
1078
|
+
createEffect(() => {
|
|
1079
|
+
const total = entryList().length
|
|
1080
|
+
if (prevEntryCount === 0) { prevEntryCount = total; return }
|
|
1081
|
+
if (total === prevEntryCount) return
|
|
1082
|
+
|
|
1083
|
+
const m = max()
|
|
1084
|
+
const wasAtNewest = props.sortOrder() === "desc"
|
|
1085
|
+
? untrack(() => scrollOffset() === 0)
|
|
1086
|
+
: untrack(() => scrollOffset() >= prevEntryCount - m)
|
|
1087
|
+
|
|
1088
|
+
prevEntryCount = total
|
|
1089
|
+
|
|
1090
|
+
if (wasAtNewest) {
|
|
1091
|
+
const target = props.sortOrder() === "desc" ? 0 : Math.max(0, total - m)
|
|
1092
|
+
setScrollOffset(target)
|
|
1093
|
+
setTimeout(() => {
|
|
1094
|
+
try { persistScroll(props.sessionId, target) } catch {}
|
|
1095
|
+
}, 0)
|
|
1096
|
+
}
|
|
1097
|
+
})
|
|
1098
|
+
|
|
1037
1099
|
const entries = createMemo(() => {
|
|
1038
1100
|
const nowVal = now()
|
|
1039
1101
|
return entryList().map((e) => ({
|
|
@@ -1193,6 +1255,7 @@ function SubAgentPanel(props: {
|
|
|
1193
1255
|
>
|
|
1194
1256
|
<box
|
|
1195
1257
|
onMouseScroll={(e) => {
|
|
1258
|
+
if (props.scrollMode() === "click") return
|
|
1196
1259
|
const total = entryList().length
|
|
1197
1260
|
const m = max()
|
|
1198
1261
|
if (total <= m) return
|
|
@@ -1205,8 +1268,28 @@ function SubAgentPanel(props: {
|
|
|
1205
1268
|
}}
|
|
1206
1269
|
>
|
|
1207
1270
|
<Show when={hiddenAbove() > 0}>
|
|
1208
|
-
<text
|
|
1209
|
-
{
|
|
1271
|
+
<text
|
|
1272
|
+
onMouseOver={() => setHoveredMoreAbove(true)}
|
|
1273
|
+
onMouseOut={() => setHoveredMoreAbove(false)}
|
|
1274
|
+
onMouseUp={() => {
|
|
1275
|
+
const total = entryList().length
|
|
1276
|
+
const m = max()
|
|
1277
|
+
if (total <= m) return
|
|
1278
|
+
const next = Math.max(0, scrollOffset() - m)
|
|
1279
|
+
if (next === 0) {
|
|
1280
|
+
setTimeout(() => {
|
|
1281
|
+
setScrollOffset(next)
|
|
1282
|
+
try { persistScroll(props.sessionId, next) } catch {}
|
|
1283
|
+
}, 0)
|
|
1284
|
+
} else {
|
|
1285
|
+
setScrollOffset(next)
|
|
1286
|
+
try { persistScroll(props.sessionId, next) } catch {}
|
|
1287
|
+
}
|
|
1288
|
+
}}
|
|
1289
|
+
>
|
|
1290
|
+
<span style={{ fg: hoveredMoreAbove() ? pal().warning : pal().muted }}>
|
|
1291
|
+
{" "}↑ {hiddenAbove()} {t("scroll.more")}
|
|
1292
|
+
</span>
|
|
1210
1293
|
</text>
|
|
1211
1294
|
</Show>
|
|
1212
1295
|
<For each={visibleList()}>
|
|
@@ -1394,23 +1477,52 @@ function SubAgentPanel(props: {
|
|
|
1394
1477
|
)
|
|
1395
1478
|
}}
|
|
1396
1479
|
</For>
|
|
1397
|
-
<Show when={hiddenBelow() > 0 || scrollOffset() > 0}>
|
|
1480
|
+
<Show when={hiddenBelow() > 0 || (props.sortOrder() === "desc" ? scrollOffset() > 0 : entryList().length > max() && clampedOffset() < entryList().length - max())}>
|
|
1398
1481
|
{(() => {
|
|
1399
1482
|
const showMore = hiddenBelow() > 0
|
|
1400
|
-
const showTop =
|
|
1483
|
+
const showTop = props.sortOrder() === "desc"
|
|
1484
|
+
? scrollOffset() > 0
|
|
1485
|
+
: entryList().length > max() && clampedOffset() < entryList().length - max()
|
|
1401
1486
|
const left = showMore ? ` \u2193 ${hiddenBelow()} ${t("scroll.more")}` : " "
|
|
1402
|
-
const right =
|
|
1487
|
+
const right = props.sortOrder() === "desc"
|
|
1488
|
+
? `\u2191 ${t("scroll.top")}`
|
|
1489
|
+
: `\u2193 ${t("scroll.bottom")}`
|
|
1403
1490
|
const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0
|
|
1404
1491
|
return (
|
|
1405
1492
|
<box flexDirection="row">
|
|
1406
|
-
<text
|
|
1493
|
+
<text
|
|
1494
|
+
onMouseOver={() => showMore && setHoveredMoreBelow(true)}
|
|
1495
|
+
onMouseOut={() => setHoveredMoreBelow(false)}
|
|
1496
|
+
onMouseUp={() => {
|
|
1497
|
+
if (!showMore) return
|
|
1498
|
+
const total = entryList().length
|
|
1499
|
+
const m = max()
|
|
1500
|
+
if (total <= m) return
|
|
1501
|
+
setScrollOffset((prev) => Math.min(total - m, prev + m))
|
|
1502
|
+
try { persistScroll(props.sessionId, scrollOffset()) } catch {}
|
|
1503
|
+
setHoveredMoreBelow(false)
|
|
1504
|
+
}}
|
|
1505
|
+
>
|
|
1506
|
+
<span style={{ fg: showMore && hoveredMoreBelow() ? pal().warning : pal().muted }}>
|
|
1507
|
+
{left}
|
|
1508
|
+
</span>
|
|
1509
|
+
</text>
|
|
1407
1510
|
{showTop ? (
|
|
1408
1511
|
<>
|
|
1409
1512
|
<text style={{ fg: pal().muted }}>{" ".repeat(pad)}</text>
|
|
1410
1513
|
<text
|
|
1411
1514
|
onMouseOver={() => setHoveredTop(true)}
|
|
1412
1515
|
onMouseOut={() => setHoveredTop(false)}
|
|
1413
|
-
onMouseUp={() => {
|
|
1516
|
+
onMouseUp={() => {
|
|
1517
|
+
const total = entryList().length
|
|
1518
|
+
const m = max()
|
|
1519
|
+
if (props.sortOrder() === "desc") {
|
|
1520
|
+
setScrollOffset(0)
|
|
1521
|
+
} else {
|
|
1522
|
+
setScrollOffset(Math.max(0, total - m))
|
|
1523
|
+
}
|
|
1524
|
+
setHoveredTop(false)
|
|
1525
|
+
}}
|
|
1414
1526
|
>
|
|
1415
1527
|
<span style={{ fg: hoveredTop() ? pal().warning : pal().muted }}>{right}</span>
|
|
1416
1528
|
</text>
|
|
@@ -1436,6 +1548,10 @@ interface SharedSignals {
|
|
|
1436
1548
|
setLang: (l: Lang) => void
|
|
1437
1549
|
maxEntries: () => number
|
|
1438
1550
|
setMaxEntries: (n: number) => void
|
|
1551
|
+
sortOrder: () => SortOrder
|
|
1552
|
+
setSortOrder: (o: SortOrder) => void
|
|
1553
|
+
scrollMode: () => ScrollMode
|
|
1554
|
+
setScrollMode: (m: ScrollMode) => void
|
|
1439
1555
|
sessionId: string
|
|
1440
1556
|
}
|
|
1441
1557
|
|
|
@@ -1451,6 +1567,8 @@ function createSidebarSlot(api: TuiPluginApi, sig: SharedSignals): TuiSlotPlugin
|
|
|
1451
1567
|
api={api}
|
|
1452
1568
|
lang={sig.lang}
|
|
1453
1569
|
maxEntries={sig.maxEntries}
|
|
1570
|
+
sortOrder={sig.sortOrder}
|
|
1571
|
+
scrollMode={sig.scrollMode}
|
|
1454
1572
|
sessionId={input.session_id}
|
|
1455
1573
|
/>
|
|
1456
1574
|
)
|
|
@@ -1470,8 +1588,14 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1470
1588
|
const [maxEntries, setMaxEntries] = createSignal(
|
|
1471
1589
|
parseInt(String(api.kv.get(`${KV_PREFIX}.max_entries`, "10")), 10) || 10
|
|
1472
1590
|
)
|
|
1591
|
+
const [sortOrder, setSortOrder] = createSignal<SortOrder>(
|
|
1592
|
+
String(api.kv.get(`${KV_PREFIX}.order`, "desc")) === "asc" ? "asc" : "desc"
|
|
1593
|
+
)
|
|
1594
|
+
const [scrollMode, setScrollMode] = createSignal<ScrollMode>(
|
|
1595
|
+
String(api.kv.get(`${KV_PREFIX}.scroll_mode`, "wheel")) === "click" ? "click" : "wheel"
|
|
1596
|
+
)
|
|
1473
1597
|
|
|
1474
|
-
const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sessionId: "" }
|
|
1598
|
+
const signals: SharedSignals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, sessionId: "" }
|
|
1475
1599
|
|
|
1476
1600
|
api.slots.register(createSidebarSlot(api, signals))
|
|
1477
1601
|
|
|
@@ -1503,6 +1627,60 @@ const tui: TuiPlugin = async (api: TuiPluginApi) => {
|
|
|
1503
1627
|
))
|
|
1504
1628
|
},
|
|
1505
1629
|
},
|
|
1630
|
+
{
|
|
1631
|
+
title: "SubAgent Magazine: Sort Order",
|
|
1632
|
+
value: "subagent-order",
|
|
1633
|
+
description: "Set sub-agent entry sort order (desc / asc)",
|
|
1634
|
+
slash: { name: "subagent-order" },
|
|
1635
|
+
onSelect: (dialog) => {
|
|
1636
|
+
const t = (k: string) => I18N[lang()][k] ?? k
|
|
1637
|
+
dialog?.replace(() => (
|
|
1638
|
+
<api.ui.DialogSelect
|
|
1639
|
+
title="Sort Order / 排序方式"
|
|
1640
|
+
options={[
|
|
1641
|
+
{ title: t("order.desc"), value: "desc" },
|
|
1642
|
+
{ title: t("order.asc"), value: "asc" },
|
|
1643
|
+
]}
|
|
1644
|
+
onSelect={(opt) => {
|
|
1645
|
+
const o = opt.value as SortOrder
|
|
1646
|
+
setSortOrder(o)
|
|
1647
|
+
api.kv.set(`${KV_PREFIX}.order`, o)
|
|
1648
|
+
api.ui.toast({
|
|
1649
|
+
message: o === "desc" ? t("order.desc") : t("order.asc"),
|
|
1650
|
+
})
|
|
1651
|
+
dialog?.clear()
|
|
1652
|
+
}}
|
|
1653
|
+
/>
|
|
1654
|
+
))
|
|
1655
|
+
},
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
title: "SubAgent Magazine: Scroll Mode",
|
|
1659
|
+
value: "subagent-scroll",
|
|
1660
|
+
description: "Set scroll mode (wheel / click)",
|
|
1661
|
+
slash: { name: "subagent-scroll" },
|
|
1662
|
+
onSelect: (dialog) => {
|
|
1663
|
+
const t = (k: string) => I18N[lang()][k] ?? k
|
|
1664
|
+
dialog?.replace(() => (
|
|
1665
|
+
<api.ui.DialogSelect
|
|
1666
|
+
title="Scroll Mode / 滚动模式"
|
|
1667
|
+
options={[
|
|
1668
|
+
{ title: t("scroll.wheel"), value: "wheel" },
|
|
1669
|
+
{ title: t("scroll.click"), value: "click" },
|
|
1670
|
+
]}
|
|
1671
|
+
onSelect={(opt) => {
|
|
1672
|
+
const m = opt.value as ScrollMode
|
|
1673
|
+
setScrollMode(m)
|
|
1674
|
+
api.kv.set(`${KV_PREFIX}.scroll_mode`, m)
|
|
1675
|
+
api.ui.toast({
|
|
1676
|
+
message: m === "wheel" ? t("scroll.wheel") : t("scroll.click"),
|
|
1677
|
+
})
|
|
1678
|
+
dialog?.clear()
|
|
1679
|
+
}}
|
|
1680
|
+
/>
|
|
1681
|
+
))
|
|
1682
|
+
},
|
|
1683
|
+
},
|
|
1506
1684
|
{
|
|
1507
1685
|
title: "SubAgent Magazine: Max Entries",
|
|
1508
1686
|
value: "subagent-max",
|