pantheon-opencode 1.5.0-beta.13 → 1.5.0-beta.15

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 (42) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
  4. package/pyproject.toml +2 -5
  5. package/src/agents/zeus.md +4 -26
  6. package/src/pantheon/background-job-board.ts +4 -4
  7. package/src/pantheon/delegate-manager.ts +95 -10
  8. package/src/pantheon/delegation-classifier.ts +3 -4
  9. package/src/pantheon/delegation-finalize.ts +69 -7
  10. package/src/pantheon/delegation.ts +16 -4
  11. package/src/pantheon/presets.d.mts +0 -20
  12. package/src/pantheon/presets.mjs +0 -64
  13. package/src/pantheon/toon-codec.ts +1 -1
  14. package/src/plugin-v2.ts +4 -6
  15. package/src/plugin.ts +6 -6
  16. package/src/plugins/pantheon-hooks.ts +1 -1
  17. package/src/plugins/tui/dist/tui.d.ts +57 -40
  18. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  19. package/src/plugins/tui/dist/tui.js +145 -54
  20. package/src/plugins/tui/package-lock.json +2 -2
  21. package/src/plugins/tui/package.json +1 -1
  22. package/src/plugins/tui/src/index.tsx +245 -83
  23. package/src/routing.yml +0 -16
  24. package/scripts/hash_verify.py +0 -192
  25. package/scripts/test-context-compression.sh +0 -169
  26. package/src/pantheon/auto-wake.ts +0 -85
  27. package/src/pantheon/chat-reminders.ts +0 -9
  28. package/src/pantheon/comment-checker.ts +0 -48
  29. package/src/pantheon/compaction-assert.ts +0 -160
  30. package/src/pantheon/compaction-handoff.ts +0 -304
  31. package/src/pantheon/cost-tracker.ts +0 -108
  32. package/src/pantheon/dispatch-guard.ts +0 -109
  33. package/src/pantheon/permission-eval.ts +0 -284
  34. package/src/pantheon/preemptive-compact.ts +0 -76
  35. package/src/pantheon/provider-capabilities.ts +0 -221
  36. package/src/pantheon/provider-stream-tracker.ts +0 -182
  37. package/src/pantheon/retry-policy.ts +0 -212
  38. package/src/pantheon/token-opt.ts +0 -689
  39. package/src/pantheon/v2-events.ts +0 -148
  40. package/src/pantheon/v2-hooks.ts +0 -287
  41. package/src/pantheon/v2-tool-definitions.ts +0 -331
  42. package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
@@ -165,7 +165,7 @@ async function detectVersion(api) {
165
165
  if (ver) return ver;
166
166
  }
167
167
  } catch {}
168
- return "1.5.0-beta.13";
168
+ return "1.5.0-beta.15";
169
169
  }
