opencode-subagent-magazine 1.7.1 → 1.7.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.
Files changed (53) hide show
  1. package/dist/_version.d.ts +1 -1
  2. package/dist/panel/entry-map.d.ts +29 -0
  3. package/dist/tui.js +166 -19
  4. package/dist/v2.js +145 -22
  5. package/package.json +17 -4
  6. package/dist/_version.js +0 -2
  7. package/dist/clipboard.js +0 -85
  8. package/dist/core/color.js +0 -68
  9. package/dist/core/format.js +0 -113
  10. package/dist/core/index.js +0 -6
  11. package/dist/core/kv.js +0 -64
  12. package/dist/core/state-machine.js +0 -27
  13. package/dist/core/types.js +0 -9
  14. package/dist/core/usage.js +0 -1
  15. package/dist/i18n.js +0 -353
  16. package/dist/index.js +0 -528
  17. package/dist/panel/SubAgentPanel.js +0 -1644
  18. package/dist/panel/entry-map.js +0 -99
  19. package/dist/panel/panel-api.js +0 -1
  20. package/dist/panel/session-routing.js +0 -4
  21. package/dist/panel/store.js +0 -5
  22. package/dist/server.js +0 -14
  23. package/dist/v2/commands.js +0 -434
  24. package/dist/v2/db.js +0 -341
  25. package/dist/v2/index.js +0 -85
  26. package/dist/v2/settings-menu.js +0 -242
  27. package/dist/v2/theme.js +0 -12
  28. package/dist/v2/types.js +0 -6
  29. package/dist/v2/v2-panel-api.js +0 -623
  30. package/src/_version.ts +0 -2
  31. package/src/clipboard.ts +0 -134
  32. package/src/core/color.ts +0 -70
  33. package/src/core/format.ts +0 -105
  34. package/src/core/index.ts +0 -6
  35. package/src/core/kv.ts +0 -78
  36. package/src/core/state-machine.ts +0 -46
  37. package/src/core/types.ts +0 -85
  38. package/src/core/usage.ts +0 -16
  39. package/src/i18n.ts +0 -365
  40. package/src/index.tsx +0 -584
  41. package/src/panel/SubAgentPanel.tsx +0 -1877
  42. package/src/panel/entry-map.ts +0 -116
  43. package/src/panel/panel-api.ts +0 -100
  44. package/src/panel/session-routing.ts +0 -10
  45. package/src/panel/store.ts +0 -8
  46. package/src/server.ts +0 -19
  47. package/src/v2/commands.ts +0 -422
  48. package/src/v2/db.ts +0 -346
  49. package/src/v2/index.tsx +0 -149
  50. package/src/v2/settings-menu.ts +0 -259
  51. package/src/v2/theme.ts +0 -14
  52. package/src/v2/types.ts +0 -167
  53. package/src/v2/v2-panel-api.ts +0 -520
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.7.1";
1
+ export declare const PLUGIN_VERSION = "1.7.3";
@@ -21,3 +21,32 @@ export declare function mergeSubEntry(base: SubEntry, incoming: SubEntry): SubEn
21
21
  * 互相丢弃对方的条目。
22
22
  */
23
23
  export declare function mergeSubEntries(base: Iterable<SubEntry>, incoming: Iterable<SubEntry>): Map<string, SubEntry>;
24
+ /** 持久化保留的 prompt 前缀长度。prompt 只用于生成 title,不参与渲染;
25
+ * 写入前截断,避免单条 10KB+ 的提示词把 KV 撑大(实测存量 prompt 占 2.9MB/4.4MB)。 */
26
+ export declare const PERSISTED_PROMPT_MAX = 128;
27
+ /** 压缩条目载荷(当前仅截断 prompt)。已达标时原样返回,避免无谓复制。 */
28
+ export declare function compactSubEntry(entry: SubEntry): SubEntry;
29
+ /**
30
+ * 持久化合并——所有 KV 写入路径的唯一入口:
31
+ * 1. 剔除 id 已进入 `clearedIds` 的条目(手动清除名单是持久化层权威,
32
+ * 过期快照/debounce 写入都不得把它们合并回去);
33
+ * 2. 压缩载荷(prompt 截断),base 里的存量胖数据也顺带清理。
34
+ */
35
+ export declare function mergeSubEntriesForPersist(base: Iterable<SubEntry>, incoming: Iterable<SubEntry>, clearedIds: readonly string[] | undefined): Map<string, SubEntry>;
36
+ /**
37
+ * 从清除名单中筛出可安全移除的 id——它们不会再被扫描/轮询重建:
38
+ * - `tool:`:对应 part 已确认状态为 error(扫描从不为 error part 建条目);
39
+ * - `sub:`:子会话已不在权威存活列表里(轮询只重建存活子会话)。
40
+ * `liveChildIds` 未提供(本地库不可用)时不碰 `sub:` id。
41
+ */
42
+ export declare function prunableClearedIds(input: {
43
+ clearedIds: Iterable<string>;
44
+ errorToolIds?: ReadonlySet<string>;
45
+ liveChildIds?: ReadonlySet<string>;
46
+ }): string[];
47
+ /**
48
+ * 从条目集合中移除 id 已清除的条目(保留原有 Map 键)。
49
+ * 扫描入口调用:防止 `globalEntryCache` / KV 中残留的已清除
50
+ * 条目在 replace=false 的合并扫描中继续存活。
51
+ */
52
+ export declare function withoutClearedEntries(entries: ReadonlyMap<string, SubEntry>, clearedIds: ReadonlySet<string> | undefined): Map<string, SubEntry>;
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.7.1";
9
+ var PLUGIN_VERSION = "1.7.3";
10
10
 
