pantheon-opencode 1.5.0-beta.8 → 1.5.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.
Files changed (82) hide show
  1. package/AGENTS.md +14 -63
  2. package/CHANGELOG.md +320 -0
  3. package/README.md +14 -27
  4. package/README.pt-BR.md +5 -6
  5. package/commands/pantheon.md +1 -35
  6. package/package.json +1 -2
  7. package/plugin.json +1 -1
  8. package/pyproject.toml +2 -5
  9. package/scripts/doctor.mjs +32 -3
  10. package/scripts/install/plugin.mjs +9 -12
  11. package/scripts/mcp_persistence_server.py +1 -1
  12. package/scripts/sync-tui.mjs +8 -11
  13. package/src/agents/apollo.md +1 -0
  14. package/src/agents/gaia.md +1 -0
  15. package/src/agents/zeus.md +9 -52
  16. package/src/instructions/memory-protocol.instructions.md +6 -3
  17. package/src/instructions/zeus-anti-stall.instructions.md +2 -4
  18. package/src/instructions/zeus-council-synthesis.instructions.md +3 -28
  19. package/src/instructions/zeus-timeout-retry.instructions.md +3 -28
  20. package/src/mcp/mcp_persistence_server.py +1 -1
  21. package/src/mcp/toon_codec.py +2 -4
  22. package/src/pantheon/background-job-board.ts +4 -4
  23. package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
  24. package/src/pantheon/cost-command.ts +1 -2
  25. package/src/pantheon/delegation-enforce.ts +59 -14
  26. package/src/pantheon/goal-loop.ts +1 -1
  27. package/src/pantheon/goal-store.ts +1 -1
  28. package/src/pantheon/idle-continuation.ts +4 -6
  29. package/src/pantheon/model-command.ts +1 -2
  30. package/src/pantheon/native-task-status.ts +2 -2
  31. package/src/pantheon/presets.d.mts +0 -20
  32. package/src/pantheon/presets.mjs +0 -64
  33. package/src/pantheon/session-guard.ts +2 -2
  34. package/src/pantheon/session-id.ts +30 -0
  35. package/src/pantheon/task-result-guard.ts +2 -3
  36. package/src/pantheon/todo-enforcer.ts +3 -3
  37. package/src/pantheon/tool-context.ts +23 -0
  38. package/src/pantheon/v2-bridge.ts +1 -4
  39. package/src/plugin-v2.ts +8 -21
  40. package/src/plugin.ts +61 -245
  41. package/src/plugins/pantheon-hooks.ts +1 -1
  42. package/src/plugins/tui/dist/tui.d.ts +229 -77
  43. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  44. package/src/plugins/tui/dist/tui.js +509 -259
  45. package/src/plugins/tui/package-lock.json +2 -2
  46. package/src/plugins/tui/package.json +2 -2
  47. package/src/plugins/tui/src/index.tsx +753 -279
  48. package/src/routing.yml +0 -16
  49. package/src/skills/context-compression/SKILL.md +1 -1
  50. package/scripts/generate-preset-docs.mjs +0 -300
  51. package/scripts/generate-prompts.sh +0 -222
  52. package/scripts/hash_verify.py +0 -192
  53. package/scripts/install/component.mjs +0 -24
  54. package/scripts/install/migrate-config.mjs +0 -302
  55. package/scripts/test-context-compression.sh +0 -169
  56. package/src/pantheon/auto-wake.ts +0 -85
  57. package/src/pantheon/chat-reminders.ts +0 -9
  58. package/src/pantheon/comment-checker.ts +0 -48
  59. package/src/pantheon/compaction-assert.ts +0 -160
  60. package/src/pantheon/compaction-handoff.ts +0 -304
  61. package/src/pantheon/cost-tracker.ts +0 -108
  62. package/src/pantheon/delegate-manager.ts +0 -766
  63. package/src/pantheon/delegation-classifier.ts +0 -307
  64. package/src/pantheon/delegation-finalize.ts +0 -406
  65. package/src/pantheon/delegation-notify.ts +0 -212
  66. package/src/pantheon/delegation-toast.ts +0 -73
  67. package/src/pantheon/delegation.ts +0 -1337
  68. package/src/pantheon/dispatch-guard.ts +0 -109
  69. package/src/pantheon/native-probe.ts +0 -234
  70. package/src/pantheon/permission-eval.ts +0 -284
  71. package/src/pantheon/preemptive-compact.ts +0 -76
  72. package/src/pantheon/provider-capabilities.ts +0 -221
  73. package/src/pantheon/provider-stream-tracker.ts +0 -182
  74. package/src/pantheon/retry-policy.ts +0 -212
  75. package/src/pantheon/step-cap.ts +0 -110
  76. package/src/pantheon/token-opt.ts +0 -689
  77. package/src/pantheon/toon-codec.ts +0 -481
  78. package/src/pantheon/v2-events.ts +0 -148
  79. package/src/pantheon/v2-hooks.ts +0 -287
  80. package/src/pantheon/v2-tool-definitions.ts +0 -331
  81. package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
  82. package/src/plugins/tui/dist/tui.tsx +0 -2777
@@ -1,6 +1,6 @@
1
1
  import { createComponent, createElement, createTextNode, effect, insert, insertNode, memo, setProp } from "@opentui/solid";
2
2
  import { Buffer } from "node:buffer";
