opencode-subagent-magazine 1.3.0 → 1.4.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/dist/tui.js CHANGED
@@ -11,7 +11,7 @@ import { createElement as _$createElement } from "@opentui/solid";
11
11
  import { createMemo, createSignal, createEffect, onMount, onCleanup, untrack, Show, For } from "solid-js";
12
12
 
13
13
  // src/_version.ts
14
- var PLUGIN_VERSION = "1.3.0";
14
+ var PLUGIN_VERSION = "1.4.0";
15
15
 
16
16
  // src/index.tsx
17
17
  var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["task", "delegate", "call_omo_agent"]);
@@ -40,7 +40,20 @@ var I18N = {
40
40
  "order.desc": "\u964D\u5E8F\uFF08\u6700\u65B0\u5728\u524D\uFF09",
41
41
  "order.asc": "\u5347\u5E8F\uFF08\u6700\u65E9\u5728\u524D\uFF09",
42
42
  "scroll.wheel": "\u6EDA\u8F6E\u7FFB\u9875",
43
- "scroll.click": "\u70B9\u51FB\u7FFB\u9875"
43
+ "scroll.click": "\u70B9\u51FB\u7FFB\u9875",
44
+ "ttl.label": "\u6E05\u7406\u5468\u671F",
45
+ "ttl.3d": "3 \u5929",
46
+ "ttl.7d": "7 \u5929",
47
+ "ttl.14d": "14 \u5929",
48
+ "ttl.30d": "30 \u5929",
49
+ "ttl.unlimited": "\u65E0\u671F\u9650",
50
+ "ttl.toast": "\u6E05\u7406\u5468\u671F\u5DF2\u8BBE\u4E3A {n} \u5929",
51
+ "ttl.toast_unlimited": "\u6E05\u7406\u5468\u671F\u5DF2\u8BBE\u4E3A\u65E0\u671F\u9650",
52
+ "clear.title": "\u786E\u8BA4\u6E05\u9664",
53
+ "clear.prompt": "\u786E\u5B9A\u6E05\u9664\u5F53\u524D\u4F1A\u8BDD\u6240\u6709\u5B50\u4EE3\u7406\u8BB0\u5F55\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002",
54
+ "clear.prompt_running": "\u5F53\u524D\u6709 {n} \u4E2A\u8FD0\u884C\u4E2D\u7684\u5B50\u4EE3\u7406\uFF0C\u6E05\u9664\u540E\u5C06\u4E0D\u53EF\u6062\u590D\u3002\u786E\u5B9A\u7EE7\u7EED\uFF1F",
55
+ "clear.done": "\u5DF2\u6E05\u9664 {n} \u6761\u5B50\u4EE3\u7406\u8BB0\u5F55",
56
+ "clear.empty": "\u5F53\u524D\u4F1A\u8BDD\u65E0\u5B50\u4EE3\u7406\u8BB0\u5F55"
44
57
  },
45
58
  en: {
46
59
  "panel.title": "SubAgent",
@@ -66,7 +79,20 @@ var I18N = {
66
79
  "order.desc": "Desc (newest first)",
67
80
  "order.asc": "Asc (oldest first)",
68
81
  "scroll.wheel": "Wheel Scroll",
69
- "scroll.click": "Click Scroll"
82
+ "scroll.click": "Click Scroll",
83
+ "ttl.label": "TTL (Time to Live)",
84
+ "ttl.3d": "3 days",
85
+ "ttl.7d": "7 days",
86
+ "ttl.14d": "14 days",
87
+ "ttl.30d": "30 days",
88
+ "ttl.unlimited": "Never",
89
+ "ttl.toast": "TTL set to {n} days",
90
+ "ttl.toast_unlimited": "TTL set to Never",
91
+ "clear.title": "Confirm",
92
+ "clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
93
+ "clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
94
+ "clear.done": "Cleared {n} sub-agent record(s)",
95
+ "clear.empty": "No sub-agent records in this session"
70
96
  }
71
97
  };