11
11
  // src/i18n.ts
12
12
  var ZH_T = {
@@ -749,6 +749,39 @@ function mergeSubEntries(base, incoming) {
749
749
  }
750
750
  return out;
751
751
  }
752
+ var PERSISTED_PROMPT_MAX = 128;
753
+ function compactSubEntry(entry) {
754
+ const prompt = entry.prompt ?? "";
755
+ if (prompt.length <= PERSISTED_PROMPT_MAX) return entry;
756
+ return { ...entry, prompt: prompt.slice(0, PERSISTED_PROMPT_MAX) };
757
+ }
758
+ function mergeSubEntriesForPersist(base, incoming, clearedIds) {
759
+ const cleared = new Set(clearedIds ?? []);
760
+ const keep = (entry) => !cleared.has(entry.id);
761
+ return mergeSubEntries(
762
+ [...base].filter(keep).map(compactSubEntry),
763
+ [...incoming].filter(keep).map(compactSubEntry)
764
+ );
765
+ }
766
+ function prunableClearedIds(input) {
767
+ const out = [];
768
+ for (const id of input.clearedIds) {
769
+ if (id.startsWith("tool:")) {
770
+ if (input.errorToolIds?.has(id)) out.push(id);
771
+ } else if (id.startsWith("sub:")) {
772
+ if (input.liveChildIds !== void 0 && !input.liveChildIds.has(id.slice(4))) out.push(id);
773
+ }
774
+ }
775
+ return out;
776
+ }
777
+ function withoutClearedEntries(entries, clearedIds) {
778
+ const next = new Map(entries);
779
+ if (!clearedIds || clearedIds.size === 0) return next;
780
+ for (const [key, entry] of next) {
781
+ if (clearedIds.has(entry.id)) next.delete(key);
782
+ }
783
+ return next;
784
+ }
752
785
 
753
786
  // src/panel/SubAgentPanel.tsx
754
787
  var LEFT_PAD = 4;
@@ -853,12 +886,12 @@ function SubAgentPanel(props) {
853
886
  if (isChild) {
854
887
  const parent = ensureRootRecord(data, parentSid);
855
888
  const child = ensureChildRecord(data, parentSid, sid);
856
- child.entries = [...mergeSubEntries(child.entries ?? [], entries2.values()).values()];
889
+ child.entries = [...mergeSubEntriesForPersist(child.entries ?? [], entries2.values(), child.clearedIds).values()];
857
890
  parent.ts = Date.now();
858
891
  } else {
859
892
  const rec = ensureRootRecord(data, sid);
860
893
  rec.ts = Date.now();
861
- rec.entries = [...mergeSubEntries(rec.entries ?? [], entries2.values()).values()];
894
+ rec.entries = [...mergeSubEntriesForPersist(rec.entries ?? [], entries2.values(), rec.clearedIds).values()];
862
895
  }
863
896
  });
864
897
  } catch {
@@ -913,7 +946,62 @@ function SubAgentPanel(props) {
913
946
  } catch {
914
947
  }
915
948
  };
949
+ const recordNeedsCompaction = (rec) => {
950
+ const cleared = new Set(rec.clearedIds ?? []);
951
+ for (const entry of rec.entries ?? []) {
952
+ if ((entry.prompt ?? "").length > PERSISTED_PROMPT_MAX) return true;
953
+ if (cleared.has(entry.id)) return true;
954
+ }
955
+ return false;
956
+ };
957
+ const compactStoredPayloads = () => {
958
+ try {
959
+ const data = loadSessionData2();
960
+ let needed = false;
961
+ for (const rec of Object.values(data)) {
962
+ if (recordNeedsCompaction(rec)) {
963
+ needed = true;
964
+ break;
965
+ }
966
+ for (const child of Object.values(rec.children ?? {})) {
967
+ if (recordNeedsCompaction(child)) {
968
+ needed = true;
969
+ break;
970
+ }
971
+ }
972
+ if (needed) break;
973
+ }
974
+ if (!needed) return;
975
+ mutateSessionData((latest) => {
976
+ for (const rec of Object.values(latest)) {
977
+ rec.entries = [...mergeSubEntriesForPersist(rec.entries ?? [], [], rec.clearedIds).values()];
978
+ for (const child of Object.values(rec.children ?? {})) {
979
+ child.entries = [...mergeSubEntriesForPersist(child.entries ?? [], [], child.clearedIds).values()];
980
+ }
981
+ }
982
+ });
983
+ } catch {
984
+ }
985
+ };
916
986
  cleanupOldSessions();
