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

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 +1 @@
1
- export declare const PLUGIN_VERSION = "1.6.0-beta.2";
1
+ export declare const PLUGIN_VERSION = "1.6.0-beta.3";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.6.0-beta.2";
2
+ export const PLUGIN_VERSION = "1.6.0-beta.3";
@@ -9,6 +9,7 @@ import { rgb, desaturateTo, dimColor, FALLBACK, MAX_SAT } from "../core/color";
9
9
  import { KV_PREFIX } from "../core/kv";
10
10
  import { globalEntryCache, clearTick } from "./store";
11
11
  import { isDirectChildSession } from "./session-routing";
12
+ import { findSubEntryKey, upsertSubEntry } from "./entry-map";
12
13
  /** Entry line left prefix: icon + space + status dot + space */
13
14
  const LEFT_PAD = 4;
14
15
  /** Detail row indent: two spaces */
@@ -51,15 +52,16 @@ export function SubAgentPanel(props) {
51
52
  return { parentSid: sid, isChild: false };
52
53
  };
53
54
  const loadEntries = (sid) => {
54
- const m = new Map();
55
+ let m = new Map();
55
56
  try {
56
57
  const { parentSid, isChild } = resolveParent(sid);
57
58
  const rec = loadSessionData()[parentSid];
58
59
  if (rec) {
59
60
  const source = isChild ? rec.children?.[sid]?.entries : rec.entries;
60
61
  if (source) {
61
- for (const e of source)
62
- m.set(e.id, e);
62
+ for (const e of source) {
63
+ m = upsertSubEntry(m, e);
64
+ }
63
65
  }
64
66
  }
65
67
  }
@@ -232,20 +234,7 @@ export function SubAgentPanel(props) {
232
234
  let disposed = false;
233
235
  // ── upsert ──
234
236
  const upsertEntry = (partial) => {
235
- setEntryMap((prev) => {
236
- const existing = prev.get(partial.id);
237
- const next = new Map(prev);
238
- const nowTs = Date.now();
239
- const e = partial.status;
240
- const ended = e === "done" || e === "error" || e === "cancelled";
241
- next.set(partial.id, {
242
- ...(existing ?? { startedAt: nowTs }),
243
- ...partial,
244
- startedAt: existing?.startedAt || partial.startedAt || nowTs,
245
- endedAt: ended ? (existing?.endedAt || nowTs) : undefined,
246
- });
247
- return next;
248
- });
237
+ setEntryMap((prev) => upsertSubEntry(prev, partial));
249
238
  };
250
239
  // ── cancel helpers ──
251
240
  const isDescendantOf = (childId, rootId) => {
@@ -370,9 +359,21 @@ export function SubAgentPanel(props) {
370
359
  const id = `tool:${String(part.id ?? "")}`;
371
360
  if (!part.id)
372
361
  return;
373
- const existing = entryMap().get(id);
362
+ const stMeta = st?.metadata;
363
+ const errorSid = stMeta?.session_id !== undefined ? String(stMeta.session_id)
364
+ : stMeta?.sessionId !== undefined ? String(stMeta.sessionId)
365
+ : undefined;
366
+ const key = findSubEntryKey(entryMap(), id, errorSid);
367
+ const existing = key ? entryMap().get(key) : undefined;
374
368
  if (existing) {
375
- upsertEntry({ id, title: existing.title, agent: existing.agent, prompt: existing.prompt, status: "error" });
369
+ upsertEntry({
370
+ id: existing.id,
371
+ title: existing.title,
372
+ agent: existing.agent,
373
+ prompt: existing.prompt,
374
+ sessionId: existing.sessionId ?? errorSid,
375
+ status: "error",
376
+ });
376
377
  }
377
378
  return;
378
379
  }
@@ -778,7 +779,12 @@ export function SubAgentPanel(props) {
778
779
  continue;
779
780
  const st = part.state;
780
781
  const rawStatus = String(st?.status ?? "");
781
- const exists = next.get(id);
782
+ const scanStMeta = st?.metadata;
783
+ const scanSubSid = scanStMeta?.session_id !== undefined ? String(scanStMeta.session_id)
784
+ : scanStMeta?.sessionId !== undefined ? String(scanStMeta.sessionId)
785
+ : undefined;
786
+ const existingKey = findSubEntryKey(next, id, scanSubSid);
787
+ const exists = existingKey ? next.get(existingKey) : undefined;
782
788
  // 已手动清除的条目:scan 发现但不在内存 → 跳过重建
783
789
  if (!exists && clearedIds.has(id))
784
790
  continue;
@@ -789,7 +795,7 @@ export function SubAgentPanel(props) {
789
795
  // "error": only update existing, never create a new entry
790
796
  if (rawStatus === "error") {
791
797
  if (exists && exists.status === "running") {
792
- next.set(id, { ...exists, status: "error", endedAt: Date.now() });
798
+ next.set(existingKey ?? id, { ...exists, status: "error", endedAt: Date.now() });
793
799
  }
794
800
  continue;
795
801
  }
@@ -805,7 +811,7 @@ export function SubAgentPanel(props) {
805
811
  status = "running";
806
812
  }
807
813
  // Already settled → skip
808
- if (exists && exists.status !== "running" && exists.status !== "cancel_requested")
814
+ if (exists && existingKey === id && exists.status !== "running" && exists.status !== "cancel_requested")
809
815
  continue;
810
816
  // Running entry with no explicit status improvement from part:
811
817
  // try message-level heuristics first, then time-based fallback.
@@ -834,15 +840,12 @@ export function SubAgentPanel(props) {
834
840
  const desc = input?.description !== undefined ? String(input.description) : "";
835
841
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
836
842
  let tokens;
837
- const scanStMeta2 = st?.metadata;
838
- const scanSubSid = scanStMeta2?.session_id !== undefined ? String(scanStMeta2.session_id)
839
- : scanStMeta2?.sessionId !== undefined ? String(scanStMeta2.sessionId)
840
- : undefined;
841
843
  if (scanSubSid)
842
844
  tokens = props.api.usage.readSessionTokens(scanSubSid);
843
845
  const ended = status === "done"; // "error" handled above, never reaches here
844
- next.set(id, {
845
- id, title, agent, prompt,
846
+ const entryKey = existingKey ?? id;
847
+ next.set(entryKey, {
848
+ id: exists?.id ?? id, title, agent, prompt,
846
849
  // Preserve existing values (from handleSessionEnd / KV) — scan must not overwrite
847
850
  tokens: exists?.tokens ?? tokens,
848
851
  sessionId: exists?.sessionId ?? scanSubSid,
@@ -850,6 +853,8 @@ export function SubAgentPanel(props) {
850
853
  startedAt: exists?.startedAt || Date.now(),
851
854
  endedAt: ended ? (exists?.endedAt || Date.now()) : undefined,
852
855
  });
856
+ if (entryKey !== id)
857
+ next.delete(id);
853
858
  }
854
859
  }
855
860
  }
@@ -0,0 +1,6 @@
1
+ import type { SubEntry } from "../core/types";
2
+ export type SubEntryPatch = Omit<SubEntry, "startedAt" | "endedAt"> & {
3
+ startedAt?: number;
4
+ };
5
+ export declare function findSubEntryKey(entries: ReadonlyMap<string, SubEntry>, id: string, sessionId?: string): string | undefined;
6
+ export declare function upsertSubEntry(entries: ReadonlyMap<string, SubEntry>, partial: SubEntryPatch, now?: number): Map<string, SubEntry>;
@@ -0,0 +1,25 @@
1
+ export function findSubEntryKey(entries, id, sessionId) {
2
+ if (sessionId) {
3
+ for (const [key, entry] of entries) {
4
+ if (entry.sessionId === sessionId)
5
+ return key;
6
+ }
7
+ }
8
+ return entries.has(id) ? id : undefined;
9
+ }
10
+ export function upsertSubEntry(entries, partial, now = Date.now()) {
11
+ const key = findSubEntryKey(entries, partial.id, partial.sessionId) ?? partial.id;
12
+ const existing = entries.get(key);
13
+ const next = new Map(entries);
14
+ if (key !== partial.id)
15
+ next.delete(partial.id);
16
+ const ended = partial.status === "done" || partial.status === "error" || partial.status === "cancelled";
17
+ next.set(key, {
18
+ ...(existing ?? { startedAt: now }),
19
+ ...partial,
20
+ id: existing?.id ?? partial.id,
21
+ startedAt: existing?.startedAt || partial.startedAt || now,
22
+ endedAt: ended ? (existing?.endedAt || now) : undefined,
23
+ });
24
+ return next;
25
+ }
package/dist/tui.js CHANGED
@@ -6,7 +6,7 @@ import { createComponent as _$createComponent2 } from "@opentui/solid";
6
6
  import { createSignal as createSignal3 } from "solid-js";
7
7
 
8
8
  // src/_version.ts
9
- var PLUGIN_VERSION = "1.6.0-beta.2";
9
+ var PLUGIN_VERSION = "1.6.0-beta.3";
10
10
 
11
11
  // src/i18n.ts
12
12
  var ZH_T = {
@@ -490,6 +490,31 @@ function isDirectChildSession(currentSessionId, eventSessionId, eventSession) {
490
490
  return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId;
491
491
  }
492
492
 
493
+ // src/panel/entry-map.ts
494
+ function findSubEntryKey(entries, id, sessionId) {
495
+ if (sessionId) {
496
+ for (const [key, entry] of entries) {
497
+ if (entry.sessionId === sessionId) return key;
498
+ }
499
+ }
500
+ return entries.has(id) ? id : void 0;
501
+ }
502
+ function upsertSubEntry(entries, partial, now = Date.now()) {
503
+ const key = findSubEntryKey(entries, partial.id, partial.sessionId) ?? partial.id;
504
+ const existing = entries.get(key);
505
+ const next = new Map(entries);
506
+ if (key !== partial.id) next.delete(partial.id);
507
+ const ended = partial.status === "done" || partial.status === "error" || partial.status === "cancelled";
508
+ next.set(key, {
509
+ ...existing ?? { startedAt: now },
510
+ ...partial,
511
+ id: existing?.id ?? partial.id,
512
+ startedAt: existing?.startedAt || partial.startedAt || now,
513
+ endedAt: ended ? existing?.endedAt || now : void 0
514
+ });
515
+ return next;
516
+ }
517
+
493
518
  // src/panel/SubAgentPanel.tsx
494
519
  var LEFT_PAD = 4;
495
520
  var INDENT = 2;
@@ -529,7 +554,7 @@ function SubAgentPanel(props) {
529
554
  };
530
555
  };
531
556
  const loadEntries = (sid) => {
532
- const m = /* @__PURE__ */ new Map();
557
+ let m = /* @__PURE__ */ new Map();
533
558
  try {
534
559
  const {
535
560
  parentSid,
@@ -539,7 +564,9 @@ function SubAgentPanel(props) {
539
564
  if (rec) {
540
565
  const source = isChild ? rec.children?.[sid]?.entries : rec.entries;
541
566
  if (source) {
542
- for (const e of source) m.set(e.id, e);
567
+ for (const e of source) {
568
+ m = upsertSubEntry(m, e);
569
+ }
543
570
  }
544
571
  }
545
572
  } catch {
@@ -777,22 +804,7 @@ function SubAgentPanel(props) {
777
804
  let boxEl;
778
805
  let disposed = false;
779
806
  const upsertEntry = (partial) => {
780
- setEntryMap((prev) => {
781
- const existing = prev.get(partial.id);
782
- const next = new Map(prev);
783
- const nowTs = Date.now();
784
- const e = partial.status;
785
- const ended = e === "done" || e === "error" || e === "cancelled";
786
- next.set(partial.id, {
787
- ...existing ?? {
788
- startedAt: nowTs
789
- },
790
- ...partial,
791
- startedAt: existing?.startedAt || partial.startedAt || nowTs,
792
- endedAt: ended ? existing?.endedAt || nowTs : void 0
793
- });
794
- return next;
795
- });
807
+ setEntryMap((prev) => upsertSubEntry(prev, partial));
796
808
  };
797
809
  const isDescendantOf = (childId, rootId) => {
798
810
  const visited = /* @__PURE__ */ new Set();
@@ -936,13 +948,17 @@ function SubAgentPanel(props) {
936
948
  if (rawStatus === "error") {
937
949
  const id2 = `tool:${String(part.id ?? "")}`;
938
950
  if (!part.id) return;
939
- const existing = entryMap().get(id2);
951
+ const stMeta2 = st?.metadata;
952
+ const errorSid = stMeta2?.session_id !== void 0 ? String(stMeta2.session_id) : stMeta2?.sessionId !== void 0 ? String(stMeta2.sessionId) : void 0;
953
+ const key = findSubEntryKey(entryMap(), id2, errorSid);
954
+ const existing = key ? entryMap().get(key) : void 0;
940
955
  if (existing) {
941
956
  upsertEntry({
942
- id: id2,
957
+ id: existing.id,
943
958
  title: existing.title,
944
959
  agent: existing.agent,
945
960
  prompt: existing.prompt,
961
+ sessionId: existing.sessionId ?? errorSid,
946
962
  status: "error"
947
963
  });
948
964
  }
@@ -1329,12 +1345,15 @@ function SubAgentPanel(props) {
1329
1345
  if (!part.id) continue;
1330
1346
  const st = part.state;
1331
1347
  const rawStatus = String(st?.status ?? "");
1332
- const exists = next.get(id);
1348
+ const scanStMeta = st?.metadata;
1349
+ const scanSubSid = scanStMeta?.session_id !== void 0 ? String(scanStMeta.session_id) : scanStMeta?.sessionId !== void 0 ? String(scanStMeta.sessionId) : void 0;
1350
+ const existingKey = findSubEntryKey(next, id, scanSubSid);
1351
+ const exists = existingKey ? next.get(existingKey) : void 0;
1333
1352
  if (!exists && clearedIds.has(id)) continue;
1334
1353
  if ((rawStatus === "pending" || rawStatus === "") && !exists) continue;
1335
1354
  if (rawStatus === "error") {
1336
1355
  if (exists && exists.status === "running") {
1337
- next.set(id, {
1356
+ next.set(existingKey ?? id, {
1338
1357
  ...exists,
1339
1358
  status: "error",
1340
1359
  endedAt: Date.now()
@@ -1345,11 +1364,11 @@ function SubAgentPanel(props) {
1345
1364
  let status = "running";
1346
1365
  if (rawStatus === "completed") status = "done";
1347
1366
  if ((st?.input?.run_in_background === true || st?.input?.background === true) && status === "done") {
1348
- const scanStMeta = st?.metadata;
1349
- const scanHasChild = scanStMeta?.session_id !== void 0 || scanStMeta?.sessionId !== void 0;
1367
+ const scanStMeta2 = st?.metadata;
1368
+ const scanHasChild = scanStMeta2?.session_id !== void 0 || scanStMeta2?.sessionId !== void 0;
1350
1369
  if (scanHasChild) status = "running";
1351
1370
  }
1352
- if (exists && exists.status !== "running" && exists.status !== "cancel_requested") continue;
1371
+ if (exists && existingKey === id && exists.status !== "running" && exists.status !== "cancel_requested") continue;
1353
1372
  if (exists && status === "running") {
1354
1373
  if (!rawStatus) {
1355
1374
  const msgTokens = msg?.tokens;
@@ -1370,12 +1389,11 @@ function SubAgentPanel(props) {
1370
1389
  const desc = input?.description !== void 0 ? String(input.description) : "";
1371
1390
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
1372
1391
  let tokens;
1373
- const scanStMeta2 = st?.metadata;
1374
- const scanSubSid = scanStMeta2?.session_id !== void 0 ? String(scanStMeta2.session_id) : scanStMeta2?.sessionId !== void 0 ? String(scanStMeta2.sessionId) : void 0;
1375
1392
  if (scanSubSid) tokens = props.api.usage.readSessionTokens(scanSubSid);
1376
1393
  const ended = status === "done";
1377
- next.set(id, {
1378
- id,
1394
+ const entryKey = existingKey ?? id;
1395
+ next.set(entryKey, {
1396
+ id: exists?.id ?? id,
1379
1397
  title,
1380
1398
  agent,
1381
1399
  prompt,
@@ -1386,6 +1404,7 @@ function SubAgentPanel(props) {
1386
1404
  startedAt: exists?.startedAt || Date.now(),
1387
1405
  endedAt: ended ? exists?.endedAt || Date.now() : void 0
1388
1406
  });
1407
+ if (entryKey !== id) next.delete(id);
1389
1408
  }
1390
1409
  }
1391
1410
  }
package/dist/v2.js CHANGED
@@ -329,7 +329,7 @@ function readTTLDays(kv) {
329
329
  }
330
330
 
331
331
  // src/_version.ts
332
- var PLUGIN_VERSION = "1.6.0-beta.2";
332
+ var PLUGIN_VERSION = "1.6.0-beta.3";
333
333
 
334
334
  // src/i18n.ts
335
335
  var ZH_T = {
@@ -1057,6 +1057,31 @@ function isDirectChildSession(currentSessionId, eventSessionId, eventSession) {
1057
1057
  return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId;
1058
1058
  }
1059
1059
 
1060
+ // src/panel/entry-map.ts
1061
+ function findSubEntryKey(entries, id, sessionId) {
1062
+ if (sessionId) {
1063
+ for (const [key, entry] of entries) {
1064
+ if (entry.sessionId === sessionId) return key;
1065
+ }
1066
+ }
1067
+ return entries.has(id) ? id : void 0;
1068
+ }
1069
+ function upsertSubEntry(entries, partial, now = Date.now()) {
1070
+ const key = findSubEntryKey(entries, partial.id, partial.sessionId) ?? partial.id;
1071
+ const existing = entries.get(key);
1072
+ const next = new Map(entries);
1073
+ if (key !== partial.id) next.delete(partial.id);
1074
+ const ended = partial.status === "done" || partial.status === "error" || partial.status === "cancelled";
1075
+ next.set(key, {
1076
+ ...existing ?? { startedAt: now },
1077
+ ...partial,
1078
+ id: existing?.id ?? partial.id,
1079
+ startedAt: existing?.startedAt || partial.startedAt || now,
1080
+ endedAt: ended ? existing?.endedAt || now : void 0
1081
+ });
1082
+ return next;
1083
+ }
1084
+
1060
1085
  // src/panel/SubAgentPanel.tsx
1061
1086
  var LEFT_PAD = 4;
1062
1087
  var INDENT = 2;
@@ -1096,7 +1121,7 @@ function SubAgentPanel(props) {
1096
1121
  };
1097
1122
  };
1098
1123
  const loadEntries = (sid) => {
1099
- const m = /* @__PURE__ */ new Map();
1124
+ let m = /* @__PURE__ */ new Map();
1100
1125
  try {
1101
1126
  const {
1102
1127
  parentSid,
@@ -1106,7 +1131,9 @@ function SubAgentPanel(props) {
1106
1131
  if (rec) {
1107
1132
  const source = isChild ? rec.children?.[sid]?.entries : rec.entries;
1108
1133
  if (source) {
1109
- for (const e of source) m.set(e.id, e);
1134
+ for (const e of source) {
1135
+ m = upsertSubEntry(m, e);
1136
+ }
1110
1137
  }
1111
1138
  }
1112
1139
  } catch {
@@ -1344,22 +1371,7 @@ function SubAgentPanel(props) {
1344
1371
  let boxEl;
1345
1372
  let disposed = false;
1346
1373
  const upsertEntry = (partial) => {
1347
- setEntryMap((prev) => {
1348
- const existing = prev.get(partial.id);
1349
- const next = new Map(prev);
1350
- const nowTs = Date.now();
1351
- const e = partial.status;
1352
- const ended = e === "done" || e === "error" || e === "cancelled";
1353
- next.set(partial.id, {
1354
- ...existing ?? {
1355
- startedAt: nowTs
1356
- },
1357
- ...partial,
1358
- startedAt: existing?.startedAt || partial.startedAt || nowTs,
1359
- endedAt: ended ? existing?.endedAt || nowTs : void 0
1360
- });
1361
- return next;
1362
- });
1374
+ setEntryMap((prev) => upsertSubEntry(prev, partial));
1363
1375
  };
1364
1376
  const isDescendantOf = (childId, rootId) => {
1365
1377
  const visited = /* @__PURE__ */ new Set();
@@ -1503,13 +1515,17 @@ function SubAgentPanel(props) {
1503
1515
  if (rawStatus === "error") {
1504
1516
  const id2 = `tool:${String(part.id ?? "")}`;
1505
1517
  if (!part.id) return;
1506
- const existing = entryMap().get(id2);
1518
+ const stMeta2 = st?.metadata;
1519
+ const errorSid = stMeta2?.session_id !== void 0 ? String(stMeta2.session_id) : stMeta2?.sessionId !== void 0 ? String(stMeta2.sessionId) : void 0;
1520
+ const key = findSubEntryKey(entryMap(), id2, errorSid);
1521
+ const existing = key ? entryMap().get(key) : void 0;
1507
1522
  if (existing) {
1508
1523
  upsertEntry({
1509
- id: id2,
1524
+ id: existing.id,
1510
1525
  title: existing.title,
1511
1526
  agent: existing.agent,
1512
1527
  prompt: existing.prompt,
1528
+ sessionId: existing.sessionId ?? errorSid,
1513
1529
  status: "error"
1514
1530
  });
1515
1531
  }
@@ -1896,12 +1912,15 @@ function SubAgentPanel(props) {
1896
1912
  if (!part.id) continue;
1897
1913
  const st = part.state;
1898
1914
  const rawStatus = String(st?.status ?? "");
1899
- const exists = next.get(id);
1915
+ const scanStMeta = st?.metadata;
1916
+ const scanSubSid = scanStMeta?.session_id !== void 0 ? String(scanStMeta.session_id) : scanStMeta?.sessionId !== void 0 ? String(scanStMeta.sessionId) : void 0;
1917
+ const existingKey = findSubEntryKey(next, id, scanSubSid);
1918
+ const exists = existingKey ? next.get(existingKey) : void 0;
1900
1919
  if (!exists && clearedIds.has(id)) continue;
1901
1920
  if ((rawStatus === "pending" || rawStatus === "") && !exists) continue;
1902
1921
  if (rawStatus === "error") {
1903
1922
  if (exists && exists.status === "running") {
1904
- next.set(id, {
1923
+ next.set(existingKey ?? id, {
1905
1924
  ...exists,
1906
1925
  status: "error",
1907
1926
  endedAt: Date.now()
@@ -1912,11 +1931,11 @@ function SubAgentPanel(props) {
1912
1931
  let status = "running";
1913
1932
  if (rawStatus === "completed") status = "done";
1914
1933
  if ((st?.input?.run_in_background === true || st?.input?.background === true) && status === "done") {
1915
- const scanStMeta = st?.metadata;
1916
- const scanHasChild = scanStMeta?.session_id !== void 0 || scanStMeta?.sessionId !== void 0;
1934
+ const scanStMeta2 = st?.metadata;
1935
+ const scanHasChild = scanStMeta2?.session_id !== void 0 || scanStMeta2?.sessionId !== void 0;
1917
1936
  if (scanHasChild) status = "running";
1918
1937
  }
1919
- if (exists && exists.status !== "running" && exists.status !== "cancel_requested") continue;
1938
+ if (exists && existingKey === id && exists.status !== "running" && exists.status !== "cancel_requested") continue;
1920
1939
  if (exists && status === "running") {
1921
1940
  if (!rawStatus) {
1922
1941
  const msgTokens = msg?.tokens;
@@ -1937,12 +1956,11 @@ function SubAgentPanel(props) {
1937
1956
  const desc = input?.description !== void 0 ? String(input.description) : "";
1938
1957
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
1939
1958
  let tokens;
1940
- const scanStMeta2 = st?.metadata;
1941
- const scanSubSid = scanStMeta2?.session_id !== void 0 ? String(scanStMeta2.session_id) : scanStMeta2?.sessionId !== void 0 ? String(scanStMeta2.sessionId) : void 0;
1942
1959
  if (scanSubSid) tokens = props.api.usage.readSessionTokens(scanSubSid);
1943
1960
  const ended = status === "done";
1944
- next.set(id, {
1945
- id,
1961
+ const entryKey = existingKey ?? id;
1962
+ next.set(entryKey, {
1963
+ id: exists?.id ?? id,
1946
1964
  title,
1947
1965
  agent,
1948
1966
  prompt,
@@ -1953,6 +1971,7 @@ function SubAgentPanel(props) {
1953
1971
  startedAt: exists?.startedAt || Date.now(),
1954
1972
  endedAt: ended ? exists?.endedAt || Date.now() : void 0
1955
1973
  });
1974
+ if (entryKey !== id) next.delete(id);
1956
1975
  }
1957
1976
  }
1958
1977
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-subagent-magazine",
3
- "version": "1.6.0-beta.2",
3
+ "version": "1.6.0-beta.3",
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.6.0-beta.2";
2
+ export const PLUGIN_VERSION="1.6.0-beta.3";
@@ -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
@@ -87,14 +88,16 @@ export function SubAgentPanel(props: {
87
88
  }
88
89
 
89
90
  const loadEntries = (sid: string): Map<string, SubEntry> => {
90
- const m = new Map<string, SubEntry>()
91
+ let m = new Map<string, SubEntry>()
91
92
  try {
92
93
  const { parentSid, isChild } = resolveParent(sid)
93
94
  const rec = loadSessionData()[parentSid]
94
95
  if (rec) {
95
96
  const source = isChild ? rec.children?.[sid]?.entries : rec.entries
96
97
  if (source) {
97
- for (const e of source) m.set(e.id, e)
98
+ for (const e of source) {
99
+ m = upsertSubEntry(m, e)
100
+ }
98
101
  }
99
102
  }
100
103
  } catch {}
@@ -256,20 +259,7 @@ export function SubAgentPanel(props: {
256
259
  const upsertEntry = (
257
260
  partial: Omit<SubEntry, "startedAt" | "endedAt"> & { startedAt?: number }
258
261
  ) => {
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
- })
262
+ setEntryMap((prev) => upsertSubEntry(prev, partial))
273
263
  }
274
264
 
275
265
  // ── cancel helpers ──
@@ -397,9 +387,21 @@ export function SubAgentPanel(props: {
397
387
  if (rawStatus === "error") {
398
388
  const id = `tool:${String(part.id ?? "")}`
399
389
  if (!part.id) return
400
- const existing = entryMap().get(id)
390
+ const stMeta = st?.metadata as Record<string, unknown> | undefined
391
+ const errorSid = stMeta?.session_id !== undefined ? String(stMeta.session_id)
392
+ : stMeta?.sessionId !== undefined ? String(stMeta.sessionId)
393
+ : undefined
394
+ const key = findSubEntryKey(entryMap(), id, errorSid)
395
+ const existing = key ? entryMap().get(key) : undefined
401
396
  if (existing) {
402
- upsertEntry({ id, title: existing.title, agent: existing.agent, prompt: existing.prompt, status: "error" })
397
+ upsertEntry({
398
+ id: existing.id,
399
+ title: existing.title,
400
+ agent: existing.agent,
401
+ prompt: existing.prompt,
402
+ sessionId: existing.sessionId ?? errorSid,
403
+ status: "error",
404
+ })
403
405
  }
404
406
  return
405
407
  }
@@ -791,7 +793,12 @@ export function SubAgentPanel(props: {
791
793
 
792
794
  const st = (part as any).state as Record<string, unknown> | undefined
793
795
  const rawStatus = String(st?.status ?? "")
794
- const exists = next.get(id)
796
+ const scanStMeta = st?.metadata as Record<string, unknown> | undefined
797
+ const scanSubSid = scanStMeta?.session_id !== undefined ? String(scanStMeta.session_id)
798
+ : scanStMeta?.sessionId !== undefined ? String(scanStMeta.sessionId)
799
+ : undefined
800
+ const existingKey = findSubEntryKey(next, id, scanSubSid)
801
+ const exists = existingKey ? next.get(existingKey) : undefined
795
802
 
796
803
  // 已手动清除的条目:scan 发现但不在内存 → 跳过重建
797
804
  if (!exists && clearedIds.has(id)) continue
@@ -803,7 +810,7 @@ export function SubAgentPanel(props: {
803
810
  // "error": only update existing, never create a new entry
804
811
  if (rawStatus === "error") {
805
812
  if (exists && exists.status === "running") {
806
- next.set(id, { ...exists, status: "error", endedAt: Date.now() })
813
+ next.set(existingKey ?? id, { ...exists, status: "error", endedAt: Date.now() })
807
814
  }
808
815
  continue
809
816
  }
@@ -819,7 +826,7 @@ export function SubAgentPanel(props: {
819
826
  }
820
827
 
821
828
  // Already settled → skip
822
- if (exists && exists.status !== "running" && exists.status !== "cancel_requested") continue
829
+ if (exists && existingKey === id && exists.status !== "running" && exists.status !== "cancel_requested") continue
823
830
  // Running entry with no explicit status improvement from part:
824
831
  // try message-level heuristics first, then time-based fallback.
825
832
  if (exists && status === "running") {
@@ -847,15 +854,12 @@ export function SubAgentPanel(props: {
847
854
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40)
848
855
 
849
856
  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
857
  if (scanSubSid) tokens = props.api.usage.readSessionTokens(scanSubSid)
855
858
 
856
859
  const ended = status === "done" // "error" handled above, never reaches here
857
- next.set(id, {
858
- id, title, agent, prompt,
860
+ const entryKey = existingKey ?? id
861
+ next.set(entryKey, {
862
+ id: exists?.id ?? id, title, agent, prompt,
859
863
  // Preserve existing values (from handleSessionEnd / KV) — scan must not overwrite
860
864
  tokens: exists?.tokens ?? tokens,
861
865
  sessionId: exists?.sessionId ?? scanSubSid,
@@ -863,6 +867,7 @@ export function SubAgentPanel(props: {
863
867
  startedAt: exists?.startedAt || Date.now(),
864
868
  endedAt: ended ? (exists?.endedAt || Date.now()) : undefined,
865
869
  })
870
+ if (entryKey !== id) next.delete(id)
866
871
  }
867
872
  }
868
873
  }
@@ -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
+ }