72
98
  function detectLang() {
@@ -191,10 +217,13 @@ function safeErrorMsg(err) {
191
217
  return "";
192
218
  }
193
219
  var globalEntryCache = /* @__PURE__ */ new Map();
220
+ var [clearTick, setClearTick] = createSignal(0);
194
221
  function SubAgentPanel(props) {
195
222
  const t = (key) => I18N[props.lang()][key] ?? key;
196
223
  const SESSION_DATA_KEY = `${KV_PREFIX}.session_data`;
197
- const TTL_MS = 3 * 24 * 60 * 60 * 1e3;
224
+ const ttlDaysRaw = parseInt(String(props.api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10);
225
+ const ttlDays = Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw;
226
+ const TTL_MS = ttlDays * 24 * 60 * 60 * 1e3;
198
227
  const loadSessionData = () => {
199
228
  try {
200
229
  const raw = props.api.kv.get(SESSION_DATA_KEY, "{}");
@@ -209,12 +238,34 @@ function SubAgentPanel(props) {
209
238
  } catch {
210
239
  }
211
240
  };
241
+ const resolveParent = (sid) => {
242
+ try {
243
+ const session = props.api.state.session.get(sid);
244
+ const parentID = session?.parentID;
245
+ if (parentID) return {
246
+ parentSid: parentID,
247
+ isChild: true
248
+ };
249
+ } catch {
250
+ }
251
+ return {
252
+ parentSid: sid,
253
+ isChild: false
254
+ };
255
+ };
212
256
  const loadEntries = (sid) => {
213
257
  const m = /* @__PURE__ */ new Map();
214
258
  try {
215
- const rec = loadSessionData()[sid];
216
- if (rec?.entries) {
217
- for (const e of rec.entries) m.set(e.id, e);
259
+ const {
260
+ parentSid,
261
+ isChild
262
+ } = resolveParent(sid);
263
+ const rec = loadSessionData()[parentSid];
264
+ if (rec) {
265
+ const source = isChild ? rec.children?.[sid]?.entries : rec.entries;
266
+ if (source) {
267
+ for (const e of source) m.set(e.id, e);
268
+ }
218
269
  }
219
270
  } catch {
220
271
  }
@@ -226,11 +277,36 @@ function SubAgentPanel(props) {
226
277
  persistTimer = setTimeout(() => {
227
278
  try {
228
279
  const data = loadSessionData();
229
- data[sid] = {
230
- ...data[sid],
231
- ts: Date.now(),
232
- entries: [...entries2.values()]
233
- };
280
+ const {
281
+ parentSid,
282
+ isChild
283
+ } = resolveParent(sid);
284
+ if (isChild) {
285
+ if (!data[parentSid]) data[parentSid] = {
286
+ ts: Date.now(),
287
+ entries: [],
288
+ scroll: 0,
289
+ expanded: "",
290
+ children: {}
291
+ };
292
+ if (!data[parentSid].children) data[parentSid].children = {};
293
+ if (!data[parentSid].children[sid]) data[parentSid].children[sid] = {
294
+ scroll: 0,
295
+ expanded: "",
296
+ entries: []
297
+ };
298
+ data[parentSid].children[sid] = {
299
+ ...data[parentSid].children[sid],
300
+ entries: [...entries2.values()]
301
+ };
302
+ } else {
303
+ data[sid] = {
304
+ ...data[sid],
305
+ ts: Date.now(),
306
+ entries: [...entries2.values()],
307
+ children: data[sid]?.children ?? {}
308
+ };
309
+ }
234
310
  saveSessionData(data);
235
311
  } catch {
236
312
  }
@@ -239,11 +315,36 @@ function SubAgentPanel(props) {
239
315
  const persistScroll = (sid, scroll) => {
240
316
  try {
241
317
  const data = loadSessionData();
242
- data[sid] = {
243
- ...data[sid],
244
- ts: Date.now(),
245
- scroll
246
- };
318
+ const {
319
+ parentSid,
320
+ isChild
321
+ } = resolveParent(sid);
322
+ if (isChild) {
323
+ if (!data[parentSid]) data[parentSid] = {
324
+ ts: Date.now(),
325
+ entries: [],
326
+ scroll: 0,
327
+ expanded: "",
328
+ children: {}
329
+ };
330
+ if (!data[parentSid].children) data[parentSid].children = {};
331
+ if (!data[parentSid].children[sid]) data[parentSid].children[sid] = {
332
+ scroll: 0,
333
+ expanded: "",
334
+ entries: []
335
+ };
336
+ data[parentSid].children[sid] = {
337
+ ...data[parentSid].children[sid],
338
+ scroll
339
+ };
340
+ } else {
341
+ data[sid] = {
342
+ ...data[sid],
343
+ ts: Date.now(),
344
+ scroll,
345
+ children: data[sid]?.children ?? {}
346
+ };
347
+ }
247
348
  saveSessionData(data);
248
349
  } catch {
249
350
  }
@@ -251,16 +352,42 @@ function SubAgentPanel(props) {
251
352
  const persistExpanded = (sid, expanded2) => {
252
353
  try {
253
354
  const data = loadSessionData();
254
- data[sid] = {
255
- ...data[sid],
256
- ts: Date.now(),
257
- expanded: expanded2
258
- };
355
+ const {
356
+ parentSid,
357
+ isChild
358
+ } = resolveParent(sid);
359
+ if (isChild) {
360
+ if (!data[parentSid]) data[parentSid] = {
361
+ ts: Date.now(),
362
+ entries: [],
363
+ scroll: 0,
364
+ expanded: "",
365
+ children: {}
366
+ };
367
+ if (!data[parentSid].children) data[parentSid].children = {};
368
+ if (!data[parentSid].children[sid]) data[parentSid].children[sid] = {
369
+ scroll: 0,
370
+ expanded: "",
371
+ entries: []
372
+ };
373
+ data[parentSid].children[sid] = {
374
+ ...data[parentSid].children[sid],
375
+ expanded: expanded2
376
+ };
377
+ } else {
378
+ data[sid] = {
379
+ ...data[sid],
380
+ ts: Date.now(),
381
+ expanded: expanded2,
382
+ children: data[sid]?.children ?? {}
383
+ };
384
+ }
259
385
  saveSessionData(data);
260
386
  } catch {
261
387
  }
262
388
  };
263
389
  const cleanupOldSessions = () => {
390
+ if (ttlDays <= 0) return;
264
391
  try {
265
392
  const data = loadSessionData();
266
393
  const cutoff = Date.now() - TTL_MS;
@@ -292,11 +419,36 @@ function SubAgentPanel(props) {
292
419
  clearTimeout(persistTimer);
293
420
  try {
294
421
  const data = loadSessionData();
295
- data[props.sessionId] = {
296
- ...data[props.sessionId],
297
- ts: Date.now(),
298
- entries: [...next.values()]
299
- };
422
+ const {
423
+ parentSid,
424
+ isChild
425
+ } = resolveParent(props.sessionId);
426
+ if (isChild) {
427
+ if (!data[parentSid]) data[parentSid] = {
428
+ ts: Date.now(),
429
+ entries: [],
430
+ scroll: 0,
431
+ expanded: "",
432
+ children: {}
433
+ };
434
+ if (!data[parentSid].children) data[parentSid].children = {};
435
+ if (!data[parentSid].children[props.sessionId]) data[parentSid].children[props.sessionId] = {
436
+ scroll: 0,
437
+ expanded: "",
438
+ entries: []
439
+ };
440
+ data[parentSid].children[props.sessionId] = {
441
+ ...data[parentSid].children[props.sessionId],
442
+ entries: [...next.values()]
443
+ };
444
+ } else {
445
+ data[props.sessionId] = {
446
+ ...data[props.sessionId],
447
+ ts: Date.now(),
448
+ entries: [...next.values()],
449
+ children: data[props.sessionId]?.children ?? {}
450
+ };
451
+ }
300
452
  saveSessionData(data);
301
453
  } catch {
302
454
  }
@@ -317,10 +469,15 @@ function SubAgentPanel(props) {
317
469
  })());
318
470
  const [expanded, setExpanded] = createSignal((() => {
319
471
  try {
320
- return loadSessionData()[props.sessionId]?.expanded || void 0;
472
+ const {
473
+ parentSid,
474
+ isChild
475
+ } = resolveParent(props.sessionId);
476
+ const rec = loadSessionData()[parentSid];
477
+ if (rec) return isChild ? rec.children?.[props.sessionId]?.expanded || void 0 : rec.expanded || void 0;
321
478
  } catch {
322
- return void 0;
323
479
  }
480
+ return void 0;
324
481
  })());
325
482
  const [hoveredOpen, setHoveredOpen] = createSignal(void 0);
326
483
  const [hoveredDismiss, setHoveredDismiss] = createSignal(void 0);
@@ -329,7 +486,12 @@ function SubAgentPanel(props) {
329
486
  const [hoveredMoreBelow, setHoveredMoreBelow] = createSignal(false);
330
487
  const [scrollOffset, setScrollOffset] = createSignal((() => {
331
488
  try {
332
- return loadSessionData()[props.sessionId]?.scroll ?? 0;
489
+ const {
490
+ parentSid,
491
+ isChild
492
+ } = resolveParent(props.sessionId);
493
+ const rec = loadSessionData()[parentSid];
494
+ return isChild ? rec?.children?.[props.sessionId]?.scroll ?? 0 : rec?.scroll ?? 0;
333
495
  } catch {
334
496
  return 0;
335
497
  }
@@ -793,18 +955,37 @@ function SubAgentPanel(props) {
793
955
  });
794
956
  });
795
957
  let lastSid = props.sessionId;
958
+ let lastTick = 0;
796
959
  createEffect(() => {
797
960
  const sid = props.sessionId;
798
961
  const switched = sid !== lastSid;
799
962
  lastSid = sid;
963
+ const tick = clearTick();
964
+ const forceReload = tick !== lastTick && !switched;
965
+ lastTick = tick;
800
966
  const t2 = setTimeout(() => {
801
967
  untrack(() => {
802
968
  if (switched) {
803
- const saved = loadSessionData()[sid]?.scroll ?? 0;
969
+ const {
970
+ parentSid,
971
+ isChild
972
+ } = resolveParent(sid);
973
+ const data = loadSessionData();
974
+ const saved = isChild ? data[parentSid]?.children?.[sid]?.scroll ?? 0 : data[sid]?.scroll ?? 0;
804
975
  setScrollOffset(saved);
976
+ if (!isChild && data[sid]?.entries?.length) {
977
+ data[sid].ts = Date.now();
978
+ saveSessionData(data);
979
+ }
805
980
  }
806
981
  setEntryMapRaw((prev) => {
807
- const next = switched ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev);
982
+ const next = switched || forceReload ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev);
983
+ const {
984
+ parentSid: scanPSid,
985
+ isChild: scanChild
986
+ } = resolveParent(sid);
987
+ const scanRec = loadSessionData()[scanPSid];
988
+ const clearedIds = new Set(scanChild ? scanRec?.children?.[sid]?.clearedIds : scanRec?.clearedIds);
808
989
  try {
809
990
  const msgs = props.api.state.session.messages(sid);
810
991
  if (msgs && msgs.length) {
@@ -820,6 +1001,7 @@ function SubAgentPanel(props) {
820
1001
  const st = part.state;
821
1002
  const rawStatus = String(st?.status ?? "");
822
1003
  const exists = next.get(id);
1004
+ if (!exists && clearedIds.has(id)) continue;
823
1005
  if ((rawStatus === "pending" || rawStatus === "") && !exists) continue;
824
1006
  if (rawStatus === "error") {
825
1007
  if (exists && exists.status === "running") {
@@ -2039,7 +2221,8 @@ var tui = async (api) => {
2039
2221
  name: "subagent-clear-running"
2040
2222
  },
2041
2223
  onSelect: (dialog) => {
2042
- const entries = globalEntryCache.get(signals.sessionId);
2224
+ const sid = signals.sessionId;
2225
+ const entries = globalEntryCache.get(sid);
2043
2226
  if (!entries || entries.size === 0) {
2044
2227
  const msg = signals.lang() === "zh" ? "\u6682\u65E0\u5B50\u4EE3\u7406\u6761\u76EE" : "No sub-agent entries found";
2045
2228
  api.ui.toast({
@@ -2059,12 +2242,35 @@ var tui = async (api) => {
2059
2242
  if (count > 0) {
2060
2243
  try {
2061
2244
  const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")));
2062
- data[signals.sessionId] = {
2063
- ts: Date.now(),
2064
- entries: [...entries.values()],
2065
- scroll: data[signals.sessionId]?.scroll ?? 0,
2066
- expanded: data[signals.sessionId]?.expanded ?? ""
2067
- };
2245
+ const sessionObj = api.state.session.get(sid);
2246
+ const parentID = sessionObj?.parentID;
2247
+ if (parentID) {
2248
+ if (!data[parentID]) data[parentID] = {
2249
+ ts: Date.now(),
2250
+ entries: [],
2251
+ scroll: 0,
2252
+ expanded: "",
2253
+ children: {}
2254
+ };
2255
+ if (!data[parentID].children) data[parentID].children = {};
2256
+ if (!data[parentID].children[sid]) data[parentID].children[sid] = {
2257
+ scroll: 0,
2258
+ expanded: "",
2259
+ entries: []
2260
+ };
2261
+ data[parentID].children[sid] = {
2262
+ ...data[parentID].children[sid],
2263
+ entries: [...entries.values()]
2264
+ };
2265
+ } else {
2266
+ data[sid] = {
2267
+ ts: Date.now(),
2268
+ entries: [...entries.values()],
2269
+ scroll: data[sid]?.scroll ?? 0,
2270
+ expanded: data[sid]?.expanded ?? "",
2271
+ children: data[sid]?.children ?? {}
2272
+ };
2273
+ }
2068
2274
  api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data));
2069
2275
  } catch {
2070
2276
  }
@@ -2080,6 +2286,113 @@ var tui = async (api) => {
2080
2286
  }
2081
2287
  dialog?.clear();
2082
2288
  }
2289
+ }, {
2290
+ title: "SubAgent Magazine: TTL",
2291
+ value: "subagent-ttl",
2292
+ description: "Set session data retention period (days before auto-cleanup)",
2293
+ slash: {
2294
+ name: "subagent-ttl"
2295
+ },
2296
+ onSelect: (dialog) => {
2297
+ const t = (k) => I18N[signals.lang()][k] ?? k;
2298
+ const curRaw = parseInt(String(api.kv.get(`${KV_PREFIX}.ttl_days`, "3")), 10);
2299
+ const curDays = Number.isNaN(curRaw) ? 3 : curRaw;
2300
+ const curLabel = curDays === 0 ? t("ttl.unlimited") : `${curDays}d`;
2301
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
2302
+ get title() {
2303
+ return `${t("ttl.label")} (${curLabel})`;
2304
+ },
2305
+ get options() {
2306
+ return [{
2307
+ title: t("ttl.3d"),
2308
+ value: "3"
2309
+ }, {
2310
+ title: t("ttl.7d"),
2311
+ value: "7"
2312
+ }, {
2313
+ title: t("ttl.14d"),
2314
+ value: "14"
2315
+ }, {
2316
+ title: t("ttl.30d"),
2317
+ value: "30"
2318
+ }, {
2319
+ title: t("ttl.unlimited"),
2320
+ value: "0"
2321
+ }];
2322
+ },
2323
+ onSelect: (opt) => {
2324
+ const days = parseInt(opt.value, 10);
2325
+ api.kv.set(`${KV_PREFIX}.ttl_days`, String(days));
2326
+ const msg = days === 0 ? t("ttl.toast_unlimited") : t("ttl.toast").replace("{n}", String(days));
2327
+ api.ui.toast({
2328
+ message: msg
2329
+ });
2330
+ dialog?.clear();
2331
+ }
2332
+ }));
2333
+ }
2334
+ }, {
2335
+ title: "SubAgent Magazine: Clear Entries",
2336
+ value: "subagent-clear-entries",
2337
+ description: "Delete all sub-agent records for the current session (cannot be undone)",
2338
+ slash: {
2339
+ name: "subagent-clear-entries"
2340
+ },
2341
+ onSelect: (dialog) => {
2342
+ const t = (k) => I18N[signals.lang()][k] ?? k;
2343
+ const sid = signals.sessionId;
2344
+ const sessionObj = api.state.session.get(sid);
2345
+ const parentID = sessionObj?.parentID;
2346
+ const cached = globalEntryCache.get(sid);
2347
+ let runningCount = 0;
2348
+ if (cached) {
2349
+ for (const [, e] of cached) {
2350
+ if (e.status === "running") runningCount++;
2351
+ }
2352
+ }
2353
+ const msg = runningCount > 0 ? t("clear.prompt_running").replace("{n}", String(runningCount)) : t("clear.prompt");
2354
+ dialog?.replace(() => _$createComponent(api.ui.DialogConfirm, {
2355
+ get title() {
2356
+ return t("clear.title");
2357
+ },
2358
+ message: msg,
2359
+ onConfirm: () => {
2360
+ try {
2361
+ const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")));
2362
+ let count = 0;
2363
+ if (parentID) {
2364
+ if (data[parentID]?.children?.[sid]) {
2365
+ const child = data[parentID].children[sid];
2366
+ const ids = child.entries?.map((e) => e.id) ?? [];
2367
+ count = ids.length;
2368
+ child.entries = [];
2369
+ child.scroll = 0;
2370
+ child.expanded = "";
2371
+ child.clearedIds = [.../* @__PURE__ */ new Set([...child.clearedIds ?? [], ...ids])];
2372
+ }
2373
+ } else {
2374
+ count = data[sid]?.entries?.length ?? 0;
2375
+ if (data[sid]) {
2376
+ const ids = data[sid].entries?.map((e) => e.id) ?? [];
2377
+ data[sid].entries = [];
2378
+ data[sid].scroll = 0;
2379
+ data[sid].expanded = "";
2380
+ data[sid].clearedIds = [.../* @__PURE__ */ new Set([...data[sid].clearedIds ?? [], ...ids])];
2381
+ }
2382
+ }
2383
+ api.kv.set(`${KV_PREFIX}.session_data`, JSON.stringify(data));
2384
+ globalEntryCache.delete(sid);
2385
+ setClearTick((v) => v + 1);
2386
+ const msg2 = t("clear.done").replace("{n}", String(count));
2387
+ api.ui.toast({
2388
+ message: msg2
2389
+ });
2390
+ } catch {
2391
+ }
2392
+ dialog?.clear();
2393
+ }
2394
+ }));
2395
+ }
2083
2396
  }]);
2084
2397
  };
2085
2398
  var mod = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-subagent-magazine",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "OpenCode TUI plugin monitoring sub-agent invocation status in real time",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
package/src/_version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION="1.3.0";
2
+ export const PLUGIN_VERSION="1.4.0";