987
+ compactStoredPayloads();
988
+ const pruneClearedIds = (sid, ids) => {
989
+ if (ids.length === 0) return;
990
+ const remove = new Set(ids);
991
+ try {
992
+ mutateSessionData((data) => {
993
+ const {
994
+ parentSid,
995
+ isChild
996
+ } = resolveParent(sid);
997
+ const rec = isChild ? data[parentSid]?.children?.[sid] : data[parentSid];
998
+ if (!rec?.clearedIds?.length) return;
999
+ const next = rec.clearedIds.filter((id) => !remove.has(id));
1000
+ if (next.length !== rec.clearedIds.length) rec.clearedIds = next;
1001
+ });
1002
+ } catch {
1003
+ }
1004
+ };
917
1005
  const [entryMap, setEntryMapRaw] = createSignal2(loadEntries(props.sessionId));
918
1006
  const setEntryMap = (arg) => {
919
1007
  setEntryMapRaw((prev) => {
@@ -937,12 +1025,12 @@ function SubAgentPanel(props) {
937
1025
  if (isChild) {
938
1026
  const parent = ensureRootRecord(data, parentSid);
939
1027
  const child = ensureChildRecord(data, parentSid, props.sessionId);
940
- child.entries = [...mergeSubEntries(child.entries ?? [], next.values()).values()];
1028
+ child.entries = [...mergeSubEntriesForPersist(child.entries ?? [], next.values(), child.clearedIds).values()];
941
1029
  parent.ts = Date.now();
942
1030
  } else {
943
1031
  const rec = ensureRootRecord(data, props.sessionId);
944
1032
  rec.ts = Date.now();
945
- rec.entries = [...mergeSubEntries(rec.entries ?? [], next.values()).values()];
1033
+ rec.entries = [...mergeSubEntriesForPersist(rec.entries ?? [], next.values(), rec.clearedIds).values()];
946
1034
  }
947
1035
  });
948
1036
  } catch {
@@ -1377,7 +1465,7 @@ function SubAgentPanel(props) {
1377
1465
  latest[parentSid] = {
1378
1466
  ...latestRec ?? rec,
1379
1467
  ts: nowTs,
1380
- entries: [...mergeSubEntries(latestRec?.entries ?? [], fallbackMap.values()).values()]
1468
+ entries: [...mergeSubEntriesForPersist(latestRec?.entries ?? [], fallbackMap.values(), latestRec?.clearedIds).values()]
1381
1469
  };
1382
1470
  });
1383
1471
  }
@@ -1395,7 +1483,7 @@ function SubAgentPanel(props) {
1395
1483
  ts: nowTs
1396
1484
  },
1397
1485
  ts: nowTs,
1398
- entries: [...mergeSubEntries(rec?.entries ?? [], parentCache.values()).values()]
1486
+ entries: [...mergeSubEntriesForPersist(rec?.entries ?? [], parentCache.values(), rec?.clearedIds).values()]
1399
1487
  };
1400
1488
  });
1401
1489
  }
@@ -1495,14 +1583,17 @@ function SubAgentPanel(props) {
1495
1583
  } catch {
1496
1584
  }
1497
1585
  if (!msgs || msgs.length === 0) return false;