3
- import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import { appendFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -10,9 +10,10 @@ import { For, Show, createEffect, createMemo, createResource, createSignal, onCl
10
10
  /**
11
11
  * pantheon-tui — Pantheon TUI plugin for opencode.
12
12
  *
13
- * Single self-contained entry file: `tsdown && cp src/index.tsx dist/tui.tsx`
14
- * keeps the no-build load path working (dist/tui.tsx is raw source, transpiled
15
- * on the fly by opencode). All feature code lives in this one file.
13
+ * Bundled entry: `tsdown` emits `dist/tui.js`, and the package `exports`
14
+ * (`./tui` → ./dist/tui.js, `./server` → ./dist/server.js) is the ONLY load
15
+ * path — no raw TSX copy is shipped. Because the bundle inlines relative
16
+ * imports, this entry may be split into modules without breaking the loader.
16
17
  *
17
18
  * Slots:
18
19
  * - sidebar_content (order 900) — Pantheon sidebar (header/version/
@@ -164,7 +165,7 @@ async function detectVersion(api) {
164
165
  if (ver) return ver;
165
166
  }
166
167
  } catch {}
167
- return "1.5.0-beta.8";
168
+ return "1.5.0";
168
169
  }
169
170
  /**
170
171
  * usage-bar — AI subscription usage gauge for the opencode TUI.
@@ -831,25 +832,31 @@ async function readDelegationEntries(dir) {
831
832
  }
832
833
  /** Read delegation reports from EVERY session under
833
834
  * `<root>/.pantheon/delegations/<sessionID>/<alias>.md` — the panel's
834
- * HISTORY channel. Unlike the children/live channels it does NOT depend on a
835
- * resolved sessionID: with no focused session (null/placeholder), the panel
836
- * still shows the reports from all past sessions (running first, Finalized
837
- * desc — the sort applied by readDelegationEntries). Fail-open: a
838
- * 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 []. */
839
841
  async function readAllDelegationEntries(root) {
840
842
  return readDelegationEntries(join(root, ".pantheon", "delegations"));
841
843
  }
842
- /** Resolve the directory where the job board writes delegation md reports.
843
- * The board writes `.pantheon/delegations` RELATIVE to the server cwd,
844
- * which the TUI exposes as `TuiState.path.directory`. `project` does NOT
845
- * exist on `TuiState.path` (the old `state?.project ?? state?.worktree`
846
- * resolution was always undefined for the first term) and `worktree` is
847
- * `/` when there is no git (e.g. the sandbox test project) — a root of
848
- * `''` or `'/'` must fall back to `process.cwd()`. */
849
- function resolveDelegationsDir(state, cwd = process.cwd()) {
844
+ /** Resolve the PROJECT ROOT used by every pantheon file channel. `directory`
845
+ * wins over `worktree` (the old `resolveDelegationsDir` already did this);
846
+ * an absent/empty root or `/` (no git — e.g. the sandbox test project) falls
847
+ * back to cwd. Standardised here so the delegations md and the panel logger
848
+ * all read the SAME root (audit finding: the channels
849
+ * resolved the root independently). Pure — no I/O. */
850
+ function resolvePantheonRoot(state, cwd = process.cwd()) {
850
851
  const root = state?.directory ?? state?.worktree ?? "";
851
- if (root === "" || root === "/") return join(cwd, ".pantheon", "delegations");
852
- return join(root, ".pantheon", "delegations");
852
+ if (root === "" || root === "/") return cwd;
853
+ return root;
854
+ }
855
+ /** Resolve the directory where delegation md reports are written.
856
+ * The finalizer writes `.pantheon/delegations` RELATIVE to the server cwd,
857
+ * which the TUI exposes as `TuiState.path.directory`. */
858
+ function resolveDelegationsDir(state, cwd = process.cwd()) {
859
+ return join(resolvePantheonRoot(state, cwd), ".pantheon", "delegations");
853
860
  }
854
861
  /** Project root derived from the delegations dir: `<root>/.pantheon/delegations`
855
862
  * → `<root>`. Used to point the panel logger at the REAL hooks.log — passing
@@ -875,21 +882,24 @@ function compareDelegationEntries(a, b) {
875
882
  return (b.updatedAt ?? b.startedAt) - (a.updatedAt ?? a.startedAt);
876
883
  }
877
884
  /** Split the panel list: active jobs (running/retry, stale-marked) first,
878
- * then the most recent terminal reports, then the archived tail (paginated
879
- * in the View). Pure — so the history-only panel (no sessionID) is testable
885
+ * then the most recent terminal reports; the remaining tail is collapsed
886
+ * by the View into a single "… +N more" line. Pure — so the history-only panel (no sessionID) is testable
880
887
  * without the TUI runtime. */
881
888
  function splitDelegationList(all, maxRecent = 8, now = Date.now(), staleThresholdMs = 1800 * 1e3) {
882
- const active = all.filter((d) => d.state === "running" || d.state === "retry").map((d) => markStaleIfRunning(d, now, staleThresholdMs));
883
- const terminal = all.filter((d) => d.state !== "running" && d.state !== "retry");
889
+ const isActiveState = (st) => st === "running" || st === "retry" || st === "stale-running";
884
890
  return {
885
- active,
886
- recent: terminal.slice(0, maxRecent),
887
- archived: terminal.slice(maxRecent)
891
+ active: all.filter((d) => isActiveState(d.state)).map((d) => markStaleIfRunning(d, now, staleThresholdMs)).sort(compareDelegationEntries),
892
+ recent: all.filter((d) => !isActiveState(d.state)).filter((d) => {
893
+ if (d.updatedAt === null || !Number.isFinite(d.updatedAt)) return true;
894
+ const age = now - d.updatedAt;
895
+ if (age < 0) return true;
896
+ return age < (delegationRowStatus(d.state) === "failed" ? DELEGATION_FAILED_RETENTION_MS : DELEGATION_DONE_RETENTION_MS);
897
+ }).sort(compareDelegationEntries).slice(0, maxRecent)
888
898
  };
889
899
  }
890
- /** The list the panel actually renders (kept for the header count and
891
- * existing tests): active jobs first, then the most recent terminal
892
- * reports (capped) — the archived tail is rendered separately. Pure. */
900
+ /** Live-first window used by {@link ceilingDelegationList} (kept for the
901
+ * ceiling helper and existing tests): active jobs first, then the most
902
+ * recent terminal reports (capped). Pure. */
893
903
  function visibleDelegationList(all, maxTerminal = 8, now = Date.now(), staleThresholdMs = 1800 * 1e3) {
894
904
  const { active, recent } = splitDelegationList(all, maxTerminal, now, staleThresholdMs);
895
905
  return [...active, ...recent];
@@ -900,10 +910,37 @@ const STALE_RUNNING_THRESHOLD_MS = 1800 * 1e3;
900
910
  * considered stale. Combined with the stale-running threshold to produce the
901
911
  * display-only `stale-running` state. */
902
912
  const IDLE_SILENCE_MS = 60 * 1e3;
913
+ /** Visual-only terminal retention windows. Reports remain on disk; these
914
+ * constants only control which rows enter the TUI window. */
915
+ const DELEGATION_DONE_RETENTION_MS = 120 * 1e3;
916
+ const DELEGATION_FAILED_RETENTION_MS = 600 * 1e3;
917
+ /** Grace window for an ABSENT child status. `api.state.session.status()` only
918
+ * carries the sessions that are currently ALIVE; every finished child of a
919
+ * long-lived session is missing from that map. A child with no status is
920
+ * therefore treated as terminal (done) — treating it as running made every
921
+ * historical child of the session show up as "active" (the "197 children,
922
+ * all active" regression). The one exception is a freshly-spawned child that
923
+ * has not been registered by the status API yet: within this window of its
924
+ * last activity (`time.updated`, falling back to `time.created`) it still
925
+ * reads as running. */
926
+ const DELEGATION_CHILD_STATUS_GRACE_MS = 60 * 1e3;
927
+ /** Recency window for the children channel. `session.children` returns EVERY
928
+ * child the focused session ever spawned, so a long session accumulates
929
+ * hundreds of historical rows that would inflate the panel. A child whose
930
+ * last activity (its own time, or its matched report's Finalized timestamp)
931
+ * is older than this window is dropped BEFORE it can enter the list. 24h
932
+ * keeps the sidebar scoped to current work while still covering long
933
+ * delegations. A child with no timestamp at all is kept (fail-open). */
934
+ const DELEGATION_CHILDREN_RECENCY_MS = 1440 * 60 * 1e3;
935
+ /** Alias-less NATIVE task() live entries never receive a report alias (the
936
+ * task tool output carries none), so the 30s alias-less prune in
937
+ * mergeChildDelegationSources must not apply to them — 5 minutes covers a
938
+ * slow child listing while still bounding the live map. */
939
+ const NATIVE_LIVE_ALIASLESS_TTL_MS = 300 * 1e3;
903
940
  /**
904
941
  * Mark a running entry as `stale-running` if it has been running longer than
905
942
  * the threshold AND has no recent activity (no `updatedAt` change in the last
906
- * `IDLE_SILENCE_MS`). This is DISPLAY-ONLY — the board state is unchanged.
943
+ * `IDLE_SILENCE_MS`). This is DISPLAY-ONLY — the persisted state is unchanged.
907
944
  *
908
945
  * A `stale-running` entry renders with a warning indicator but the underlying
909
946
  * delegation is still treated as running by the backend.
@@ -917,7 +954,7 @@ function markStaleIfRunning(entry, now, thresholdMs = STALE_RUNNING_THRESHOLD_MS
917
954
  state: "stale-running"
918
955
  };
919
956
  }
920
- /** Compact elapsed-time label: "5m 12s", "1h 30m", "2d 4h" — ticks every
957
+ /** Compact elapsed-time label, single unit only: "12s"/"3m"/"1h"/"2d" — ticks every
921
958
  * second for running jobs. */
922
959
  function fmtElapsed(ms) {
923
960
  const total = Math.max(0, Math.floor(ms / 1e3));
@@ -925,15 +962,16 @@ function fmtElapsed(ms) {
925
962
  const hours = Math.floor(total % 86400 / 3600);
926
963
  const minutes = Math.floor(total % 3600 / 60);
927
964
  const seconds = total % 60;
928
- if (days > 0) return `${days}d ${hours}h`;
929
- if (hours > 0) return `${hours}h ${minutes}m`;
930
- if (minutes > 0) return `${minutes}m ${seconds}s`;
965
+ if (days > 0) return `${days}d`;
966
+ if (hours > 0) return `${hours}h`;
967
+ if (minutes > 0) return `${minutes}m`;
931
968
  return `${seconds}s`;
932
969
  }
933
- /** Elapsed label for one entry: running → ticks `now - startedAt`, terminal
934
- * → fixed `updatedAt - startedAt` (em dash when no finalized timestamp). */
970
+ /** Elapsed label for one entry: an ACTIVE entry (running/retry/stale-running)
971
+ * ticks `now - startedAt`; a terminal one is fixed at
972
+ * `updatedAt - startedAt` (em dash when no finalized timestamp). */
935
973
  function delegationElapsed(entry, now) {
936
- if (entry.state === "running" || entry.state === "stale-running") return fmtElapsed(now - entry.startedAt);
974
+ if (entry.state === "running" || entry.state === "retry" || entry.state === "stale-running") return fmtElapsed(now - entry.startedAt);
937
975
  return entry.updatedAt !== null ? fmtElapsed(entry.updatedAt - entry.startedAt) : "—";
938
976
  }
939
977
  /** The activity labels shown by the animated row. Keeping this pure makes the
@@ -969,18 +1007,42 @@ const DELEGATION_SPINNER_FRAMES = [
969
1007
  "⠇",
970
1008
  "⠏"
971
1009
  ];
972
- /** Return a deterministic spinner frame. The View ticks this every 140ms. */
973
- function delegationSpinnerFrame(now) {
974
- const index = Math.floor(Math.max(0, now) / 140) % DELEGATION_SPINNER_FRAMES.length;
1010
+ /** Return the spinner glyph for the given frame counter. The frame counter
1011
+ * is incremented every ~80ms by a dedicated animation timer, decoupled from
1012
+ * the data poll cycle. Pure — no side effects. */
1013
+ function delegationSpinnerFrame(frame) {
1014
+ const index = Math.abs(Math.floor(frame)) % DELEGATION_SPINNER_FRAMES.length;
975
1015
  return DELEGATION_SPINNER_FRAMES[index] ?? DELEGATION_SPINNER_FRAMES[0];
976
1016
  }
1017
+ /** Every state the row knows how to draw: the delegation lifecycle states
1018
+ * derived from the children status + md reports (`completed`, `error`,
1019
+ * `cancelled`, `startup_failed`, `startup_unknown`) plus the TUI-only
1020
+ * states (`retry`, `stale-running`). Fase 1 deliberately omits speculative
1021
+ * blocked/paused/scheduled/skipped. There is no `pending` display state: a
1022
+ * pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
1023
+ /** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
1024
+ * Kept separate + pure so the color channel is testable without booting the
1025
+ * renderer. Every display state resolves to one of the three status colors;
1026
+ * the row paints its whole content with it (see {@link DelegationRow}). */
1027
+ /** Status → color mapping for the whole-row tone. Red = failure, green =
1028
+ * terminal, yellow = in flight. The glyph remains a redundant channel so the
1029
+ * state stays legible in monochrome. Display-only; no behavior change. */
1030
+ function delegationStateTone(state) {
1031
+ switch (state) {
1032
+ case "running":
1033
+ case "retry":
1034
+ case "startup_unknown":
1035
+ case "stale-running": return "warning";
1036
+ case "error":
1037
+ case "startup_failed": return "error";
1038
+ case "completed":
1039
+ case "cancelled": return "success";
1040
+ }
1041
+ }
977
1042
  /** Bounded tracker: child session id → latest running tool call. */
978
1043
  const latestToolActivity = /* @__PURE__ */ new Map();
979
1044
  const MAX_TOOL_ACTIVITY_ENTRIES = 200;
980
1045
  const TOOL_ACTIVITY_TTL_MS = 300 * 1e3;
981
- /** Archived-section pagination state — module-level so it survives sidebar
982
- * remounts (route changes) without leaking into the component tree. */
983
- const archivedUiState = { page: 0 };
984
1046
  /** First usable summary string from common tool input fields. */
985
1047
  function summarizeToolInput(input) {
986
1048
  if (!input) return "";
@@ -1049,8 +1111,11 @@ function mergeChildDelegationSources(children, live) {
1049
1111
  bySessionAlias.set(key(entry.sessionID, entry.alias), index);
1050
1112
  });
1051
1113
  for (const liveEntry of live) {
1052
- if (liveEntry.alias === null && liveEntry.taskID === null && Date.now() - liveEntry.startedAt > 3e4) continue;
1114
+ const aliaslessTTL = liveEntry.tool === "task" ? NATIVE_LIVE_ALIASLESS_TTL_MS : 3e4;
1115
+ if (liveEntry.alias === null && liveEntry.taskID === null && Date.now() - liveEntry.startedAt > aliaslessTTL) continue;
1053
1116
  const incoming = toDelegationEntry(liveEntry);
1117
+ const isNativeLive = liveEntry.tool === "task" && liveEntry.alias === null;
1118
+ if (isNativeLive) incoming.source = "children-only";
1054
1119
  const index = (incoming.taskID !== void 0 ? byTask.get(incoming.taskID) : void 0) ?? (incoming.alias !== "" ? bySessionAlias.get(key(incoming.sessionID, incoming.alias)) : void 0);
1055
1120
  if (index === void 0) {
1056
1121
  result.push(incoming);
@@ -1078,7 +1143,7 @@ function mergeChildDelegationSources(children, live) {
1078
1143
  startedAt: Math.min(existing.startedAt, incoming.startedAt),
1079
1144
  updatedAt: incoming.updatedAt,
1080
1145
  read: incoming.read,
1081
- source: "live"
1146
+ source: isNativeLive ? existing.source : "live"
1082
1147
  };
1083
1148
  }
1084
1149
  result.sort(compareDelegationEntries);
@@ -1095,10 +1160,12 @@ const DELEGATE_TASKID_PATTERN = /\(task\s+([a-z0-9_]+)\)/i;
1095
1160
  const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i;
1096
1161
  /** Extract the tool name + args from a `message.part.updated` part and
1097
1162
  * reduce it to what the panel needs. Returns null for anything that is
1098
- * not a pantheon delegation tool part (or is missing its callID). */
1163
+ * not a pantheon delegation tool part, the native `task` subagent tool
1164
+ * (same parentID === caller mechanism — its children render `nat:`),
1165
+ * or is missing its callID. */
1099
1166
  function parseDelegationToolPart(part, now = Date.now()) {
1100
1167
  if (part.type !== "tool") return null;
1101
- if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read") return null;
1168
+ if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read" && part.tool !== "task") return null;
1102
1169
  const callID = part.callID;
1103
1170
  if (callID === void 0 || callID === "") return null;
1104
1171
  const sessionID = part.sessionID ?? "";
@@ -1130,8 +1197,8 @@ function parseDelegationToolPart(part, now = Date.now()) {
1130
1197
  endAt
1131
1198
  };
1132
1199
  }
1133
- const agent = typeof input.agent === "string" ? input.agent : "agent";
1134
- const description = typeof input.description === "string" ? input.description : "";
1200
+ const agent = typeof input.agent === "string" ? input.agent : typeof input.subagent_type === "string" ? input.subagent_type : "agent";
1201
+ const description = typeof input.description === "string" ? input.description : typeof input.prompt === "string" ? input.prompt.slice(0, 120) : "";
1135
1202
  let alias = null;
1136
1203
  let taskID = null;
1137
1204
  if (status === "completed") {
@@ -1143,7 +1210,7 @@ function parseDelegationToolPart(part, now = Date.now()) {
1143
1210
  callID,
1144
1211
  partID: part.id ?? "",
1145
1212
  sessionID,
1146
- tool: "pantheon_delegate",
1213
+ tool: part.tool === "task" ? "task" : "pantheon_delegate",
1147
1214
  agent,
1148
1215
  description,
1149
1216
  status,
@@ -1192,7 +1259,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
1192
1259
  callID: parsed.callID,
1193
1260
  partID: parsed.partID,
1194
1261
  sessionID: parsed.sessionID,
1195
- tool: "pantheon_delegate",
1262
+ tool: parsed.tool,
1196
1263
  agent: parsed.agent ?? "agent",
1197
1264
  description: parsed.description,
1198
1265
  alias: existing?.alias ?? null,
@@ -1209,7 +1276,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
1209
1276
  callID: parsed.callID,
1210
1277
  partID: parsed.partID,
1211
1278
  sessionID: parsed.sessionID,
1212
- tool: "pantheon_delegate",
1279
+ tool: parsed.tool,
1213
1280
  agent: parsed.agent ?? "agent",
1214
1281
  description: parsed.description,
1215
1282
  alias: parsed.alias,
@@ -1254,11 +1321,14 @@ function removeDelegationEntry(map, partIDOrCallID) {
1254
1321
  }
1255
1322
  return false;
1256
1323
  }
1257
- /** Collect pantheon delegation tool parts from a session's messages.
1324
+ /** Collect pantheon delegation + native task tool parts from a session's messages.
1258
1325
  * Messages may carry their parts inline (duck-typed `msg.parts`); when
1259
1326
  * they don't, the optional `getParts(messageID)` callback is used (the TUI
1260
- * SDK exposes `api.state.part(messageID)`). Pure w.r.t. I/O — used by the
1261
- * mount re-scan to re-seed the live map after compaction/attach. */
1327
+ * SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
1328
+ * child session with parentID = caller — the same mechanism as
1329
+ * pantheon_delegate — so its parts feed the live-map as the native signal
1330
+ * (rows come from the children channel). Pure w.r.t. I/O — used
1331
+ * by the mount re-scan to re-seed the live map after compaction/attach. */
1262
1332
  function collectDelegationToolParts(messages, getParts) {
1263
1333
  const out = [];
1264
1334
  for (const msg of messages ?? []) {
@@ -1268,7 +1338,7 @@ function collectDelegationToolParts(messages, getParts) {
1268
1338
  if (!parts) continue;
1269
1339
  for (const raw of parts) {
1270
1340
  const part = raw;
1271
- if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read")) out.push(part);
1341
+ if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read" || part.tool === "task")) out.push(part);
1272
1342
  }
1273
1343
  }
1274
1344
  return out;
@@ -1328,6 +1398,26 @@ function mergeDelegationSources(live, md) {
1328
1398
  all.sort(compareDelegationEntries);
1329
1399
  return all;
1330
1400
  }
1401
+ /** Scope a fully-merged display list to the ACTIVE session only.
1402
+ *
1403
+ * The panel is session-scoped: rows from other sessions (md history under
1404
+ * `.pantheon/delegations/<other-session>/`) and rows with no
1405
+ * attributable session (empty sessionID) are DROPPED. The previous
1406
+ * cross-session behavior rendered those rows and clicking them led to
1407
+ * "Session not found" — there is no cross-session channel anymore.
1408
+ *
1409
+ * The md channel still feeds the merge UNFILTERED so it can ENRICH an
1410
+ * active-session row with state/alias/agent (dedup by taskID), but it can
1411
+ * never introduce a row for another session: this filter is applied
1412
+ * ONCE, after every merge (children + live + md).
1413
+ *
1414
+ * Returns [] when no active session resolves (null/placeholder) — there is no
1415
+ * scope to show. Native children always carry the active session id (stamped
1416
+ * by `childrenToDelegationEntries`), so they survive the filter. Pure. */
1417
+ function filterDelegationsToSession(entries, activeSessionID) {
1418
+ if (!isValidSessionId(activeSessionID)) return [];
1419
+ return entries.filter((entry) => entry.sessionID === activeSessionID);
1420
+ }
1331
1421
  /** Server-aligned session id validity: opencode rejects anything not starting
1332
1422
  * with "ses" (SchemaError). This deliberately mirrors that exact contract —
1333
1423
  * nothing stricter, nothing looser — so a template placeholder ("{sessionID}"),
@@ -1357,41 +1447,216 @@ function resolveCurrentSessionID(sources) {
1357
1447
  return null;
1358
1448
  }
1359
1449
  /** THE single choke point for every `session.children` / session-API path.
1360
- * Returns `{ path: { id } }` ONLY for a server-valid session id; returns
1361
- * null for anything else (placeholder, empty, foreign id) so the caller
1362
- * skips the call entirely instead of sending an unsubstituted placeholder
1363
- * (the "%7BsessionID%7D" regression). Every session-API call site MUST go
1364
- * through this function (enforced by the source-scan test in
1365
- * tests/pantheon/tui-delegations.test.ts). */
1450
+ * Returns the v2 SDK parameter shape `{ sessionID }` ONLY for a
1451
+ * server-valid session id; returns null for anything else (placeholder,
1452
+ * empty, foreign id) so the caller skips the call entirely instead of
1453
+ * sending an unsubstituted placeholder (the "%7BsessionID%7D" regression).
1454
+ * The TUI client is `@opencode-ai/sdk/v2`, whose session methods take a
1455
+ * FLAT parameter object (`{ sessionID }`), NOT the v1 `{ path: { id } }`
1456
+ * envelope — passing the v1 shape left the v2 `{sessionID}` URL template
1457
+ * unsubstituted (`/session/%7BsessionID%7D/children`). Every session-API
1458
+ * call site MUST go through this function (enforced by the source-scan
1459
+ * test in tests/pantheon/tui-delegations.test.ts). */
1366
1460
  function safeSessionPath(id) {
1367
1461
  if (!isValidSessionId(id)) return null;
1368
- return { path: { id } };
1462
+ return { sessionID: id };
1369
1463
  }
1370
- /** Build the `session.children` path ONLY from a validated session id.
1464
+ /** Build the `session.children` parameters ONLY from a validated session id.
1371
1465
  * Delegates to {@link safeSessionPath} — the single choke point. Returns
1372
- * null for null/invalid ids so the caller skips the fetch instead of
1373
- * sending an unsubstituted placeholder (the "%7BsessionID%7D" regression). */
1466
+ * the v2 SDK shape `{ sessionID }`; null for null/invalid ids so the caller
1467
+ * skips the fetch instead of sending an unsubstituted placeholder (the
1468
+ * "%7BsessionID%7D" regression). */
1374
1469
  function buildChildrenPath(id) {
1375
1470
  return safeSessionPath(id);
1376
1471
  }
1377
1472
  /** Duck-typed subset of a child Session (+ its live status type). */
1378
- /** Map a child status type to a display state. busy/retry → running
1379
- * (the child is actively working), idle → completed, unknown → running
1380
- * (fail-open: a freshly-seen child is assumed active; the 1s poll + md
1381
- * correct it as soon as terminal data exists). */
1382
- function childStatusToState(status) {
1383
- if (status === "idle") return "completed";
1473
+ /** Map a child status type to a display state.
1474
+ *
1475
+ * Only `busy`/`retry` are EXPLICIT live states. `idle` is terminal and so is
1476
+ * any other/unknown status — a status the panel does not recognise is not
1477
+ * evidence of activity. An ABSENT status is the historical case: the status
1478
+ * map (`api.state.session.status`) contains only currently-alive sessions, so
1479
+ * every finished child of a long session is missing from it. Defaulting that
1480
+ * to running made the whole session history render as "active" (the
1481
+ * "197 children, all active" regression), so an absent status is terminal
1482
+ * (done) UNLESS the child was active within `graceMs` — a just-spawned child
1483
+ * that the status API has not registered yet must still render as running.
1484
+ *
1485
+ * @param status the `api.state.session.status(id)?.type`, or undefined
1486
+ * @param time the child session time fields (`created`/`updated`)
1487
+ * @param now current wall-clock ms (injectable for tests)
1488
+ * @param graceMs recency window for the absent-status running assumption
1489
+ * Pure — no I/O. */
1490
+ function childStatusToState(status, time, now = Date.now(), graceMs = DELEGATION_CHILD_STATUS_GRACE_MS) {
1384
1491
  if (status === "retry") return "retry";
1385
- return "running";
1492
+ if (status === "busy") {
1493
+ if (time) {
1494
+ const lastActivity = Math.max(time.updated ?? 0, time.created ?? 0);
1495
+ if (lastActivity > 0 && now - lastActivity > 18e5) return "completed";
1496
+ }
1497
+ return "running";
1498
+ }
1499
+ if (status !== void 0) return "completed";
1500
+ const lastActivity = Math.max(time?.updated ?? 0, time?.created ?? 0);
1501
+ if (lastActivity > 0 && now - lastActivity < graceMs) return "running";
1502
+ return "completed";
1503
+ }
1504
+ /** Status-only row model: ONE glyph + ONE identity per row.
1505
+ *
1506
+ * A row is
1507
+ * `{glyph} {alias:7} {elapsed:>5}` plus a muted description line. The short
1508
+ * alias (`apo-1`) is the single identity; rows without a report alias show
1509
+ * the agent instead (never both). */
1510
+ /** Map every FSM/display state to one of the 4 row kinds. reconciled never
1511
+ * reaches the panel (the md parser drops it) but reads as done;
1512
+ * cancelled reads as done; startup_failed reads as failed while
1513
+ * startup_unknown (no error known) reads as retry. Pure. */
1514
+ function delegationRowStatus(state) {
1515
+ switch (state) {
1516
+ case "running":
1517
+ case "stale-running": return "active";
1518
+ case "retry":
1519
+ case "startup_unknown": return "retry";
1520
+ case "completed":
1521
+ case "cancelled": return "done";
1522
+ case "error":
1523
+ case "startup_failed": return "failed";
1524
+ }
1525
+ }
1526
+ /** The only 4 glyphs a row may show: animated active, done, failed, retry.
1527
+ * Shape is redundant with color (never the only signal). Pure. */
1528
+ const DELEGATION_ROW_GLYPHS = {
1529
+ active: "⠋",
1530
+ done: "✓",
1531
+ failed: "✕",
1532
+ retry: "⟳"
1533
+ };
1534
+ function delegationRowGlyph(status) {
1535
+ return DELEGATION_ROW_GLYPHS[status];
1536
+ }
1537
+ /** Row marker (`<glyph> `) — the single state channel. `active` animates
1538
+ * through the spinner for the given frame counter (incremented every ~80ms);
1539
+ * every other kind is static. Pure. */
1540
+ function delegationRowMarker(state, frame = 0) {
1541
+ const status = delegationRowStatus(state);
1542
+ return `${status === "active" ? delegationSpinnerFrame(frame) : delegationRowGlyph(status)} `;
1543
+ }
1544
+ /** ONE identity per row: the short report alias (`apo-1`); a row without a
1545
+ * report alias (native task() child `native-task` / `native-<id>`, or an
1546
+ * alias-less native live row `live-<callID>` kept as children-only) shows
1547
+ * the agent instead — never alias + agent stacked. Pure. */
1548
+ function delegationRowIdentity(entry) {
1549
+ if (entry.agent === "") return entry.alias;
1550
+ if (entry.alias === "native-task" || entry.alias.startsWith("native-")) return entry.agent;
1551
+ if (entry.alias.startsWith("live-") && entry.source === "children-only") return entry.agent;
1552
+ return entry.alias;
1553
+ }
1554
+ /** Fixed alias cell, pad-right + hard-truncate to `width` (default 7) so
1555
+ * identities line up across rows. Pure. */
1556
+ const DELEGATION_ALIAS_WIDTH = 7;
1557
+ function formatDelegationAlias(identity, width = 7) {
1558
+ if (identity.length >= width) return identity.slice(0, width);
1559
+ return `${identity}${" ".repeat(width - identity.length)}`;
1560
+ }
1561
+ /** Max description width on the row detail line — the old 180-char slice
1562
+ * wrapped the sidebar; 44 keeps one readable line. */
1563
+ const DELEGATION_DESCRIPTION_MAX = 44;
1564
+ /** Lazily-built grapheme segmenter. `Intl.Segmenter` keeps ZWJ emoji families
1565
+ * (and skin-tone/variation sequences) intact; `Array.from` (code points) is
1566
+ * the fallback for runtimes without it. */
1567
+ let graphemeSegmenter;
1568
+ /** Split `text` into display graphemes so truncation never cuts a multi-unit
1569
+ * emoji in half (surrogate pair → mojibake). */
1570
+ function splitGraphemes(text) {
1571
+ if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
1572
+ graphemeSegmenter ??= new Intl.Segmenter("pt", { granularity: "grapheme" });
1573
+ return Array.from(graphemeSegmenter.segment(text), (s) => s.segment);
1574
+ }
1575
+ return Array.from(text);
1576
+ }
1577
+ /** Truncate a description to `max` graphemes appending `…` when cut. Exact
1578
+ * `max`-length text is left untouched. Grapheme-granular, so an emoji made of
1579
+ * several code points is never split into mojibake at the boundary. Pure. */
1580
+ function truncateDelegationDescription(text, max = 44) {
1581
+ const graphemes = splitGraphemes(text);
1582
+ if (graphemes.length <= max) return text;
1583
+ return `${graphemes.slice(0, Math.max(0, max - 1)).join("")}\u2026`;
1584
+ }
1585
+ /** Fixed elapsed-time box: right-aligned to `width` (default 5) so elapsed
1586
+ * values line up across rows, e.g. ` 12s`. A label that already fills
1587
+ * the box is returned untouched. Pure. */
1588
+ const DELEGATION_ELAPSED_WIDTH = 5;
1589
+ function formatDelegationElapsed(entry, now, width = 5) {
1590
+ const label = delegationElapsed(entry, now);
1591
+ if (label.length >= width) return label;
1592
+ return `${" ".repeat(width - label.length)}${label}`;
1593
+ }
1594
+ /** Colored left half of a row line: `<marker><alias:7> ` — the state glyph
1595
+ * plus the single identity, padded so the muted elapsed column aligns. The
1596
+ * row renders this colored lead separately from the muted elapsed tail. Pure. */
1597
+ function formatDelegationRowLead(entry, marker) {
1598
+ return `${marker}${formatDelegationAlias(delegationRowIdentity(entry))} `;
1386
1599
  }
1387
- /** Row tag for a delegation entry: `[native]` for native task() children
1388
- * (source 'children-only' — no board report), `[pantheon:<alias>]` for board
1389
- * rows ([pantheon:apo-1]) — the same tags the manager prints in
1390
- * pantheon_delegation_list, so panel rows and CLI list lines match. The
1391
- * panel renders the tag with a distinct style so native task() children are
1392
- * visually separable from pantheon_delegate jobs. */
1393
- function delegationTag(entry) {
1394
- return entry.source === "children-only" ? "[native]" : `[pantheon:${entry.alias}]`;
1600
+ /** Visible-row ceiling for the panel: the remainder collapses into a single
1601
+ * "… +N more" line. Live rows render first, so a running job is never hidden
1602
+ * by the cap. */
1603
+ const DELEGATION_VISIBLE_CEILING = 8;
1604
+ /** Header summary: `(N active · M done)` plus `· K failed` only when K > 0.
1605
+ * Active counts running/retry AND display-only stale-running (a stale row is
1606
+ * still a live job — it must never read as done); failed counts
1607
+ * error/startup_failed; cancelled reads as done. Pure. */
1608
+ function formatDelegationHeader(entries) {
1609
+ let active = 0;
1610
+ let done = 0;
1611
+ let failed = 0;
1612
+ for (const e of entries) switch (delegationRowStatus(e.state)) {
1613
+ case "active":
1614
+ case "retry":
1615
+ active++;
1616
+ break;
1617
+ case "failed":
1618
+ failed++;
1619
+ break;
1620
+ case "done":
1621
+ done++;
1622
+ break;
1623
+ }
1624
+ const tail = failed > 0 ? ` · ${failed} failed` : "";
1625
+ return `(${active} active · ${done} done${tail})`;
1626
+ }
1627
+ /** Cap the panel: live rows first, then most-recent retained terminal rows, at
1628
+ * most `maxVisible` total. Hidden counts describe the retained render list,
1629
+ * not expired history. */
1630
+ function ceilingDelegationList(all, maxVisible = 8, now = Date.now()) {
1631
+ const { active, recent } = splitDelegationList(all, all.length, now);
1632
+ const visibleActive = active.slice(0, maxVisible);
1633
+ const visible = [...visibleActive, ...recent.slice(0, Math.max(0, maxVisible - visibleActive.length))];
1634
+ const hiddenActive = Math.max(0, active.length - visibleActive.length);
1635
+ const hiddenTerminal = Math.max(0, recent.length - (visible.length - visibleActive.length));
1636
+ return {
1637
+ visible,
1638
+ hidden: hiddenActive + hiddenTerminal,
1639
+ hiddenActive,
1640
+ hiddenTerminal
1641
+ };
1642
+ }
1643
+ /** Split a display list into native task() rows vs pantheon_delegate rows.
1644
+ * Native = source 'children-only' (no delegate report); everything else counts
1645
+ * as pantheon. Pure — powers the hooks.log line. */
1646
+ function countDelegationSources(entries) {
1647
+ let native = 0;
1648
+ for (const e of entries) if (e.source === "children-only") native++;
1649
+ return {
1650
+ native,
1651
+ pantheon: entries.length - native,
1652
+ total: entries.length
1653
+ };
1654
+ }
1655
+ /** Diagnostic hooks.log line for a panel re-fetch, with the children
1656
+ * breakdown (pantheon = children WITH a delegate report, native = children
1657
+ * WITHOUT one). Pure — the View logs the returned string verbatim. */
1658
+ function formatPanelLogLine(children, pantheon, native, md, events) {
1659
+ return `panel: children=${children}(pantheon=${pantheon} native=${native}) md=${md} events=${events}`;
1395
1660
  }
1396
1661
  /** Turn child sessions (PRIMARY) enriched with md reports into the display
1397
1662
  * list. One entry per child id (duplicates across re-fetches collapse).
@@ -1401,12 +1666,38 @@ function delegationTag(entry) {
1401
1666
  * itself (fallback 'agent'), state derived from its status, startedAt from
1402
1667
  * time.created. A report-less child is a NATIVE task() child (every
1403
1668
  * child of the current session — pantheon_delegate OR the native `task()`
1404
- * tool — carries parentID = caller), so it gets source 'children-only'
1405
- * and the `[native]` tag instead of a board alias.
1406
- * Terminal md state wins over the derived state; a running md defers to
1407
- * the child's live status. Sorted running-first (compareDelegationEntries).
1669
+ * tool — carries parentID = caller), so it gets source 'children-only', a
1670
+ * per-child alias 'native-<last4 of the child id>' (one identity per native
1671
+ * row) instead of a report alias. The 'task nativa' description fallback
1672
+ * keeps the row non-empty when the child carries no title.
1673
+ * A FRESH terminal md state wins over the derived state (a stale md report
1674
+ * from a previous incarnation never flips a live child — see
1675
+ * mdTerminalCoversChild); a running md defers to
1676
+ * the child's live status. A running child is NEVER archived — it always
1677
+ * lands in the active split (splitDelegationList active = running/retry).
1678
+ * `parentSessionID` (the focused session id) is stamped on report-less
1679
+ * children so the row carries its origin session; omit it and they stay
1680
+ * unscoped ('').
1681
+ * Children outside {@link DELEGATION_CHILDREN_RECENCY_MS} are dropped before
1682
+ * they can enter the list (the per-session child list grows for the whole
1683
+ * session lifetime); the survivors are sorted running-first, most recent
1684
+ * first (compareDelegationEntries), so a ceiling hides the OLDEST rows.
1408
1685
  * Pure — no I/O. */
1409
- function childrenToDelegationEntries(children, md, now = Date.now()) {
1686
+ /** Temporal guard for the children channel (same recycled-alias class as the
1687
+ * md merge): an md terminal report matched by taskID may still belong to
1688
+ * a PREVIOUS job incarnation. Accept it only when its Finalized timestamp
1689
+ * covers the child's latest activity, when either side has no timestamp to
1690
+ * compare, or when both startedAt agree on a single job incarnation.
1691
+ * Pure — no I/O. */
1692
+ function mdTerminalCoversChild(md, child) {
1693
+ const childUpdated = child.time?.updated;
1694
+ if (md.updatedAt !== null && childUpdated !== void 0) {
1695
+ if (md.updatedAt >= childUpdated) return true;
1696
+ return md.startedAt === child.time?.created;
1697
+ }
1698
+ return true;
1699
+ }
1700
+ function childrenToDelegationEntries(children, md, now = Date.now(), parentSessionID = "") {
1410
1701
  const byTaskID = /* @__PURE__ */ new Map();
1411
1702
  for (const m of md) if (m.taskID !== void 0 && !byTaskID.has(m.taskID)) byTaskID.set(m.taskID, m);
1412
1703
  const out = [];
@@ -1415,17 +1706,19 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
1415
1706
  if (child.id === "" || seen.has(child.id)) continue;
1416
1707
  seen.add(child.id);
1417
1708
  const mdEntry = byTaskID.get(child.id);
1418
- const state = mdEntry !== void 0 && mdEntry.state !== "running" ? mdEntry.state : childStatusToState(child.status);
1709
+ const lastActivity = Math.max(child.time?.updated ?? 0, child.time?.created ?? 0, mdEntry?.updatedAt ?? 0);
1710
+ if (lastActivity > 0 && now - lastActivity > 864e5) continue;
1711
+ const state = mdEntry !== void 0 && mdEntry.state !== "running" && mdTerminalCoversChild(mdEntry, child) ? mdEntry.state : childStatusToState(child.status, child.time, now);
1419
1712
  out.push({
1420
- alias: mdEntry?.alias ?? "task",
1421
- sessionID: mdEntry?.sessionID ?? "",
1713
+ alias: mdEntry?.alias ?? `native-${child.id.slice(-4)}`,
1714
+ sessionID: mdEntry?.sessionID ?? parentSessionID,
1422
1715
  taskID: child.id,
1423
1716
  agent: mdEntry?.agent ?? child.agent ?? "agent",
1424
1717
  state,
1425
1718
  startedAt: mdEntry?.startedAt ?? child.time?.created ?? now,
1426
1719
  updatedAt: mdEntry?.updatedAt ?? (state === "running" || state === "stale-running" ? null : child.time?.updated ?? null),
1427
1720
  timedOut: mdEntry?.timedOut ?? false,
1428
- description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title ?? "",
1721
+ description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title !== void 0 && child.title !== "" ? child.title : "task nativa",
1429
1722
  source: mdEntry !== void 0 ? "md" : "children-only"
1430
1723
  });
1431
1724
  }
@@ -1439,8 +1732,19 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
1439
1732
  * unsubstituted "{sessionID}" placeholder can never be routed. */
1440
1733
  function navigateToDelegationSession(route, taskID) {
1441
1734
  if (typeof route?.navigate !== "function" || !isValidSessionId(taskID)) return false;
1442
- route.navigate("session", { sessionID: taskID });
1443
- return true;
1735
+ try {
1736
+ const navigation = route.navigate("session", { sessionID: taskID });
1737
+ if (navigation !== void 0) Promise.resolve(navigation).catch(() => void 0);
1738
+ return true;
1739
+ } catch {
1740
+ return false;
1741
+ }
1742
+ }
1743
+ /** Build the mouse handler used by each delegation row. */
1744
+ function createDelegationRowOpenHandler(route, taskID) {
1745
+ return () => {
1746
+ navigateToDelegationSession(route, taskID);
1747
+ };
1444
1748
  }
1445
1749
  /** Silence-by-default panel logger.
1446
1750
  * @param projectRoot the PROJECT ROOT — the logger appends to
@@ -1468,7 +1772,11 @@ function createTuiLogger(projectRoot, module = "pantheon-tui") {
1468
1772
  await mkdir(dirname(logPath), { recursive: true });
1469
1773
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
1470
1774
  await appendFile(logPath, `${lines.map((l) => `[${stamp}] [${module}] ${l}`).join("\n")}\n`, "utf8");
1471
- } catch {}
1775
+ } catch (err) {
1776
+ if (echo) try {
1777
+ process.stderr.write(`[${module}] file-log failed: ${formatArg(err)}\n`);
1778
+ } catch {}
1779
+ }
1472
1780
  })();
1473
1781
  if (echo) for (const line of lines) console.log(`[${module}] ${line}`);
1474
1782
  };
@@ -1500,72 +1808,61 @@ function SessionRow(props) {
1500
1808
  }
1501
1809
  function DelegationRow(props) {
1502
1810
  const theme = () => props.api.theme.current;
1503
- const color = createMemo(() => {
1811
+ const tone = createMemo(() => {
1504
1812
  const t = theme();
1505
- switch (props.job.state) {
1506
- case "running": return t.warning;
1507
- case "retry": return t.warning;
1508
- case "stale-running": return t.error;
1509
- case "completed": return t.success;
1813
+ switch (delegationStateTone(props.job.state)) {
1814
+ case "warning": return t.warning;
1510
1815
  case "error": return t.error;
1816
+ case "success": return t.success;
1511
1817
  default: return t.textMuted;
1512
1818
  }
1513
1819
  });
1514
- const marker = createMemo(() => {
1515
- if (props.job.state === "running") return `${delegationSpinnerFrame(props.animationNow)} `;
1516
- if (props.job.state === "retry") return "⟳ ";
1517
- if (props.job.state === "stale-running") return "⚠ ";
1518
- switch (props.job.state) {
1519
- case "completed": return "✓ ";
1520
- case "error": return "✕ ";
1521
- default: return "○ ";
1522
- }
1523
- });
1524
- const tagColor = createMemo(() => props.job.source === "children-only" ? theme().info : color());
1525
- const detail = createMemo(() => {
1526
- const line = `${delegationElapsed(props.job, props.now)}${props.job.description !== "" ? ` \u2014 ${props.job.description}` : ""}`;
1527
- return line.length > 180 ? `${line.slice(0, 177)}\u2026` : line;
1528
- });
1820
+ const marker = createMemo(() => delegationRowMarker(props.job.state, props.frame));
1821
+ const lead = createMemo(() => formatDelegationRowLead(props.job, marker()));
1822
+ const elapsed = createMemo(() => formatDelegationElapsed(props.job, props.now));
1823
+ const description = createMemo(() => truncateDelegationDescription(props.job.description));
1529
1824
  const activity = createMemo(() => latestToolActivityFor(latestToolActivity, props.job.taskID, props.now));
1530
- const open = () => {
1531
- navigateToDelegationSession(props.api.route, props.job.taskID);
1532
- };
1825
+ const open = createDelegationRowOpenHandler(props.api.route, props.job.taskID);
1533
1826
  return (() => {
1534
- var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("text"), _el$17 = createElement("text"), _el$18 = createElement("text");
1827
+ var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("span"), _el$17 = createElement("span");
1535
1828
  insertNode(_el$13, _el$14);
1536
- insertNode(_el$13, _el$18);
1537
1829
  setProp(_el$13, "onMouseDown", open);
1538
1830
  insertNode(_el$14, _el$15);
1539
- insertNode(_el$14, _el$16);
1540
- insertNode(_el$14, _el$17);
1541
1831
  setProp(_el$14, "flexDirection", "row");
1542
- insert(_el$15, marker);
1543
- insert(_el$16, () => delegationTag(props.job));
1544
- insert(_el$17, () => ` ${props.job.agent} \u2014 ${delegationActivityLabel(props.job)}`);
1545
- insert(_el$18, detail);
1832
+ insertNode(_el$15, _el$16);
1833
+ insertNode(_el$15, _el$17);
1834
+ insert(_el$16, lead);
1835
+ insert(_el$17, elapsed);
1836
+ insert(_el$13, createComponent(Show, {
1837
+ get when() {
1838
+ return description() !== "";
1839
+ },
1840
+ get children() {
1841
+ var _el$18 = createElement("text");
1842
+ insert(_el$18, () => ` ${description()}`);
1843
+ effect((_$p) => setProp(_el$18, "fg", tone(), _$p));
1844
+ return _el$18;
1845
+ }
1846
+ }), null);
1546
1847
  insert(_el$13, createComponent(Show, {
1547
1848
  get when() {
1548
1849
  return activity();
1549
1850
  },
1550
1851
  children: (a) => (() => {
1551
1852
  var _el$19 = createElement("text");
1552
- insert(_el$19, () => ` \u21b3 ${a().tool}${a().summary !== "" ? " " + a().summary : ""}`);
1553
- effect((_$p) => setProp(_el$19, "fg", theme().textMuted, _$p));
1853
+ insert(_el$19, () => ` ↳ ${truncateDelegationDescription(`${a().tool}${a().summary !== "" ? ` ${a().summary}` : ""}`)}`);
1854
+ effect((_$p) => setProp(_el$19, "fg", tone(), _$p));
1554
1855
  return _el$19;
1555
1856
  })()
1556
1857
  }), null);
1557
1858
  effect((_p$) => {
1558
- var _v$5 = color(), _v$6 = tagColor(), _v$7 = color(), _v$8 = theme().textMuted;
1559
- _v$5 !== _p$.e && (_p$.e = setProp(_el$15, "fg", _v$5, _p$.e));
1560
- _v$6 !== _p$.t && (_p$.t = setProp(_el$16, "fg", _v$6, _p$.t));
1561
- _v$7 !== _p$.a && (_p$.a = setProp(_el$17, "fg", _v$7, _p$.a));
1562
- _v$8 !== _p$.o && (_p$.o = setProp(_el$18, "fg", _v$8, _p$.o));
1859
+ var _v$5 = tone(), _v$6 = tone();
1860
+ _v$5 !== _p$.e && (_p$.e = setProp(_el$16, "fg", _v$5, _p$.e));
1861
+ _v$6 !== _p$.t && (_p$.t = setProp(_el$17, "fg", _v$6, _p$.t));
1563
1862
  return _p$;
1564
1863
  }, {
1565
1864
  e: void 0,
1566
- t: void 0,
1567
- a: void 0,
1568
- o: void 0
1865
+ t: void 0
1569
1866
  });
1570
1867
  return _el$13;
1571
1868
  })();
@@ -1623,11 +1920,11 @@ function View(props) {
1623
1920
  return (b.time?.updated ?? b.updated ?? 0) - ta;
1624
1921
  }).slice(0, 8);
1625
1922
  });
1626
- const delegationsDir = createMemo(() => resolveDelegationsDir(props.api.state.path));
1627
- const panelLog = createTuiLogger(panelLogDir(delegationsDir()));
1923
+ const projectRoot = createMemo(() => resolvePantheonRoot(props.api.state.path));
1924
+ const panelLog = createTuiLogger(projectRoot());
1628
1925
  const [childDelegations, setChildDelegations] = createSignal([]);
1629
1926
  const [now, setNow] = createSignal(Date.now());
1630
- const [animationNow, setAnimationNow] = createSignal(Date.now());
1927
+ const [frame, setFrame] = createSignal(0);
1631
1928
  let eventRefreshCount = 0;
1632
1929
  let delegationsInflight = null;
1633
1930
  const refreshDelegations = () => {
@@ -1637,7 +1934,7 @@ function View(props) {
1637
1934
  const state = props.api.state;
1638
1935
  let md = [];
1639
1936
  try {
1640
- md = await readAllDelegationEntries(panelLogDir(delegationsDir()));
1937
+ md = await readAllDelegationEntries(projectRoot());
1641
1938
  } catch {
1642
1939
  md = [];
1643
1940
  }
@@ -1646,13 +1943,14 @@ function View(props) {
1646
1943
  api: props.api
1647
1944
  });
1648
1945
  if (sessionID === null) {
1649
- setChildDelegations(md);
1650
- panelLog.info(`panel: children=0 md=${md.length} events=${eventRefreshCount} (no sessionID — history shown)`);
1946
+ setChildDelegations([]);
1947
+ panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — inactive panel)`);
1651
1948
  return;
1652
1949
  }
1653
1950
  let children = [];
1654
1951
  try {
1655
- const result = await props.api.client?.session?.children?.(buildChildrenPath(sessionID));
1952
+ const childrenPath = buildChildrenPath(sessionID);
1953
+ const result = childrenPath ? await props.api.client.session.children(childrenPath) : void 0;
1656
1954
  const data = result?.data ?? result;
1657
1955
  children = Array.isArray(data) ? data : [];
1658
1956
  } catch (err) {
@@ -1662,7 +1960,7 @@ function View(props) {
1662
1960
  const resolveStatus = (childID) => {
1663
1961
  if (!isValidSessionId(childID)) return void 0;
1664
1962
  try {
1665
- return state?.session?.status?.(childID)?.type;
1963
+ return state.session.status(childID)?.type;
1666
1964
  } catch {
1667
1965
  return;
1668
1966
  }
@@ -1670,25 +1968,20 @@ function View(props) {
1670
1968
  const childEntries = childrenToDelegationEntries(children.map((c) => ({
1671
1969
  ...c,
1672
1970
  status: resolveStatus(c.id)
1673
- })), md, now());
1971
+ })), md, now(), sessionID);
1674
1972
  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);
1675
1973
  const liveEntries = [...props.liveStore.map.values()].filter((entry) => entry.sessionID === sessionID);
1676
- setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries));
1677
- panelLog.info(`panel: children=${children.length} md=${md.length} events=${eventRefreshCount}`);
1974
+ setChildDelegations(filterDelegationsToSession(mergeChildDelegationSources(childEntries, liveEntries), sessionID));
1975
+ const counts = countDelegationSources(childEntries);
1976
+ panelLog.info(formatPanelLogLine(children.length, counts.pantheon, counts.native, md.length, eventRefreshCount));
1678
1977
  } finally {
1679
1978
  delegationsInflight = null;
1680
1979
  }
1681
1980
  })();
1682
1981
  return delegationsInflight;
1683
1982
  };
1684
- const delegationSplit = createMemo(() => splitDelegationList(childDelegations()));
1685
- const visibleDelegations = createMemo(() => [...delegationSplit().active, ...delegationSplit().recent]);
1686
- const [archivedPage, setArchivedPage] = createSignal(archivedUiState.page);
1687
- const clampArchivedPage = (page, pages) => {
1688
- const clamped = Math.max(0, Math.min(page, Math.max(0, pages - 1)));
1689
- archivedUiState.page = clamped;
1690
- setArchivedPage(clamped);
1691
- };
1983
+ const delegationCeiling = createMemo(() => ceilingDelegationList(childDelegations(), 8, now()));
1984
+ const delegationHeader = createMemo(() => formatDelegationHeader(delegationCeiling().visible));
1692
1985
  onMount(() => {
1693
1986
  const cleanup = [];
1694
1987
  try {
@@ -1717,7 +2010,7 @@ function View(props) {
1717
2010
  refreshDelegations();
1718
2011
  }, 1e3);
1719
2012
  cleanup.push(() => clearInterval(poll));
1720
- const animation = setInterval(() => setAnimationNow(Date.now()), 140);
2013
+ const animation = setInterval(() => setFrame((f) => f + 1), 80);
1721
2014
  cleanup.push(() => clearInterval(animation));
1722
2015
  try {
1723
2016
  const sdk = props.api.state?.session;
@@ -1756,10 +2049,10 @@ function View(props) {
1756
2049
  return branch();
1757
2050
  },
1758
2051
  children: (b) => (() => {
1759
- var _el$32 = createElement("text");
1760
- insert(_el$32, b);
1761
- effect((_$p) => setProp(_el$32, "fg", theme().textMuted, _$p));
1762
- return _el$32;
2052
+ var _el$33 = createElement("text");
2053
+ insert(_el$33, b);
2054
+ effect((_$p) => setProp(_el$33, "fg", theme().textMuted, _$p));
2055
+ return _el$33;
1763
2056
  })()
1764
2057
  }), _el$24);
1765
2058
  insert(_el$22, createComponent(Show, {
@@ -1768,37 +2061,37 @@ function View(props) {
1768
2061
  },
1769
2062
  get fallback() {
1770
2063
  return (() => {
1771
- var _el$33 = createElement("box"), _el$34 = createElement("text");
1772
- insertNode(_el$33, _el$34);
1773
- setProp(_el$33, "flexDirection", "row");
1774
- setProp(_el$33, "gap", 1);
1775
- insertNode(_el$34, createTextNode(`Preset: default`));
1776
- effect((_$p) => setProp(_el$34, "fg", theme().textMuted, _$p));
1777
- return _el$33;
2064
+ var _el$34 = createElement("box"), _el$35 = createElement("text");
2065
+ insertNode(_el$34, _el$35);
2066
+ setProp(_el$34, "flexDirection", "row");
2067
+ setProp(_el$34, "gap", 1);
2068
+ insertNode(_el$35, createTextNode(`Preset: default`));
2069
+ effect((_$p) => setProp(_el$35, "fg", theme().textMuted, _$p));
2070
+ return _el$34;
1778
2071
  })();
1779
2072
  },
1780
2073
  children: (name) => (() => {
1781
- var _el$36 = createElement("box"), _el$37 = createElement("text"), _el$39 = createElement("text"), _el$40 = createElement("text");
1782
- insertNode(_el$36, _el$37);
1783
- insertNode(_el$36, _el$39);
1784
- insertNode(_el$36, _el$40);
1785
- setProp(_el$36, "flexDirection", "row");
1786
- setProp(_el$36, "gap", 1);
1787
- insertNode(_el$37, createTextNode(`⚡ Preset:`));
1788
- insert(_el$39, name);
1789
- insert(_el$40, () => `(${preset().source ?? ""})`);
2074
+ var _el$37 = createElement("box"), _el$38 = createElement("text"), _el$40 = createElement("text"), _el$41 = createElement("text");
2075
+ insertNode(_el$37, _el$38);
2076
+ insertNode(_el$37, _el$40);
2077
+ insertNode(_el$37, _el$41);
2078
+ setProp(_el$37, "flexDirection", "row");
2079
+ setProp(_el$37, "gap", 1);
2080
+ insertNode(_el$38, createTextNode(`⚡ Preset:`));
2081
+ insert(_el$40, name);
2082
+ insert(_el$41, () => `(${preset().source ?? ""})`);
1790
2083
  effect((_p$) => {
1791
- var _v$12 = theme().textMuted, _v$13 = theme().accent, _v$14 = theme().textMuted;
1792
- _v$12 !== _p$.e && (_p$.e = setProp(_el$37, "fg", _v$12, _p$.e));
1793
- _v$13 !== _p$.t && (_p$.t = setProp(_el$39, "fg", _v$13, _p$.t));
1794
- _v$14 !== _p$.a && (_p$.a = setProp(_el$40, "fg", _v$14, _p$.a));
2084
+ var _v$10 = theme().textMuted, _v$11 = theme().accent, _v$12 = theme().textMuted;
2085
+ _v$10 !== _p$.e && (_p$.e = setProp(_el$38, "fg", _v$10, _p$.e));
2086
+ _v$11 !== _p$.t && (_p$.t = setProp(_el$40, "fg", _v$11, _p$.t));
2087
+ _v$12 !== _p$.a && (_p$.a = setProp(_el$41, "fg", _v$12, _p$.a));
1795
2088
  return _p$;
1796
2089
  }, {
1797
2090
  e: void 0,
1798
2091
  t: void 0,
1799
2092
  a: void 0
1800
2093
  });
1801
- return _el$36;
2094
+ return _el$37;
1802
2095
  })()
1803
2096
  }), _el$24);
1804
2097
  insert(_el$22, createComponent(HR, {}), _el$24);
@@ -1819,12 +2112,12 @@ function View(props) {
1819
2112
  },
1820
2113
  get fallback() {
1821
2114
  return (() => {
1822
- var _el$41 = createElement("box"), _el$42 = createElement("text");
1823
- insertNode(_el$41, _el$42);
1824
- setProp(_el$41, "marginLeft", 1);
1825
- insertNode(_el$42, createTextNode(`No recent sessions`));
1826
- effect((_$p) => setProp(_el$42, "fg", theme().textMuted, _$p));
1827
- return _el$41;
2115
+ var _el$42 = createElement("box"), _el$43 = createElement("text");
2116
+ insertNode(_el$42, _el$43);
2117
+ setProp(_el$42, "marginLeft", 1);
2118
+ insertNode(_el$43, createTextNode(`No recent sessions`));
2119
+ effect((_$p) => setProp(_el$43, "fg", theme().textMuted, _$p));
2120
+ return _el$42;
1828
2121
  })();
1829
2122
  },
1830
2123
  get children() {
@@ -1852,7 +2145,10 @@ function View(props) {
1852
2145
  setProp(_el$28, "onMouseDown", () => setShowDelegations((x) => !x));
1853
2146
  setProp(_el$29, "attributes", 1);
1854
2147
  insert(_el$29, () => `${showDelegations() ? "▼" : "▶"} Delegations`);
1855
- insert(_el$30, () => ` (${String(visibleDelegations().length)})`);
2148
+ insert(_el$30, (() => {
2149
+ var _c$ = memo(() => delegationCeiling().visible.length > 0);
2150
+ return () => _c$() ? ` ${delegationHeader()}` : " — idle";
2151
+ })());
1856
2152
  insert(_el$22, createComponent(Show, {
1857
2153
  get when() {
1858
2154
  return showDelegations();
@@ -1860,16 +2156,16 @@ function View(props) {
1860
2156
  get children() {
1861
2157
  return createComponent(Show, {
1862
2158
  get when() {
1863
- return visibleDelegations().length > 0;
2159
+ return delegationCeiling().visible.length > 0;
1864
2160
  },
1865
2161
  get fallback() {
1866
2162
  return (() => {
1867
- var _el$44 = createElement("box"), _el$45 = createElement("text");
1868
- insertNode(_el$44, _el$45);
1869
- setProp(_el$44, "marginLeft", 1);
1870
- insertNode(_el$45, createTextNode(`No delegations`));
1871
- effect((_$p) => setProp(_el$45, "fg", theme().textMuted, _$p));
1872
- return _el$44;
2163
+ var _el$45 = createElement("box"), _el$46 = createElement("text");
2164
+ insertNode(_el$45, _el$46);
2165
+ setProp(_el$45, "marginLeft", 1);
2166
+ insertNode(_el$46, createTextNode(`No delegations`));
2167
+ effect((_$p) => setProp(_el$46, "fg", theme().textMuted, _$p));
2168
+ return _el$45;
1873
2169
  })();
1874
2170
  },
1875
2171
  get children() {
@@ -1878,7 +2174,7 @@ function View(props) {
1878
2174
  setProp(_el$31, "flexDirection", "column");
1879
2175
  insert(_el$31, createComponent(For, {
1880
2176
  get each() {
1881
- return visibleDelegations();
2177
+ return delegationCeiling().visible;
1882
2178
  },
1883
2179
  children: (job) => createComponent(DelegationRow, {
1884
2180
  get api() {
@@ -1888,69 +2184,23 @@ function View(props) {
1888
2184
  get now() {
1889
2185
  return now();
1890
2186
  },
1891
- get animationNow() {
1892
- return animationNow();
2187
+ get frame() {
2188
+ return frame();
1893
2189
  }
1894
2190
  })
1895
2191
  }), null);
1896
2192
  insert(_el$31, createComponent(Show, {
1897
2193
  get when() {
1898
- return delegationSplit().archived.length > 0;
2194
+ return delegationCeiling().hidden > 0;
1899
2195
  },
1900
2196
  get children() {
1901
- return (() => {
1902
- const archived = delegationSplit().archived;
1903
- const pageSize = 8;
1904
- const pages = Math.ceil(archived.length / pageSize);
1905
- const page = Math.min(archivedPage(), pages - 1);
1906
- const slice = archived.slice(page * pageSize, page * pageSize + pageSize);
1907
- return (() => {
1908
- var _el$47 = createElement("box"), _el$48 = createElement("box"), _el$49 = createElement("text"), _el$51 = createElement("text"), _el$52 = createElement("text"), _el$53 = createElement("text");
1909
- insertNode(_el$47, _el$48);
1910
- setProp(_el$47, "flexDirection", "column");
1911
- insertNode(_el$48, _el$49);
1912
- insertNode(_el$48, _el$51);
1913
- insertNode(_el$48, _el$52);
1914
- insertNode(_el$48, _el$53);
1915
- setProp(_el$48, "flexDirection", "row");
1916
- setProp(_el$48, "marginLeft", 0);
1917
- insertNode(_el$49, createTextNode(`‹ `));
1918
- setProp(_el$49, "onMouseDown", () => clampArchivedPage(page - 1, pages));
1919
- insert(_el$51, () => `Archived (${archived.length}) `);
1920
- insert(_el$52, `${page + 1}/${pages}`);
1921
- insertNode(_el$53, createTextNode(` ›`));
1922
- setProp(_el$53, "onMouseDown", () => clampArchivedPage(page + 1, pages));
1923
- insert(_el$47, createComponent(For, {
1924
- each: slice,
1925
- children: (job) => createComponent(DelegationRow, {
1926
- get api() {
1927
- return props.api;
1928
- },
1929
- job,
1930
- get now() {
1931
- return now();
1932
- },
1933
- get animationNow() {
1934
- return animationNow();
1935
- }
1936
- })
1937
- }), null);
1938
- effect((_p$) => {
1939
- var _v$15 = theme().textMuted, _v$16 = theme().textMuted, _v$17 = theme().textMuted, _v$18 = theme().textMuted;
1940
- _v$15 !== _p$.e && (_p$.e = setProp(_el$49, "fg", _v$15, _p$.e));
1941
- _v$16 !== _p$.t && (_p$.t = setProp(_el$51, "fg", _v$16, _p$.t));
1942
- _v$17 !== _p$.a && (_p$.a = setProp(_el$52, "fg", _v$17, _p$.a));
1943
- _v$18 !== _p$.o && (_p$.o = setProp(_el$53, "fg", _v$18, _p$.o));
1944
- return _p$;
1945
- }, {
1946
- e: void 0,
1947
- t: void 0,
1948
- a: void 0,
1949
- o: void 0
1950
- });
1951
- return _el$47;
1952
- })();
1953
- })();
2197
+ var _el$32 = createElement("text");
2198
+ insert(_el$32, (() => {
2199
+ var _c$2 = memo(() => delegationCeiling().hiddenActive > 0);
2200
+ return () => _c$2() ? `… +${delegationCeiling().hiddenActive} active` : `… +${delegationCeiling().hiddenTerminal} more`;
2201
+ })());
2202
+ effect((_$p) => setProp(_el$32, "fg", theme().textMuted, _$p));
2203
+ return _el$32;
1954
2204
  }
1955
2205
  }), null);
1956
2206
  return _el$31;
@@ -1959,12 +2209,12 @@ function View(props) {
1959
2209
  }
1960
2210
  }), null);
1961
2211
  effect((_p$) => {
1962
- var _v$9 = theme().accent, _v$0 = theme().text, _v$1 = theme().textMuted, _v$10 = theme().text, _v$11 = theme().textMuted;
1963
- _v$9 !== _p$.e && (_p$.e = setProp(_el$23, "fg", _v$9, _p$.e));
1964
- _v$0 !== _p$.t && (_p$.t = setProp(_el$25, "fg", _v$0, _p$.t));
1965
- _v$1 !== _p$.a && (_p$.a = setProp(_el$26, "fg", _v$1, _p$.a));
1966
- _v$10 !== _p$.o && (_p$.o = setProp(_el$29, "fg", _v$10, _p$.o));
1967
- _v$11 !== _p$.i && (_p$.i = setProp(_el$30, "fg", _v$11, _p$.i));
2212
+ var _v$7 = theme().accent, _v$8 = theme().text, _v$9 = theme().textMuted, _v$0 = theme().text, _v$1 = theme().textMuted;
2213
+ _v$7 !== _p$.e && (_p$.e = setProp(_el$23, "fg", _v$7, _p$.e));
2214
+ _v$8 !== _p$.t && (_p$.t = setProp(_el$25, "fg", _v$8, _p$.t));
2215
+ _v$9 !== _p$.a && (_p$.a = setProp(_el$26, "fg", _v$9, _p$.a));
2216
+ _v$0 !== _p$.o && (_p$.o = setProp(_el$29, "fg", _v$0, _p$.o));
2217
+ _v$1 !== _p$.i && (_p$.i = setProp(_el$30, "fg", _v$1, _p$.i));
1968
2218
  return _p$;
1969
2219
  }, {
1970
2220
  e: void 0,
@@ -2033,6 +2283,6 @@ const plugin = {
2033
2283
  setup: async () => {}
2034
2284
  };
2035
2285
  //#endregion
2036
- export { IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame, delegationTag, extractToolActivity, fmtElapsed, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, tuiLogPath, visibleDelegationList };
2286
+ export { DELEGATION_ALIAS_WIDTH, DELEGATION_CHILDREN_RECENCY_MS, DELEGATION_CHILD_STATUS_GRACE_MS, 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, 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, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, resolvePantheonRoot, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
2037
2287
 
2038
2288
  //# sourceMappingURL=tui.js.map