170
170
  /**
171
171
  * usage-bar — AI subscription usage gauge for the opencode TUI.
@@ -832,11 +832,12 @@ async function readDelegationEntries(dir) {
832
832
  }
833
833
  /** Read delegation reports from EVERY session under
834
834
  * `<root>/.pantheon/delegations/<sessionID>/<alias>.md` — the panel's
835
- * HISTORY channel. Unlike the children/live channels it does NOT depend on a
836
- * resolved sessionID: with no focused session (null/placeholder), the panel
837
- * still shows the reports from all past sessions (running first, Finalized
838
- * desc — the sort applied by readDelegationEntries). Fail-open: a
839
- * missing/unreadable directory yields []. */
835
+ * ENRICHMENT source. The read is deliberately unfiltered (each entry carries
836
+ * its directory's sessionID, so a taskID match can enrich any channel);
837
+ * callers MUST scope the merged result with {@link filterDelegationsToSession}
838
+ * before rendering — only the active session's reports are shown. Entries are
839
+ * running first, Finalized desc (the sort applied by readDelegationEntries).
840
+ * Fail-open: a missing/unreadable directory yields []. */
840
841
  async function readAllDelegationEntries(root) {
841
842
  return readDelegationEntries(join(root, ".pantheon", "delegations"));
842
843
  }
@@ -939,8 +940,13 @@ function compareDelegationEntries(a, b) {
939
940
  function splitDelegationList(all, maxRecent = 8, now = Date.now(), staleThresholdMs = 1800 * 1e3) {
940
941
  const isActiveState = (st) => st === "running" || st === "retry" || st === "stale-running";
941
942
  return {
942
- active: all.filter((d) => isActiveState(d.state)).map((d) => markStaleIfRunning(d, now, staleThresholdMs)),
943
- recent: all.filter((d) => !isActiveState(d.state)).slice(0, maxRecent)
943
+ active: all.filter((d) => isActiveState(d.state)).map((d) => markStaleIfRunning(d, now, staleThresholdMs)).sort(compareDelegationEntries),
944
+ recent: all.filter((d) => !isActiveState(d.state)).filter((d) => {
945
+ if (d.updatedAt === null || !Number.isFinite(d.updatedAt)) return true;
946
+ const age = now - d.updatedAt;
947
+ if (age < 0) return true;
948
+ return age < (delegationRowStatus(d.state) === "failed" ? DELEGATION_FAILED_RETENTION_MS : DELEGATION_DONE_RETENTION_MS);
949
+ }).sort(compareDelegationEntries).slice(0, maxRecent)
944
950
  };
945
951
  }
946
952
  /** Live-first window used by {@link ceilingDelegationList} (kept for the
@@ -956,6 +962,15 @@ const STALE_RUNNING_THRESHOLD_MS = 1800 * 1e3;
956
962
  * considered stale. Combined with the stale-running threshold to produce the
957
963
  * display-only `stale-running` state. */
958
964
  const IDLE_SILENCE_MS = 60 * 1e3;
965
+ /** Visual-only terminal retention windows. Reports remain on disk and in the
966
+ * board; these constants only control which rows enter the TUI window. */
967
+ const DELEGATION_DONE_RETENTION_MS = 120 * 1e3;
968
+ const DELEGATION_FAILED_RETENTION_MS = 600 * 1e3;
969
+ /** Alias-less NATIVE task() live entries never receive a board alias (the
970
+ * task tool output carries none), so the 30s alias-less prune in
971
+ * mergeChildDelegationSources must not apply to them — 5 minutes covers a
972
+ * slow child listing while still bounding the live map. */
973
+ const NATIVE_LIVE_ALIASLESS_TTL_MS = 300 * 1e3;
959
974
  /**
960
975
  * Mark a running entry as `stale-running` if it has been running longer than
961
976
  * the threshold AND has no recent activity (no `updatedAt` change in the last
@@ -1039,20 +1054,21 @@ function delegationSpinnerFrame(now) {
1039
1054
  * pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
1040
1055
  /** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
1041
1056
  * Kept separate + pure so the color channel is testable without booting the
1042
- * renderer. */
1043
- /** Color is the ONLY channel separating `stale-running` from a live `running`
1044
- * row — the 4-glyph row design has no dedicated stale shape. Display-only;
1045
- * no behavior change. */
1057
+ * renderer. Every display state resolves to one of the three status colors;
1058
+ * the row paints its whole content with it (see {@link DelegationRow}). */
1059
+ /** Status → color mapping for the whole-row tone. Red = failure, green =
1060
+ * terminal, yellow = in flight. The glyph remains a redundant channel so the
1061
+ * state stays legible in monochrome. Display-only; no behavior change. */
1046
1062
  function delegationStateTone(state) {
1047
1063
  switch (state) {
1048
1064
  case "running":
1049
1065
  case "retry":
1050
- case "startup_unknown": return "warning";
1051
- case "stale-running":
1066
+ case "startup_unknown":
1067
+ case "stale-running": return "warning";
1052
1068
  case "error":
1053
1069
  case "startup_failed": return "error";
1054
- case "completed": return "success";
1055
- case "cancelled": return "muted";
1070
+ case "completed":
1071
+ case "cancelled": return "success";
1056
1072
  }
1057
1073
  }
1058
1074
  /** Bounded tracker: child session id → latest running tool call. */
@@ -1127,13 +1143,11 @@ function mergeChildDelegationSources(children, live) {
1127
1143
  bySessionAlias.set(key(entry.sessionID, entry.alias), index);
1128
1144
  });
1129
1145
  for (const liveEntry of live) {
1130
- if (liveEntry.alias === null && liveEntry.taskID === null && Date.now() - liveEntry.startedAt > 3e4) continue;
1146
+ const aliaslessTTL = liveEntry.tool === "task" ? NATIVE_LIVE_ALIASLESS_TTL_MS : 3e4;
1147
+ if (liveEntry.alias === null && liveEntry.taskID === null && Date.now() - liveEntry.startedAt > aliaslessTTL) continue;
1131
1148
  const incoming = toDelegationEntry(liveEntry);
1132
1149
  const isNativeLive = liveEntry.tool === "task" && liveEntry.alias === null;
1133
- if (isNativeLive) {
1134
- incoming.source = "children-only";
1135
- incoming.alias = "native-task";
1136
- }
1150
+ if (isNativeLive) incoming.source = "children-only";
1137
1151
  const index = (incoming.taskID !== void 0 ? byTask.get(incoming.taskID) : void 0) ?? (incoming.alias !== "" ? bySessionAlias.get(key(incoming.sessionID, incoming.alias)) : void 0);
1138
1152
  if (index === void 0) {
1139
1153
  result.push(incoming);
@@ -1456,10 +1470,29 @@ function boardRecordsToDelegationEntries(records) {
1456
1470
  out.sort(compareDelegationEntries);
1457
1471
  return out;
1458
1472
  }
1473
+ /** Active (non-terminal) display states — a job in one of these is still live. */
1474
+ function isActiveDelegationState(state) {
1475
+ return state === "running" || state === "retry" || state === "stale-running";
1476
+ }
1477
+ /** Temporal guard for the board merge (recycled-alias regression): board
1478
+ * aliases recycle across jobs (apo-1 of a previous job), so a board
1479
+ * terminal record only closes a running entry when its Finalized timestamp
1480
+ * is STRICTLY NEWER than the entry it would close. Older/equal/missing
1481
+ * keeps the live entry — when in doubt the live job wins. Pure. */
1482
+ function shouldKeepRunningOverBoardTerminal(existing, incoming) {
1483
+ if (existing === void 0) return false;
1484
+ if (!isActiveDelegationState(existing.state)) return false;
1485
+ if (isActiveDelegationState(incoming.state)) return false;
1486
+ const incomingTs = incoming.updatedAt;
1487
+ if (incomingTs === null) return true;
1488
+ return incomingTs <= (existing.updatedAt ?? existing.startedAt);
1489
+ }
1459
1490
  /** Merge the board over any other channel. The board is AUTHORITATIVE for
1460
- * state/alias/agent (it is the persisted FSM) and it is the only channel
1461
- * that carries jobs from OTHER sessions. Dedup by taskID first, then by
1462
- * (sessionID, alias) — aliases are per parent session. Pure. */
1491
+ * state/alias/agent (it is the persisted FSM). This merge is intentionally
1492
+ * UNSCOPED so the board can enrich an active-session row (dedup by taskID);
1493
+ * the caller scopes the merged list with {@link filterDelegationsToSession},
1494
+ * which is what keeps other-session board jobs out of the panel. Dedup by
1495
+ * taskID first, then by (sessionID, alias) — aliases are per parent session. Pure. */
1463
1496
  function mergeBoardDelegationSources(base, board) {
1464
1497
  const key = (sessionID, alias) => `${sessionID}\u0000${alias}`;
1465
1498
  const byTaskID = /* @__PURE__ */ new Map();
@@ -1478,6 +1511,7 @@ function mergeBoardDelegationSources(base, board) {
1478
1511
  bySessionAlias.set(key(incoming.sessionID, incoming.alias), next);
1479
1512
  continue;
1480
1513
  }
1514
+ if (shouldKeepRunningOverBoardTerminal(out[index], incoming)) continue;
1481
1515
  out[index] = {
1482
1516
  ...incoming,
1483
1517
  sessionID: incoming.sessionID !== "" ? incoming.sessionID : out[index]?.sessionID ?? ""
@@ -1486,6 +1520,26 @@ function mergeBoardDelegationSources(base, board) {
1486
1520
  out.sort(compareDelegationEntries);
1487
1521
  return out;
1488
1522
  }
1523
+ /** Scope a fully-merged display list to the ACTIVE session only.
1524
+ *
1525
+ * The panel is session-scoped: rows from other sessions (board snapshot, md
1526
+ * history under `.pantheon/delegations/<other-session>/`) and rows with no
1527
+ * attributable session (empty sessionID) are DROPPED. The previous
1528
+ * cross-session behavior rendered those rows and clicking them led to
1529
+ * "Session not found" — there is no cross-session channel anymore.
1530
+ *
1531
+ * The board/md channels still feed the merge UNFILTERED so the board can
1532
+ * ENRICH an active-session row with state/alias/agent (dedup by taskID), but
1533
+ * they can never introduce a row for another session: this filter is applied
1534
+ * ONCE, after every merge (children + live + md + board).
1535
+ *
1536
+ * Returns [] when no active session resolves (null/placeholder) — there is no
1537
+ * scope to show. Native children always carry the active session id (stamped
1538
+ * by `childrenToDelegationEntries`), so they survive the filter. Pure. */
1539
+ function filterDelegationsToSession(entries, activeSessionID) {
1540
+ if (!isValidSessionId(activeSessionID)) return [];
1541
+ return entries.filter((entry) => entry.sessionID === activeSessionID);
1542
+ }
1489
1543
  /** Server-aligned session id validity: opencode rejects anything not starting
1490
1544
  * with "ses" (SchemaError). This deliberately mirrors that exact contract —
1491
1545
  * nothing stricter, nothing looser — so a template placeholder ("{sessionID}"),
@@ -1583,10 +1637,13 @@ function delegationRowMarker(state, now = Date.now()) {
1583
1637
  return `${status === "active" ? delegationSpinnerFrame(now) : delegationRowGlyph(status)} `;
1584
1638
  }
1585
1639
  /** ONE identity per row: the short board alias (`apo-1`); a row without a
1586
- * board alias (native task() child, alias `native-task`) shows the agent
1587
- * instead — never alias + agent stacked. Pure. */
1640
+ * board alias (native task() child `native-task` / `native-<id>`, or an
1641
+ * alias-less native live row `live-<callID>` kept as children-only) shows
1642
+ * the agent instead — never alias + agent stacked. Pure. */
1588
1643
  function delegationRowIdentity(entry) {
1589
- if (entry.alias === "native-task" && entry.agent !== "") return entry.agent;
1644
+ if (entry.agent === "") return entry.alias;
1645
+ if (entry.alias === "native-task" || entry.alias.startsWith("native-")) return entry.agent;
1646
+ if (entry.alias.startsWith("live-") && entry.source === "children-only") return entry.agent;
1590
1647
  return entry.alias;
1591
1648
  }
1592
1649
  /** Fixed alias cell, pad-right + hard-truncate to `width` (default 7) so
@@ -1662,14 +1719,20 @@ function formatDelegationHeader(entries) {
1662
1719
  const tail = failed > 0 ? ` · ${failed} failed` : "";
1663
1720
  return `(${active} active · ${done} done${tail})`;
1664
1721
  }
1665
- /** Cap the panel: live rows first, then most-recent terminal rows, at most
1666
- * `maxVisible` total. `hidden` is how many were dropped (rendered as
1667
- * "… +N more"). Pure. */
1722
+ /** Cap the panel: live rows first, then most-recent retained terminal rows, at
1723
+ * most `maxVisible` total. Hidden counts describe the retained render list,
1724
+ * not expired history. */
1668
1725
  function ceilingDelegationList(all, maxVisible = 8, now = Date.now()) {
1669
- const visible = visibleDelegationList(all, maxVisible, now).slice(0, maxVisible);
1726
+ const { active, recent } = splitDelegationList(all, all.length, now);
1727
+ const visibleActive = active.slice(0, maxVisible);
1728
+ const visible = [...visibleActive, ...recent.slice(0, Math.max(0, maxVisible - visibleActive.length))];
1729
+ const hiddenActive = Math.max(0, active.length - visibleActive.length);
1730
+ const hiddenTerminal = Math.max(0, recent.length - (visible.length - visibleActive.length));
1670
1731
  return {
1671
1732
  visible,
1672
- hidden: Math.max(0, all.length - visible.length)
1733
+ hidden: hiddenActive + hiddenTerminal,
1734
+ hiddenActive,
1735
+ hiddenTerminal
1673
1736
  };
1674
1737
  }
1675
1738
  /** Split a display list into native task() rows vs pantheon_delegate rows.
@@ -1698,11 +1761,13 @@ function formatPanelLogLine(children, pantheon, native, md, events) {
1698
1761
  * itself (fallback 'agent'), state derived from its status, startedAt from
1699
1762
  * time.created. A report-less child is a NATIVE task() child (every
1700
1763
  * child of the current session — pantheon_delegate OR the native `task()`
1701
- * tool — carries parentID = caller), so it gets source 'children-only',
1702
- * the internal alias 'native-task' instead of a
1703
- * board alias. The 'task nativa' description fallback keeps the row
1704
- * non-empty when the child carries no title.
1705
- * Terminal md state wins over the derived state; a running md defers to
1764
+ * tool — carries parentID = caller), so it gets source 'children-only', a
1765
+ * per-child alias 'native-<last4 of the child id>' (one identity per native
1766
+ * row) instead of a board alias. The 'task nativa' description fallback
1767
+ * keeps the row non-empty when the child carries no title.
1768
+ * A FRESH terminal md state wins over the derived state (a stale md report
1769
+ * from a previous incarnation never flips a live child — see
1770
+ * mdTerminalCoversChild); a running md defers to
1706
1771
  * the child's live status. A running child is NEVER archived — it always
1707
1772
  * lands in the active split (splitDelegationList active = running/retry).
1708
1773
  * `parentSessionID` (the focused session id) is stamped on report-less
@@ -1710,6 +1775,20 @@ function formatPanelLogLine(children, pantheon, native, md, events) {
1710
1775
  * unscoped ('').
1711
1776
  * Sorted running-first (compareDelegationEntries).
1712
1777
  * Pure — no I/O. */
1778
+ /** Temporal guard for the children channel (same recycled-alias class as the
1779
+ * board merge): an md terminal report matched by taskID may still belong to
1780
+ * a PREVIOUS job incarnation. Accept it only when its Finalized timestamp
1781
+ * covers the child's latest activity, when either side has no timestamp to
1782
+ * compare, or when both startedAt agree on a single job incarnation.
1783
+ * Pure — no I/O. */
1784
+ function mdTerminalCoversChild(md, child) {
1785
+ const childUpdated = child.time?.updated;
1786
+ if (md.updatedAt !== null && childUpdated !== void 0) {
1787
+ if (md.updatedAt >= childUpdated) return true;
1788
+ return md.startedAt === child.time?.created;
1789
+ }
1790
+ return true;
1791
+ }
1713
1792
  function childrenToDelegationEntries(children, md, now = Date.now(), parentSessionID = "") {
1714
1793
  const byTaskID = /* @__PURE__ */ new Map();
1715
1794
  for (const m of md) if (m.taskID !== void 0 && !byTaskID.has(m.taskID)) byTaskID.set(m.taskID, m);
@@ -1719,9 +1798,9 @@ function childrenToDelegationEntries(children, md, now = Date.now(), parentSessi
1719
1798
  if (child.id === "" || seen.has(child.id)) continue;
1720
1799
  seen.add(child.id);
1721
1800
  const mdEntry = byTaskID.get(child.id);
1722
- const state = mdEntry !== void 0 && mdEntry.state !== "running" ? mdEntry.state : childStatusToState(child.status);
1801
+ const state = mdEntry !== void 0 && mdEntry.state !== "running" && mdTerminalCoversChild(mdEntry, child) ? mdEntry.state : childStatusToState(child.status);
1723
1802
  out.push({
1724
- alias: mdEntry?.alias ?? "native-task",
1803
+ alias: mdEntry?.alias ?? `native-${child.id.slice(-4)}`,
1725
1804
  sessionID: mdEntry?.sessionID ?? parentSessionID,
1726
1805
  taskID: child.id,
1727
1806
  agent: mdEntry?.agent ?? child.agent ?? "agent",
@@ -1743,8 +1822,19 @@ function childrenToDelegationEntries(children, md, now = Date.now(), parentSessi
1743
1822
  * unsubstituted "{sessionID}" placeholder can never be routed. */
1744
1823
  function navigateToDelegationSession(route, taskID) {
1745
1824
  if (typeof route?.navigate !== "function" || !isValidSessionId(taskID)) return false;
1746
- route.navigate("session", { sessionID: taskID });
1747
- return true;
1825
+ try {
1826
+ const navigation = route.navigate("session", { sessionID: taskID });
1827
+ if (navigation !== void 0) Promise.resolve(navigation).catch(() => void 0);
1828
+ return true;
1829
+ } catch {
1830
+ return false;
1831
+ }
1832
+ }
1833
+ /** Build the mouse handler used by each delegation row. */
1834
+ function createDelegationRowOpenHandler(route, taskID) {
1835
+ return () => {
1836
+ navigateToDelegationSession(route, taskID);
1837
+ };
1748
1838
  }
1749
1839
  /** Silence-by-default panel logger.
1750
1840
  * @param projectRoot the PROJECT ROOT — the logger appends to
@@ -1822,9 +1912,7 @@ function DelegationRow(props) {
1822
1912
  const elapsed = createMemo(() => formatDelegationElapsed(props.job, props.now));
1823
1913
  const description = createMemo(() => truncateDelegationDescription(props.job.description));
1824
1914
  const activity = createMemo(() => latestToolActivityFor(latestToolActivity, props.job.taskID, props.now));
1825
- const open = () => {
1826
- navigateToDelegationSession(props.api.route, props.job.taskID);
1827
- };
1915
+ const open = createDelegationRowOpenHandler(props.api.route, props.job.taskID);
1828
1916
  return (() => {
1829
1917
  var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("span"), _el$17 = createElement("span");
1830
1918
  insertNode(_el$13, _el$14);
@@ -1842,7 +1930,7 @@ function DelegationRow(props) {
1842
1930
  get children() {
1843
1931
  var _el$18 = createElement("text");
1844
1932
  insert(_el$18, () => ` ${description()}`);
1845
- effect((_$p) => setProp(_el$18, "fg", theme().textMuted, _$p));
1933
+ effect((_$p) => setProp(_el$18, "fg", tone(), _$p));
1846
1934
  return _el$18;
1847
1935
  }
1848
1936
  }), null);
@@ -1853,12 +1941,12 @@ function DelegationRow(props) {
1853
1941
  children: (a) => (() => {
1854
1942
  var _el$19 = createElement("text");
1855
1943
  insert(_el$19, () => ` ↳ ${truncateDelegationDescription(`${a().tool}${a().summary !== "" ? ` ${a().summary}` : ""}`)}`);
1856
- effect((_$p) => setProp(_el$19, "fg", theme().textMuted, _$p));
1944
+ effect((_$p) => setProp(_el$19, "fg", tone(), _$p));
1857
1945
  return _el$19;
1858
1946
  })()
1859
1947
  }), null);
1860
1948
  effect((_p$) => {
1861
- var _v$5 = tone(), _v$6 = theme().textMuted;
1949
+ var _v$5 = tone(), _v$6 = tone();
1862
1950
  _v$5 !== _p$.e && (_p$.e = setProp(_el$16, "fg", _v$5, _p$.e));
1863
1951
  _v$6 !== _p$.t && (_p$.t = setProp(_el$17, "fg", _v$6, _p$.t));
1864
1952
  return _p$;
@@ -1951,8 +2039,8 @@ function View(props) {
1951
2039
  api: props.api
1952
2040
  });
1953
2041
  if (sessionID === null) {
1954
- setChildDelegations(mergeBoardDelegationSources(md, board));
1955
- panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — history+board shown)`);
2042
+ setChildDelegations(filterDelegationsToSession(mergeBoardDelegationSources(md, board), sessionID));
2043
+ panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — inactive panel)`);
1956
2044
  return;
1957
2045
  }
1958
2046
  let children = [];
@@ -1978,7 +2066,7 @@ function View(props) {
1978
2066
  })), md, now(), sessionID);
1979
2067
  for (const [k, v] of props.liveStore.map) if (v.alias === null && v.taskID === null && Date.now() - v.startedAt > 3e4) props.liveStore.map.delete(k);
1980
2068
  const liveEntries = [...props.liveStore.map.values()].filter((entry) => entry.sessionID === sessionID);
1981
- setChildDelegations(mergeBoardDelegationSources(mergeChildDelegationSources(childEntries, liveEntries), board));
2069
+ setChildDelegations(filterDelegationsToSession(mergeBoardDelegationSources(mergeChildDelegationSources(childEntries, liveEntries), board), sessionID));
1982
2070
  const counts = countDelegationSources(childEntries);
1983
2071
  panelLog.info(formatPanelLogLine(children.length, counts.pantheon, counts.native, md.length, eventRefreshCount));
1984
2072
  } finally {
@@ -1988,7 +2076,7 @@ function View(props) {
1988
2076
  return delegationsInflight;
1989
2077
  };
1990
2078
  const delegationCeiling = createMemo(() => ceilingDelegationList(childDelegations(), 8, now()));
1991
- const delegationHeader = createMemo(() => formatDelegationHeader(childDelegations()));
2079
+ const delegationHeader = createMemo(() => formatDelegationHeader(delegationCeiling().visible));
1992
2080
  onMount(() => {
1993
2081
  const cleanup = [];
1994
2082
  try {
@@ -2153,7 +2241,7 @@ function View(props) {
2153
2241
  setProp(_el$29, "attributes", 1);
2154
2242
  insert(_el$29, () => `${showDelegations() ? "▼" : "▶"} Delegations`);
2155
2243
  insert(_el$30, (() => {
2156
- var _c$ = memo(() => childDelegations().length > 0);
2244
+ var _c$ = memo(() => delegationCeiling().visible.length > 0);
2157
2245
  return () => _c$() ? ` ${delegationHeader()}` : " — idle";
2158
2246
  })());
2159
2247
  insert(_el$22, createComponent(Show, {
@@ -2202,7 +2290,10 @@ function View(props) {
2202
2290
  },
2203
2291
  get children() {
2204
2292
  var _el$32 = createElement("text");
2205
- insert(_el$32, () => `… +${delegationCeiling().hidden} more`);
2293
+ insert(_el$32, (() => {
2294
+ var _c$2 = memo(() => delegationCeiling().hiddenActive > 0);
2295
+ return () => _c$2() ? `… +${delegationCeiling().hiddenActive} active` : `… +${delegationCeiling().hiddenTerminal} more`;
2296
+ })());
2206
2297
  effect((_$p) => setProp(_el$32, "fg", theme().textMuted, _$p));
2207
2298
  return _el$32;
2208
2299
  }
@@ -2287,6 +2378,6 @@ const plugin = {
2287
2378
  setup: async () => {}
2288
2379
  };
2289
2380
  //#endregion
2290
- export { DELEGATION_ALIAS_WIDTH, DELEGATION_DESCRIPTION_MAX, DELEGATION_ELAPSED_WIDTH, DELEGATION_ROW_GLYPHS, DELEGATION_VISIBLE_CEILING, IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, boardRecordToDelegationEntry, boardRecordsToDelegationEntries, boardStatePath, buildChildrenPath, ceilingDelegationList, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationRowGlyph, delegationRowIdentity, delegationRowMarker, delegationRowStatus, delegationSpinnerFrame, delegationStateTone, extractToolActivity, fmtElapsed, formatDelegationAlias, formatDelegationElapsed, formatDelegationHeader, formatDelegationRowLead, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeBoardDelegationSources, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readBoardState, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, resolvePantheonRoot, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
2381
+ export { DELEGATION_ALIAS_WIDTH, DELEGATION_DESCRIPTION_MAX, DELEGATION_DONE_RETENTION_MS, DELEGATION_ELAPSED_WIDTH, DELEGATION_FAILED_RETENTION_MS, DELEGATION_ROW_GLYPHS, DELEGATION_VISIBLE_CEILING, IDLE_SILENCE_MS, NATIVE_LIVE_ALIASLESS_TTL_MS, STALE_RUNNING_THRESHOLD_MS, boardRecordToDelegationEntry, boardRecordsToDelegationEntries, boardStatePath, buildChildrenPath, ceilingDelegationList, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, createDelegationRowOpenHandler, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationRowGlyph, delegationRowIdentity, delegationRowMarker, delegationRowStatus, delegationSpinnerFrame, delegationStateTone, extractToolActivity, filterDelegationsToSession, fmtElapsed, formatDelegationAlias, formatDelegationElapsed, formatDelegationHeader, formatDelegationRowLead, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeBoardDelegationSources, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readBoardState, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, resolvePantheonRoot, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
2291
2382
 
2292
2383
  //# sourceMappingURL=tui.js.map
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
- "version": "1.5.0-beta.13",
3
+ "version": "1.5.0-beta.15",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pantheon-tui",
9
- "version": "1.5.0-beta.13",
9
+ "version": "1.5.0-beta.15",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@opencode-ai/plugin": ">=1.14.0",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
3
  "type": "module",
4
- "version": "1.5.0-beta.13",
4
+ "version": "1.5.0-beta.15",
5
5
  "description": "Pantheon TUI plugin — sidebar (version, preset, sessions, real-time delegations) + AI subscription usage gauges incl. OpenCode Go/Zen dollar limits (vendored, MIT)",
6
6
  "license": "MIT",
7
7
  "exports": {