1586
+ const seenErrorIds = /* @__PURE__ */ new Set();
1587
+ let scanClearedIds = /* @__PURE__ */ new Set();
1498
1588
  setEntryMapRaw((prev) => {
1499
- const next = replace ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev);
1500
1589
  const {
1501
1590
  parentSid: scanPSid,
1502
1591
  isChild: scanChild
1503
1592
  } = resolveParent(sid);
1504
1593
  const scanRec = loadSessionData2()[scanPSid];
1505
1594
  const clearedIds = new Set(scanChild ? scanRec?.children?.[sid]?.clearedIds : scanRec?.clearedIds);
1595
+ scanClearedIds = clearedIds;
1596
+ const next = withoutClearedEntries(replace ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev), clearedIds);
1506
1597
  try {
1507
1598
  for (const msg of msgs) {
1508
1599
  const parts = props.api.session.part(msg.id) ?? [];
@@ -1529,7 +1620,10 @@ function SubAgentPanel(props) {
1529
1620
  }
1530
1621
  const existingKey = findSubEntryKey(next, id, scanSubSid);
1531
1622
  const exists = existingKey ? next.get(existingKey) : void 0;
1532
- if (!exists && clearedIds.has(id)) continue;
1623
+ if (!exists && clearedIds.has(id)) {
1624
+ if (rawStatus === "error") seenErrorIds.add(id);
1625
+ continue;
1626
+ }
1533
1627
  if ((rawStatus === "pending" || rawStatus === "") && !exists) continue;
1534
1628
  if (rawStatus === "error") {
1535
1629
  if (exists && exists.status === "running") {
@@ -1612,6 +1706,22 @@ function SubAgentPanel(props) {
1612
1706
  if (snapshot.size > 0) {
1613
1707
  globalEntryCache.set(sid, new Map(snapshot));
1614
1708
  persistEntries(sid, snapshot);
1709
+ } else if (globalEntryCache.has(sid)) {
1710
+ globalEntryCache.set(sid, new Map(snapshot));
1711
+ }
1712
+ if (scanClearedIds.size > 0) {
1713
+ let liveChildIds;
1714
+ try {
1715
+ const children = props.api.session.listChildren?.(sid);
1716
+ if (children) liveChildIds = new Set(children.map((c) => c.id).filter((id) => Boolean(id)));
1717
+ } catch {
1718
+ }
1719
+ const prunable = prunableClearedIds({
1720
+ clearedIds: scanClearedIds,
1721
+ errorToolIds: seenErrorIds,
1722
+ liveChildIds
1723
+ });
1724
+ if (prunable.length > 0) pruneClearedIds(sid, prunable);
1615
1725
  }
1616
1726
  return true;
1617
1727
  };
@@ -1882,6 +1992,7 @@ function SubAgentPanel(props) {
1882
1992
  const tick = clearTick();
1883
1993
  const forceReload = tick !== lastTick && !switched;
1884
1994
  lastTick = tick;
1995
+ if (forceReload) clearTimeout(persistTimer);
1885
1996
  let attempts = 0;
1886
1997
  let retryTimer;
1887
1998
  const run = () => {
@@ -2039,7 +2150,15 @@ function SubAgentPanel(props) {
2039
2150
  const expandedPad = (label) => Math.max(0, expandedMaxLabelW() - visualWidth(label + ": "));
2040
2151
  const expandedValAvail = () => Math.max(6, panelWidth() - gutter() - INDENT - expandedMaxLabelW());
2041
2152
  const summaryParts = createMemo(() => {
2042
- if (!anyEntry()) return null;
2153
+ if (!anyEntry()) {
2154
+ return {
2155
+ done: "",
2156
+ running: null,
2157
+ err: null,
2158
+ duration: "",
2159
+ cost: ""
2160
+ };
2161
+ }
2043
2162
  const dot = "\u25CF";
2044
2163
  const cost = totalCost();
2045
2164
  return {
@@ -2052,7 +2171,6 @@ function SubAgentPanel(props) {
2052
2171
  });
2053
2172
  const summaryCols = createMemo(() => {
2054
2173
  const p = summaryParts();
2055
- if (!p) return 0;
2056
2174
  let w = visualWidth(p.done);
2057
2175
  if (p.running) w += 1 + visualWidth(p.running);
2058
2176
  if (p.err) w += 1 + visualWidth(p.err);
@@ -3380,14 +3498,16 @@ var tui = async (api) => {
3380
3498
  expanded: "",
3381
3499
  entries: []
3382
3500
  };
3501
+ const child = data[parentID].children[sid];
3383
3502
  data[parentID].children[sid] = {
3384
- ...data[parentID].children[sid],
3385
- entries: [...entries.values()]
3503
+ ...child,
3504
+ entries: [...mergeSubEntriesForPersist(child.entries ?? [], entries.values(), child.clearedIds).values()]
3386
3505
  };
3387
3506
  } else {
3388
3507
  data[sid] = {
3508
+ ...data[sid] ?? {},
3389
3509
  ts: Date.now(),
3390
- entries: [...entries.values()],
3510
+ entries: [...mergeSubEntriesForPersist(data[sid]?.entries ?? [], entries.values(), data[sid]?.clearedIds).values()],
3391
3511
  scroll: data[sid]?.scroll ?? 0,
3392
3512
  expanded: data[sid]?.expanded ?? "",
3393
3513
  children: data[sid]?.children ?? {}
@@ -3485,11 +3605,29 @@ var tui = async (api) => {
3485
3605
  onConfirm: () => {
3486
3606
  try {
3487
3607
  const data = JSON.parse(String(api.kv.get(`${KV_PREFIX}.session_data`, "{}")));
3608
+ const cachedIds = [...globalEntryCache.get(sid)?.values() ?? []].map((e) => e.id);
3488
3609
  let count = 0;
3489
3610
  if (parentID) {
3490
- if (data[parentID]?.children?.[sid]) {
3611
+ if (data[parentID]?.children?.[sid] || cachedIds.length > 0) {
3612
+ if (!data[parentID]) {
3613
+ data[parentID] = {
3614
+ ts: Date.now(),
3615
+ entries: [],
3616
+ scroll: 0,
3617
+ expanded: "",
3618
+ children: {}
3619
+ };
3620
+ }
3621
+ if (!data[parentID].children) data[parentID].children = {};
3622
+ if (!data[parentID].children[sid]) {
3623
+ data[parentID].children[sid] = {
3624
+ scroll: 0,
3625
+ expanded: "",
3626
+ entries: []
3627
+ };
3628
+ }
3491
3629
  const child = data[parentID].children[sid];
3492
- const ids = child.entries?.map((e) => e.id) ?? [];
3630
+ const ids = [.../* @__PURE__ */ new Set([...child.entries?.map((e) => e.id) ?? [], ...cachedIds])];
3493
3631
  count = ids.length;
3494
3632
  child.entries = [];
3495
3633
  child.scroll = 0;
@@ -3497,9 +3635,18 @@ var tui = async (api) => {
3497
3635
  child.clearedIds = [.../* @__PURE__ */ new Set([...child.clearedIds ?? [], ...ids])];
3498
3636
  }
3499
3637
  } else {
3500
- count = data[sid]?.entries?.length ?? 0;
3501
- if (data[sid]) {
3502
- const ids = data[sid].entries?.map((e) => e.id) ?? [];
3638
+ if (data[sid] || cachedIds.length > 0) {
3639
+ if (!data[sid]) {
3640
+ data[sid] = {
3641
+ ts: Date.now(),
3642
+ entries: [],
3643
+ scroll: 0,
3644
+ expanded: "",
3645
+ children: {}
3646
+ };
3647
+ }
3648
+ const ids = [.../* @__PURE__ */ new Set([...data[sid].entries?.map((e) => e.id) ?? [], ...cachedIds])];
3649
+ count = ids.length;
3503
3650
  data[sid].entries = [];
3504
3651
  data[sid].scroll = 0;
3505
3652
  data[sid].expanded = "";
package/dist/v2.js CHANGED
@@ -933,7 +933,7 @@ function safeErrorMsg(err) {
933
933
  }
934
934
 
935
935
  // src/_version.ts
936
- var PLUGIN_VERSION = "1.7.1";
936
+ var PLUGIN_VERSION = "1.7.3";
937
937
 
938
938
  // src/i18n.ts
939
939
  var ZH_T = {
@@ -1354,6 +1354,39 @@ function mergeSubEntries(base, incoming) {
1354
1354
  }
1355
1355
  return out;
1356
1356
  }
1357
+ var PERSISTED_PROMPT_MAX = 128;
1358
+ function compactSubEntry(entry) {
1359
+ const prompt = entry.prompt ?? "";
1360
+ if (prompt.length <= PERSISTED_PROMPT_MAX) return entry;
1361
+ return { ...entry, prompt: prompt.slice(0, PERSISTED_PROMPT_MAX) };
1362
+ }
1363
+ function mergeSubEntriesForPersist(base, incoming, clearedIds) {
1364
+ const cleared = new Set(clearedIds ?? []);
1365
+ const keep = (entry) => !cleared.has(entry.id);
1366
+ return mergeSubEntries(
1367
+ [...base].filter(keep).map(compactSubEntry),
1368
+ [...incoming].filter(keep).map(compactSubEntry)
1369
+ );
1370
+ }
1371
+ function prunableClearedIds(input) {
1372
+ const out = [];
1373
+ for (const id of input.clearedIds) {
1374
+ if (id.startsWith("tool:")) {
1375
+ if (input.errorToolIds?.has(id)) out.push(id);
1376
+ } else if (id.startsWith("sub:")) {
1377
+ if (input.liveChildIds !== void 0 && !input.liveChildIds.has(id.slice(4))) out.push(id);
1378
+ }
1379
+ }
1380
+ return out;
1381
+ }
1382
+ function withoutClearedEntries(entries, clearedIds) {
1383
+ const next = new Map(entries);
1384
+ if (!clearedIds || clearedIds.size === 0) return next;
1385
+ for (const [key, entry] of next) {
1386
+ if (clearedIds.has(entry.id)) next.delete(key);
1387
+ }
1388
+ return next;
1389
+ }
1357
1390
 
1358
1391
  // src/v2/settings-menu.ts
1359
1392
  var TTL_VALUES = [3, 7, 14, 30, 0];
@@ -1841,7 +1874,7 @@ function makeCommands(context, api, signals) {
1841
1874
  if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
1842
1875
  if (!data[parentSid].children) data[parentSid].children = {};
1843
1876
  const prev = data[parentSid].children[sid]?.entries ?? [];
1844
- const merged = [...mergeSubEntries(prev, entries.values()).values()].map(
1877
+ const merged = [...mergeSubEntriesForPersist(prev, entries.values(), data[parentSid].children[sid]?.clearedIds).values()].map(
1845
1878
  (e) => e.status === "running" || e.status === "cancel_requested" ? { ...e, status: "done", endedAt: e.endedAt ?? Date.now() } : e
1846
1879
  );
1847
1880
  data[parentSid].children[sid] = {
@@ -1850,7 +1883,7 @@ function makeCommands(context, api, signals) {
1850
1883
  };
1851
1884
  } else {
1852
1885
  const rec = data[parentSid] ?? { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
1853
- const merged = [...mergeSubEntries(rec.entries ?? [], entries.values()).values()].map(
1886
+ const merged = [...mergeSubEntriesForPersist(rec.entries ?? [], entries.values(), rec.clearedIds).values()].map(
1854
1887
  (e) => e.status === "running" || e.status === "cancel_requested" ? { ...e, status: "done", endedAt: e.endedAt ?? Date.now() } : e
1855
1888
  );
1856
1889
  data[parentSid] = { ...rec, ts: Date.now(), entries: merged, children: rec.children ?? {} };
@@ -1919,6 +1952,7 @@ function makeCommands(context, api, signals) {
1919
1952
  ]
1920
1953
  });
1921
1954
  if (choice !== "yes") return;
1955
+ const cachedIds = [...globalEntryCache.get(sid)?.values() ?? []].map((e) => e.id);
1922
1956
  try {
1923
1957
  let count = 0;
1924
1958
  const finish = () => {
@@ -1928,9 +1962,12 @@ function makeCommands(context, api, signals) {
1928
1962
  };
1929
1963
  const done = updateSessionData(kv, (data) => {
1930
1964
  if (parentID) {
1931
- const child = data[parentID]?.children?.[sid];
1932
- if (child) {
1933
- const ids = child.entries?.map((e) => e.id) ?? [];
1965
+ const parent = data[parentID];
1966
+ if (parent?.children?.[sid] || cachedIds.length > 0) {
1967
+ const parentRec = parent ?? (data[parentID] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} });
1968
+ if (!parentRec.children) parentRec.children = {};
1969
+ const child = parentRec.children[sid] ?? (parentRec.children[sid] = { scroll: 0, expanded: "", entries: [] });
1970
+ const ids = [.../* @__PURE__ */ new Set([...(child.entries ?? []).map((e) => e.id), ...cachedIds])];
1934
1971
  count = ids.length;
1935
1972
  child.entries = [];
1936
1973
  child.scroll = 0;
@@ -1939,13 +1976,14 @@ function makeCommands(context, api, signals) {
1939
1976
  }
1940
1977
  } else {
1941
1978
  const rec = data[sid];
1942
- if (rec) {
1943
- const ids = rec.entries?.map((e) => e.id) ?? [];
1979
+ if (rec || cachedIds.length > 0) {
1980
+ const target = rec ?? (data[sid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} });
1981
+ const ids = [.../* @__PURE__ */ new Set([...(target.entries ?? []).map((e) => e.id), ...cachedIds])];
1944
1982
  count = ids.length;
1945
- rec.entries = [];
1946
- rec.scroll = 0;
1947
- rec.expanded = "";
1948
- rec.clearedIds = [.../* @__PURE__ */ new Set([...rec.clearedIds ?? [], ...ids])];
1983
+ target.entries = [];
1984
+ target.scroll = 0;
1985
+ target.expanded = "";
1986
+ target.clearedIds = [.../* @__PURE__ */ new Set([...target.clearedIds ?? [], ...ids])];
1949
1987
  }
1950
1988
  }
1951
1989
  });
@@ -2286,12 +2324,12 @@ function SubAgentPanel(props) {
2286
2324
  if (isChild) {
2287
2325
  const parent = ensureRootRecord(data, parentSid);
2288
2326
  const child = ensureChildRecord(data, parentSid, sid);
2289
- child.entries = [...mergeSubEntries(child.entries ?? [], entries2.values()).values()];
2327
+ child.entries = [...mergeSubEntriesForPersist(child.entries ?? [], entries2.values(), child.clearedIds).values()];
2290
2328
  parent.ts = Date.now();
2291
2329
  } else {
2292
2330
  const rec = ensureRootRecord(data, sid);
2293
2331
  rec.ts = Date.now();
2294
- rec.entries = [...mergeSubEntries(rec.entries ?? [], entries2.values()).values()];
2332
+ rec.entries = [...mergeSubEntriesForPersist(rec.entries ?? [], entries2.values(), rec.clearedIds).values()];
2295
2333
  }
2296
2334
  });
2297
2335
  } catch {
@@ -2346,7 +2384,62 @@ function SubAgentPanel(props) {
2346
2384
  } catch {
2347
2385
  }
2348
2386
  };
2387
+ const recordNeedsCompaction = (rec) => {
2388
+ const cleared = new Set(rec.clearedIds ?? []);
2389
+ for (const entry of rec.entries ?? []) {
2390
+ if ((entry.prompt ?? "").length > PERSISTED_PROMPT_MAX) return true;
2391
+ if (cleared.has(entry.id)) return true;
2392
+ }
2393
+ return false;
2394
+ };
2395
+ const compactStoredPayloads = () => {
2396
+ try {
2397
+ const data = loadSessionData2();
2398
+ let needed = false;
2399
+ for (const rec of Object.values(data)) {
2400
+ if (recordNeedsCompaction(rec)) {
2401
+ needed = true;
2402
+ break;
2403
+ }
2404
+ for (const child of Object.values(rec.children ?? {})) {
2405
+ if (recordNeedsCompaction(child)) {
2406
+ needed = true;
2407
+ break;
2408
+ }
2409
+ }
2410
+ if (needed) break;
2411
+ }
2412
+ if (!needed) return;
2413
+ mutateSessionData((latest) => {
2414
+ for (const rec of Object.values(latest)) {
2415
+ rec.entries = [...mergeSubEntriesForPersist(rec.entries ?? [], [], rec.clearedIds).values()];
2416
+ for (const child of Object.values(rec.children ?? {})) {
2417
+ child.entries = [...mergeSubEntriesForPersist(child.entries ?? [], [], child.clearedIds).values()];
2418
+ }
2419
+ }
2420
+ });
2421
+ } catch {
2422
+ }
2423
+ };
2349
2424
  cleanupOldSessions();
2425
+ compactStoredPayloads();
2426
+ const pruneClearedIds = (sid, ids) => {
2427
+ if (ids.length === 0) return;
2428
+ const remove = new Set(ids);
2429
+ try {
2430
+ mutateSessionData((data) => {
2431
+ const {
2432
+ parentSid,
2433
+ isChild
2434
+ } = resolveParent(sid);
2435
+ const rec = isChild ? data[parentSid]?.children?.[sid] : data[parentSid];
2436
+ if (!rec?.clearedIds?.length) return;
2437
+ const next = rec.clearedIds.filter((id) => !remove.has(id));
2438
+ if (next.length !== rec.clearedIds.length) rec.clearedIds = next;
2439
+ });
2440
+ } catch {
2441
+ }
2442
+ };
2350
2443
  const [entryMap, setEntryMapRaw] = createSignal2(loadEntries(props.sessionId));
2351
2444
  const setEntryMap = (arg) => {
2352
2445
  setEntryMapRaw((prev) => {
@@ -2370,12 +2463,12 @@ function SubAgentPanel(props) {
2370
2463
  if (isChild) {
2371
2464
  const parent = ensureRootRecord(data, parentSid);
2372
2465
  const child = ensureChildRecord(data, parentSid, props.sessionId);
2373
- child.entries = [...mergeSubEntries(child.entries ?? [], next.values()).values()];
2466
+ child.entries = [...mergeSubEntriesForPersist(child.entries ?? [], next.values(), child.clearedIds).values()];
2374
2467
  parent.ts = Date.now();
2375
2468
  } else {
2376
2469
  const rec = ensureRootRecord(data, props.sessionId);
2377
2470
  rec.ts = Date.now();
2378
- rec.entries = [...mergeSubEntries(rec.entries ?? [], next.values()).values()];
2471
+ rec.entries = [...mergeSubEntriesForPersist(rec.entries ?? [], next.values(), rec.clearedIds).values()];
2379
2472
  }
2380
2473
  });
2381
2474
  } catch {
@@ -2810,7 +2903,7 @@ function SubAgentPanel(props) {
2810
2903
  latest[parentSid] = {
2811
2904
  ...latestRec ?? rec,
2812
2905
  ts: nowTs,
2813
- entries: [...mergeSubEntries(latestRec?.entries ?? [], fallbackMap.values()).values()]
2906
+ entries: [...mergeSubEntriesForPersist(latestRec?.entries ?? [], fallbackMap.values(), latestRec?.clearedIds).values()]
2814
2907
  };
2815
2908
  });
2816
2909
  }
@@ -2828,7 +2921,7 @@ function SubAgentPanel(props) {
2828
2921
  ts: nowTs
2829
2922
  },
2830
2923
  ts: nowTs,
2831
- entries: [...mergeSubEntries(rec?.entries ?? [], parentCache.values()).values()]
2924
+ entries: [...mergeSubEntriesForPersist(rec?.entries ?? [], parentCache.values(), rec?.clearedIds).values()]
2832
2925
  };
2833
2926
  });
2834
2927
  }
@@ -2928,14 +3021,17 @@ function SubAgentPanel(props) {
2928
3021
  } catch {
2929
3022
  }
2930
3023
  if (!msgs || msgs.length === 0) return false;
3024
+ const seenErrorIds = /* @__PURE__ */ new Set();
3025
+ let scanClearedIds = /* @__PURE__ */ new Set();
2931
3026
  setEntryMapRaw((prev) => {
2932
- const next = replace ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev);
2933
3027
  const {
2934
3028
  parentSid: scanPSid,
2935
3029
  isChild: scanChild
2936
3030
  } = resolveParent(sid);
2937
3031
  const scanRec = loadSessionData2()[scanPSid];
2938
3032
  const clearedIds = new Set(scanChild ? scanRec?.children?.[sid]?.clearedIds : scanRec?.clearedIds);
3033
+ scanClearedIds = clearedIds;
3034
+ const next = withoutClearedEntries(replace ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev), clearedIds);
2939
3035
  try {
2940
3036
  for (const msg of msgs) {
2941
3037
  const parts = props.api.session.part(msg.id) ?? [];
@@ -2962,7 +3058,10 @@ function SubAgentPanel(props) {
2962
3058
  }
2963
3059
  const existingKey = findSubEntryKey(next, id, scanSubSid);
2964
3060
  const exists = existingKey ? next.get(existingKey) : void 0;
2965
- if (!exists && clearedIds.has(id)) continue;
3061
+ if (!exists && clearedIds.has(id)) {
3062
+ if (rawStatus === "error") seenErrorIds.add(id);
3063
+ continue;
3064
+ }
2966
3065
  if ((rawStatus === "pending" || rawStatus === "") && !exists) continue;
2967
3066
  if (rawStatus === "error") {
2968
3067
  if (exists && exists.status === "running") {
@@ -3045,6 +3144,22 @@ function SubAgentPanel(props) {
3045
3144
  if (snapshot.size > 0) {
3046
3145
  globalEntryCache.set(sid, new Map(snapshot));
3047
3146
  persistEntries(sid, snapshot);
3147
+ } else if (globalEntryCache.has(sid)) {
3148
+ globalEntryCache.set(sid, new Map(snapshot));
3149
+ }
3150
+ if (scanClearedIds.size > 0) {
3151
+ let liveChildIds;
3152
+ try {
3153
+ const children = props.api.session.listChildren?.(sid);
3154
+ if (children) liveChildIds = new Set(children.map((c) => c.id).filter((id) => Boolean(id)));
3155
+ } catch {
3156
+ }
3157
+ const prunable = prunableClearedIds({
3158
+ clearedIds: scanClearedIds,
3159
+ errorToolIds: seenErrorIds,
3160
+ liveChildIds
3161
+ });
3162
+ if (prunable.length > 0) pruneClearedIds(sid, prunable);
3048
3163
  }
3049
3164
  return true;
3050
3165
  };
@@ -3315,6 +3430,7 @@ function SubAgentPanel(props) {
3315
3430
  const tick = clearTick();
3316
3431
  const forceReload = tick !== lastTick && !switched;
3317
3432
  lastTick = tick;
3433
+ if (forceReload) clearTimeout(persistTimer);
3318
3434
  let attempts = 0;
3319
3435
  let retryTimer;
3320
3436
  const run = () => {
@@ -3472,7 +3588,15 @@ function SubAgentPanel(props) {
3472
3588
  const expandedPad = (label) => Math.max(0, expandedMaxLabelW() - visualWidth(label + ": "));
3473
3589
  const expandedValAvail = () => Math.max(6, panelWidth() - gutter() - INDENT - expandedMaxLabelW());
3474
3590
  const summaryParts = createMemo(() => {
3475
- if (!anyEntry()) return null;
3591
+ if (!anyEntry()) {
3592
+ return {
3593
+ done: "",
3594
+ running: null,
3595
+ err: null,
3596
+ duration: "",
3597
+ cost: ""
3598
+ };
3599
+ }
3476
3600
  const dot = "\u25CF";
3477
3601
  const cost = totalCost();
3478
3602
  return {
@@ -3485,7 +3609,6 @@ function SubAgentPanel(props) {
3485
3609
  });
3486
3610
  const summaryCols = createMemo(() => {
3487
3611
  const p = summaryParts();
3488
- if (!p) return 0;
3489
3612
  let w = visualWidth(p.done);
3490
3613
  if (p.running) w += 1 + visualWidth(p.running);
3491
3614
  if (p.err) w += 1 + visualWidth(p.err);