pantheon-opencode 1.5.0-beta.1 → 1.5.0-beta.11

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 (83) hide show
  1. package/.pantheon/code-mode/manifest.json +21 -0
  2. package/CHANGELOG.md +1054 -0
  3. package/README.md +171 -16
  4. package/README.pt-BR.md +99 -13
  5. package/bin/pantheon-init.mjs +178 -21
  6. package/package.json +23 -15
  7. package/plugin.json +1 -1
  8. package/pyproject.toml +65 -0
  9. package/scripts/code_mode_server.py +431 -55
  10. package/scripts/cost.mjs +30 -14
  11. package/scripts/dev-tui.sh +48 -0
  12. package/scripts/doctor.mjs +207 -9
  13. package/scripts/install/cli-ui.mjs +17 -11
  14. package/scripts/install/config-migration.mjs +4 -6
  15. package/scripts/install/opencode.mjs +137 -35
  16. package/scripts/install/plugin.mjs +3 -33
  17. package/scripts/install/shared.mjs +43 -2
  18. package/scripts/install/strings.mjs +129 -0
  19. package/scripts/install-mcp.mjs +1 -0
  20. package/scripts/manifest-inventory.mjs +410 -0
  21. package/scripts/mcp_persistence_server.py +537 -128
  22. package/scripts/package-evidence.mjs +242 -0
  23. package/scripts/probe-context-rehydrate.mjs +240 -0
  24. package/scripts/probe-pantheon-cost.mjs +313 -0
  25. package/scripts/release-beta-version.mjs +56 -60
  26. package/scripts/sync-artifacts.mjs +169 -0
  27. package/scripts/sync-tui.mjs +121 -53
  28. package/scripts/test-opencode-v1-v2-sandbox.sh +367 -96
  29. package/scripts/validation-policy.mjs +7 -7
  30. package/scripts/version-check.mjs +113 -108
  31. package/scripts/versioning.mjs +230 -86
  32. package/src/agents/mnemosyne.md +1 -1
  33. package/src/agents/zeus.md +2 -2
  34. package/src/mcp/code_mode_server.py +431 -55
  35. package/src/mcp/mcp_persistence_server.py +537 -128
  36. package/src/pantheon/auto-wake.ts +1 -5
  37. package/src/pantheon/background-job-board.ts +43 -1
  38. package/src/pantheon/compaction-handoff.ts +304 -0
  39. package/src/pantheon/cost-command.ts +345 -62
  40. package/src/pantheon/delegate-manager.ts +390 -8
  41. package/src/pantheon/delegation-finalize.ts +32 -0
  42. package/src/pantheon/delegation.ts +235 -18
  43. package/src/pantheon/file-persistence-salvage.ts +64 -0
  44. package/src/pantheon/file-persistence.ts +194 -45
  45. package/src/pantheon/native-probe.ts +234 -0
  46. package/src/pantheon/native-task-status.ts +139 -0
  47. package/src/pantheon/provider-capabilities.ts +221 -0
  48. package/src/pantheon/provider-stream-tracker.ts +182 -0
  49. package/src/pantheon/shared-board.ts +37 -0
  50. package/src/pantheon/task-result-guard.ts +107 -0
  51. package/src/pantheon/token-opt.ts +146 -12
  52. package/src/pantheon/tool-ceiling.ts +159 -0
  53. package/src/pantheon/v2-events.ts +18 -1
  54. package/src/plugin-v2.ts +2 -14
  55. package/src/plugin.ts +28 -17
  56. package/src/plugins/pantheon-hooks.ts +51 -0
  57. package/src/plugins/tui/dist/server.d.ts +1 -1
  58. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  59. package/src/plugins/tui/dist/server.js +1 -1
  60. package/src/plugins/tui/dist/server.js.map +1 -1
  61. package/src/plugins/tui/dist/tui.d.ts +137 -27
  62. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  63. package/src/plugins/tui/dist/tui.js +459 -163
  64. package/src/plugins/tui/dist/tui.js.map +1 -1
  65. package/src/plugins/tui/dist/tui.tsx +506 -103
  66. package/src/plugins/tui/package-lock.json +2 -2
  67. package/src/plugins/tui/package.json +1 -1
  68. package/src/plugins/tui/src/index.tsx +506 -103
  69. package/.pantheon/code-mode/.gitkeep +0 -0
  70. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-1.json +0 -8
  71. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-10.json +0 -8
  72. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-11.json +0 -8
  73. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-2.json +0 -8
  74. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-3.json +0 -8
  75. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-4.json +0 -8
  76. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-5.json +0 -8
  77. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-6.json +0 -8
  78. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-7.json +0 -8
  79. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-8.json +0 -8
  80. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-9.json +0 -8
  81. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/heartbeat.json +0 -8
  82. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/session.json +0 -11
  83. package/src/pantheon/delegate-task-adapter.ts +0 -444
@@ -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";
@@ -164,7 +164,7 @@ async function detectVersion(api) {
164
164
  if (ver) return ver;
165
165
  }
166
166
  } catch {}
167
- return "1.5.0";
167
+ return "1.5.0-beta.11";
168
168
  }
169
169
  /**
170
170
  * usage-bar — AI subscription usage gauge for the opencode TUI.
@@ -868,19 +868,32 @@ function tuiLogPath(projectRoot) {
868
868
  * falling back to startedAt, descending). Shared by the md reader and
869
869
  * mergeDelegationSources. */
870
870
  function compareDelegationEntries(a, b) {
871
- const aRun = a.state === "running" || a.state === "stale-running" ? 1 : 0;
872
- const bRun = b.state === "running" || b.state === "stale-running" ? 1 : 0;
871
+ const isActive = (st) => st === "running" || st === "retry" || st === "stale-running" ? 1 : 0;
872
+ const aRun = isActive(a.state);
873
+ const bRun = isActive(b.state);
873
874
  if (aRun !== bRun) return bRun - aRun;
874
875
  return (b.updatedAt ?? b.startedAt) - (a.updatedAt ?? a.startedAt);
875
876
  }
876
- /** The list the panel actually renders: running jobs first, then the most
877
- * recent terminal reports (capped). Pure — so the history-only panel (no
878
- * sessionID) is testable without the TUI runtime. The header count uses the
879
- * same "running + recentes" list. */
877
+ /** 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
880
+ * without the TUI runtime. */
881
+ function splitDelegationList(all, maxRecent = 8, now = Date.now(), staleThresholdMs = 1800 * 1e3) {
882
+ const isActiveState = (st) => st === "running" || st === "retry" || st === "stale-running";
883
+ const active = all.filter((d) => isActiveState(d.state)).map((d) => markStaleIfRunning(d, now, staleThresholdMs));
884
+ const terminal = all.filter((d) => !isActiveState(d.state));
885
+ return {
886
+ active,
887
+ recent: terminal.slice(0, maxRecent),
888
+ archived: terminal.slice(maxRecent)
889
+ };
890
+ }
891
+ /** The list the panel actually renders (kept for the header count and
892
+ * existing tests): active jobs first, then the most recent terminal
893
+ * reports (capped) — the archived tail is rendered separately. Pure. */
880
894
  function visibleDelegationList(all, maxTerminal = 8, now = Date.now(), staleThresholdMs = 1800 * 1e3) {
881
- const running = all.filter((d) => d.state === "running").map((d) => markStaleIfRunning(d, now, staleThresholdMs));
882
- const terminal = all.filter((d) => d.state !== "running").slice(0, maxTerminal);
883
- return [...running, ...terminal];
895
+ const { active, recent } = splitDelegationList(all, maxTerminal, now, staleThresholdMs);
896
+ return [...active, ...recent];
884
897
  }
885
898
  /** Default stale-running threshold: 30 minutes. */
886
899
  const STALE_RUNNING_THRESHOLD_MS = 1800 * 1e3;
@@ -918,10 +931,11 @@ function fmtElapsed(ms) {
918
931
  if (minutes > 0) return `${minutes}m ${seconds}s`;
919
932
  return `${seconds}s`;
920
933
  }
921
- /** Elapsed label for one entry: running → ticks `now - startedAt`, terminal
922
- * → fixed `updatedAt - startedAt` (em dash when no finalized timestamp). */
934
+ /** Elapsed label for one entry: an ACTIVE entry (running/retry/stale-running)
935
+ * ticks `now - startedAt`; a terminal one is fixed at
936
+ * `updatedAt - startedAt` (em dash when no finalized timestamp). */
923
937
  function delegationElapsed(entry, now) {
924
- if (entry.state === "running" || entry.state === "stale-running") return fmtElapsed(now - entry.startedAt);
938
+ if (entry.state === "running" || entry.state === "retry" || entry.state === "stale-running") return fmtElapsed(now - entry.startedAt);
925
939
  return entry.updatedAt !== null ? fmtElapsed(entry.updatedAt - entry.startedAt) : "—";
926
940
  }
927
941
  /** The activity labels shown by the animated row. Keeping this pure makes the
@@ -935,6 +949,7 @@ function delegationActivity(entry) {
935
949
  return "working";
936
950
  }
937
951
  function delegationActivityLabel(entry) {
952
+ if (entry.state === "retry") return "RETRYING";
938
953
  switch (delegationActivity(entry)) {
939
954
  case "delegating": return "DELEGATING";
940
955
  case "working": return "WORKING";
@@ -956,11 +971,114 @@ const DELEGATION_SPINNER_FRAMES = [
956
971
  "⠇",
957
972
  "⠏"
958
973
  ];
959
- /** Return a deterministic spinner frame. The View ticks this every 140ms. */
974
+ /** Return a deterministic spinner frame. The View ticks this every 1000ms
975
+ * (not 140ms — the fast tick flickered without adding information). */
960
976
  function delegationSpinnerFrame(now) {
961
- const index = Math.floor(Math.max(0, now) / 140) % DELEGATION_SPINNER_FRAMES.length;
977
+ const index = Math.floor(Math.max(0, now) / 1e3) % DELEGATION_SPINNER_FRAMES.length;
962
978
  return DELEGATION_SPINNER_FRAMES[index] ?? DELEGATION_SPINNER_FRAMES[0];
963
979
  }
980
+ /** Every state the row knows how to draw: the real BackgroundJobBoard FSM
981
+ * (src/pantheon/background-job-board.ts) plus the TUI display-only states
982
+ * (`retry`, `stale-running`). Fase 1 deliberately omits speculative
983
+ * blocked/paused/scheduled/skipped. There is no `pending` display state: a
984
+ * pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
985
+ /** Static glyph per display state. `running` shows its base spinner frame;
986
+ * callers that animate must prefer {@link delegationStateMarker}. Unicode
987
+ * geometric shapes only (no Nerd Font) — shape is an independent channel
988
+ * from color, so rows stay legible without color. */
989
+ const DELEGATION_STATE_GLYPHS = {
990
+ running: "⠋",
991
+ retry: "⟳",
992
+ "stale-running": "⚠",
993
+ completed: "✓",
994
+ error: "✕",
995
+ cancelled: "−",
996
+ startup_failed: "⚠",
997
+ startup_unknown: "⚠"
998
+ };
999
+ function delegationStateGlyph(state) {
1000
+ return DELEGATION_STATE_GLYPHS[state];
1001
+ }
1002
+ /** Row marker (`<glyph> `) — the state channel. `running` animates through
1003
+ * {@link delegationSpinnerFrame} for the given tick; every other state is
1004
+ * static. Pure. */
1005
+ function delegationStateMarker(state, now = Date.now()) {
1006
+ return `${state === "running" ? delegationSpinnerFrame(now) : delegationStateGlyph(state)} `;
1007
+ }
1008
+ /** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
1009
+ * Kept separate + pure so the color channel is testable without booting the
1010
+ * renderer. */
1011
+ function delegationStateTone(state) {
1012
+ switch (state) {
1013
+ case "running":
1014
+ case "retry":
1015
+ case "startup_unknown": return "warning";
1016
+ case "stale-running":
1017
+ case "error":
1018
+ case "startup_failed": return "error";
1019
+ case "completed": return "success";
1020
+ case "cancelled": return "muted";
1021
+ }
1022
+ }
1023
+ /** Bounded tracker: child session id → latest running tool call. */
1024
+ const latestToolActivity = /* @__PURE__ */ new Map();
1025
+ const MAX_TOOL_ACTIVITY_ENTRIES = 200;
1026
+ const TOOL_ACTIVITY_TTL_MS = 300 * 1e3;
1027
+ /** Archived-section pagination state — module-level so it survives sidebar
1028
+ * remounts (route changes) without leaking into the component tree. */
1029
+ const archivedUiState = { page: 0 };
1030
+ /** First usable summary string from common tool input fields. */
1031
+ function summarizeToolInput(input) {
1032
+ if (!input) return "";
1033
+ for (const key of [
1034
+ "command",
1035
+ "description",
1036
+ "prompt",
1037
+ "filePath",
1038
+ "pattern",
1039
+ "url",
1040
+ "query"
1041
+ ]) {
1042
+ const value = input[key];
1043
+ if (typeof value === "string" && value.trim() !== "") return value;
1044
+ }
1045
+ return "";
1046
+ }
1047
+ /** Reduce one message.part.updated tool part to displayable activity.
1048
+ * Returns null for non-tool parts, completed/error parts (no live activity)
1049
+ * or parts without a session id. Pure. */
1050
+ function extractToolActivity(part, now = Date.now()) {
1051
+ if (part?.type !== "tool") return null;
1052
+ const status = part.state?.status;
1053
+ if (status !== "pending" && status !== "running") return null;
1054
+ const sessionID = typeof part.sessionID === "string" ? part.sessionID : "";
1055
+ if (sessionID === "") return null;
1056
+ return {
1057
+ sessionID,
1058
+ activity: {
1059
+ tool: typeof part.tool === "string" && part.tool !== "" ? part.tool : "tool",
1060
+ summary: summarizeToolInput(part.state?.input).replace(/\s+/g, " ").trim().slice(0, 48),
1061
+ at: now
1062
+ }
1063
+ };
1064
+ }
1065
+ /** Record an activity sample (bounded map, oldest dropped). */
1066
+ function trackToolActivity(map, sessionID, activity) {
1067
+ if (sessionID === "") return;
1068
+ if (map.size >= MAX_TOOL_ACTIVITY_ENTRIES) {
1069
+ const oldest = map.keys().next().value;
1070
+ if (oldest !== void 0) map.delete(oldest);
1071
+ }
1072
+ map.set(sessionID, activity);
1073
+ }
1074
+ /** Latest live activity for a child session, or null when absent/stale. */
1075
+ function latestToolActivityFor(map, sessionID, now = Date.now(), ttlMs = TOOL_ACTIVITY_TTL_MS) {
1076
+ if (!sessionID) return null;
1077
+ const hit = map.get(sessionID);
1078
+ if (hit === void 0) return null;
1079
+ if (now - hit.at > ttlMs) return null;
1080
+ return hit;
1081
+ }
964
1082
  /** Merge the immediate tool-event channel into the child-session channel.
965
1083
  *
966
1084
  * Children remain the durable source, while live entries make a delegation
@@ -1023,10 +1141,12 @@ const DELEGATE_TASKID_PATTERN = /\(task\s+([a-z0-9_]+)\)/i;
1023
1141
  const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i;
1024
1142
  /** Extract the tool name + args from a `message.part.updated` part and
1025
1143
  * reduce it to what the panel needs. Returns null for anything that is
1026
- * not a pantheon delegation tool part (or is missing its callID). */
1144
+ * not a pantheon delegation tool part, the native `task` subagent tool
1145
+ * (same parentID === caller mechanism — its children render `nat:`),
1146
+ * or is missing its callID. */
1027
1147
  function parseDelegationToolPart(part, now = Date.now()) {
1028
1148
  if (part.type !== "tool") return null;
1029
- if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read") return null;
1149
+ if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read" && part.tool !== "task") return null;
1030
1150
  const callID = part.callID;
1031
1151
  if (callID === void 0 || callID === "") return null;
1032
1152
  const sessionID = part.sessionID ?? "";
@@ -1058,8 +1178,8 @@ function parseDelegationToolPart(part, now = Date.now()) {
1058
1178
  endAt
1059
1179
  };
1060
1180
  }
1061
- const agent = typeof input.agent === "string" ? input.agent : "agent";
1062
- const description = typeof input.description === "string" ? input.description : "";
1181
+ const agent = typeof input.agent === "string" ? input.agent : typeof input.subagent_type === "string" ? input.subagent_type : "agent";
1182
+ const description = typeof input.description === "string" ? input.description : typeof input.prompt === "string" ? input.prompt.slice(0, 120) : "";
1063
1183
  let alias = null;
1064
1184
  let taskID = null;
1065
1185
  if (status === "completed") {
@@ -1071,7 +1191,7 @@ function parseDelegationToolPart(part, now = Date.now()) {
1071
1191
  callID,
1072
1192
  partID: part.id ?? "",
1073
1193
  sessionID,
1074
- tool: "pantheon_delegate",
1194
+ tool: part.tool === "task" ? "task" : "pantheon_delegate",
1075
1195
  agent,
1076
1196
  description,
1077
1197
  status,
@@ -1120,7 +1240,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
1120
1240
  callID: parsed.callID,
1121
1241
  partID: parsed.partID,
1122
1242
  sessionID: parsed.sessionID,
1123
- tool: "pantheon_delegate",
1243
+ tool: parsed.tool,
1124
1244
  agent: parsed.agent ?? "agent",
1125
1245
  description: parsed.description,
1126
1246
  alias: existing?.alias ?? null,
@@ -1137,7 +1257,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
1137
1257
  callID: parsed.callID,
1138
1258
  partID: parsed.partID,
1139
1259
  sessionID: parsed.sessionID,
1140
- tool: "pantheon_delegate",
1260
+ tool: parsed.tool,
1141
1261
  agent: parsed.agent ?? "agent",
1142
1262
  description: parsed.description,
1143
1263
  alias: parsed.alias,
@@ -1182,11 +1302,14 @@ function removeDelegationEntry(map, partIDOrCallID) {
1182
1302
  }
1183
1303
  return false;
1184
1304
  }
1185
- /** Collect pantheon delegation tool parts from a session's messages.
1305
+ /** Collect pantheon delegation + native task tool parts from a session's messages.
1186
1306
  * Messages may carry their parts inline (duck-typed `msg.parts`); when
1187
1307
  * they don't, the optional `getParts(messageID)` callback is used (the TUI
1188
- * SDK exposes `api.state.part(messageID)`). Pure w.r.t. I/O — used by the
1189
- * mount re-scan to re-seed the live map after compaction/attach. */
1308
+ * SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
1309
+ * child session with parentID = caller — the same mechanism as
1310
+ * pantheon_delegate — so its parts feed the live-map as the native signal
1311
+ * (rows render `nat:` via the children channel). Pure w.r.t. I/O — used
1312
+ * by the mount re-scan to re-seed the live map after compaction/attach. */
1190
1313
  function collectDelegationToolParts(messages, getParts) {
1191
1314
  const out = [];
1192
1315
  for (const msg of messages ?? []) {
@@ -1196,7 +1319,7 @@ function collectDelegationToolParts(messages, getParts) {
1196
1319
  if (!parts) continue;
1197
1320
  for (const raw of parts) {
1198
1321
  const part = raw;
1199
- if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read")) out.push(part);
1322
+ if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read" || part.tool === "task")) out.push(part);
1200
1323
  }
1201
1324
  }
1202
1325
  return out;
@@ -1309,16 +1432,102 @@ function buildChildrenPath(id) {
1309
1432
  * correct it as soon as terminal data exists). */
1310
1433
  function childStatusToState(status) {
1311
1434
  if (status === "idle") return "completed";
1435
+ if (status === "retry") return "retry";
1312
1436
  return "running";
1313
1437
  }
1314
- /** Row tag for a delegation entry: `[native]` for native task() children
1315
- * (source 'children-only' — no board report), `[pantheon:<alias>]` for board
1316
- * rows ([pantheon:apo-1]) — the same tags the manager prints in
1317
- * pantheon_delegation_list, so panel rows and CLI list lines match. The
1318
- * panel renders the tag with a distinct style so native task() children are
1319
- * visually separable from pantheon_delegate jobs. */
1438
+ /** Short row prefix for a delegation entry (one of the two visual channels
1439
+ * that split native task() work from pantheon_delegate jobs — the other is
1440
+ * {@link delegationIcon}):
1441
+ *
1442
+ * - `nat:<agent>` — a native task() child (source 'children-only', no board
1443
+ * report). The child session carries no board alias, so the agent IS the
1444
+ * identity.
1445
+ * - `pan:<alias>` — a pantheon_delegate board row (`pan:apo-1`), the same
1446
+ * alias the manager prints in pantheon_delegation_list.
1447
+ *
1448
+ * Short prefixes keep the narrow sidebar readable (the old
1449
+ * `pan:apo-1`/`nat:` tags ate the row width). */
1320
1450
  function delegationTag(entry) {
1321
- return entry.source === "children-only" ? "[native]" : `[pantheon:${entry.alias}]`;
1451
+ return entry.source === "children-only" ? `nat:${entry.agent}` : `pan:${entry.alias}`;
1452
+ }
1453
+ /** Row glyph: hollow diamond `◇` for native task() children (info color,
1454
+ * outline) vs filled diamond `◆` for pantheon_delegate rows (state color).
1455
+ * Shape + color are independent channels, so the split stays legible even
1456
+ * without color. */
1457
+ function delegationIcon(entry) {
1458
+ return entry.source === "children-only" ? "◇" : "◆";
1459
+ }
1460
+ /** Row identity after the status marker + glyph. Pantheon rows append the
1461
+ * agent (`pan:apo-1 apollo`); native rows already carry it inside the tag
1462
+ * (`nat:apollo`) and must not duplicate it. */
1463
+ function formatDelegationIdentity(entry) {
1464
+ return entry.source === "children-only" ? delegationTag(entry) : `${delegationTag(entry)} ${entry.agent}`;
1465
+ }
1466
+ /** Max description width on the row detail line — the old 180-char slice
1467
+ * wrapped the sidebar; 44 keeps one readable line. */
1468
+ const DELEGATION_DESCRIPTION_MAX = 44;
1469
+ /** Lazily-built grapheme segmenter. `Intl.Segmenter` keeps ZWJ emoji families
1470
+ * (and skin-tone/variation sequences) intact; `Array.from` (code points) is
1471
+ * the fallback for runtimes without it. */
1472
+ let graphemeSegmenter;
1473
+ /** Split `text` into display graphemes so truncation never cuts a multi-unit
1474
+ * emoji in half (surrogate pair → mojibake). */
1475
+ function splitGraphemes(text) {
1476
+ if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
1477
+ graphemeSegmenter ??= new Intl.Segmenter("pt", { granularity: "grapheme" });
1478
+ return Array.from(graphemeSegmenter.segment(text), (s) => s.segment);
1479
+ }
1480
+ return Array.from(text);
1481
+ }
1482
+ /** Truncate a description to `max` graphemes appending `…` when cut. Exact
1483
+ * `max`-length text is left untouched. Grapheme-granular, so an emoji made of
1484
+ * several code points is never split into mojibake at the boundary. Pure. */
1485
+ function truncateDelegationDescription(text, max = 44) {
1486
+ const graphemes = splitGraphemes(text);
1487
+ if (graphemes.length <= max) return text;
1488
+ return `${graphemes.slice(0, Math.max(0, max - 1)).join("")}\u2026`;
1489
+ }
1490
+ /** Fixed elapsed-time box: right-aligned to `width` (default 8) so elapsed
1491
+ * values line up across rows, e.g. ` 12s`. A label that already fills
1492
+ * the box is returned untouched. Pure. */
1493
+ const DELEGATION_ELAPSED_WIDTH = 8;
1494
+ function formatDelegationElapsed(entry, now, width = 8) {
1495
+ const label = delegationElapsed(entry, now);
1496
+ if (label.length >= width) return label;
1497
+ return `${" ".repeat(width - label.length)}${label}`;
1498
+ }
1499
+ /** First row line: `<marker><glyph> <identity>`. The marker already carries
1500
+ * its trailing space. Pure — the row renders the returned string verbatim. */
1501
+ function formatDelegationRow(entry, marker) {
1502
+ return `${marker}${delegationIcon(entry)} ${formatDelegationIdentity(entry)}`;
1503
+ }
1504
+ /** Header summary: `(N active · M done · nat:K pan:M)`. Active counts
1505
+ * running/retry AND display-only stale-running (a stale row is still a live
1506
+ * job — it must never read as done). Pure. */
1507
+ function formatDelegationHeader(entries) {
1508
+ let active = 0;
1509
+ for (const e of entries) if (e.state === "running" || e.state === "retry" || e.state === "stale-running") active++;
1510
+ const done = entries.length - active;
1511
+ const counts = countDelegationSources(entries);
1512
+ return `(${active} active \u00b7 ${done} done \u00b7 nat:${counts.native} pan:${counts.pantheon})`;
1513
+ }
1514
+ /** Split a display list into native task() rows vs pantheon_delegate rows.
1515
+ * Native = source 'children-only' (no board report); everything else counts
1516
+ * as pantheon. Pure — powers the header breakdown + the hooks.log line. */
1517
+ function countDelegationSources(entries) {
1518
+ let native = 0;
1519
+ for (const e of entries) if (e.source === "children-only") native++;
1520
+ return {
1521
+ native,
1522
+ pantheon: entries.length - native,
1523
+ total: entries.length
1524
+ };
1525
+ }
1526
+ /** Diagnostic hooks.log line for a panel re-fetch, with the children
1527
+ * breakdown (pantheon = children WITH a board report, native = children
1528
+ * WITHOUT one). Pure — the View logs the returned string verbatim. */
1529
+ function formatPanelLogLine(children, pantheon, native, md, events) {
1530
+ return `panel: children=${children}(pantheon=${pantheon} native=${native}) md=${md} events=${events}`;
1322
1531
  }
1323
1532
  /** Turn child sessions (PRIMARY) enriched with md reports into the display
1324
1533
  * list. One entry per child id (duplicates across re-fetches collapse).
@@ -1328,10 +1537,14 @@ function delegationTag(entry) {
1328
1537
  * itself (fallback 'agent'), state derived from its status, startedAt from
1329
1538
  * time.created. A report-less child is a NATIVE task() child (every
1330
1539
  * child of the current session — pantheon_delegate OR the native `task()`
1331
- * tool — carries parentID = caller), so it gets source 'children-only'
1332
- * and the `[native]` tag instead of a board alias.
1540
+ * tool — carries parentID = caller), so it gets source 'children-only',
1541
+ * the internal alias 'native-task' and the `nat:` tag instead of a
1542
+ * board alias. The 'task nativa' description fallback keeps the row
1543
+ * non-empty when the child carries no title.
1333
1544
  * Terminal md state wins over the derived state; a running md defers to
1334
- * the child's live status. Sorted running-first (compareDelegationEntries).
1545
+ * the child's live status. A running child is NEVER archived — it always
1546
+ * lands in the active split (splitDelegationList active = running/retry).
1547
+ * Sorted running-first (compareDelegationEntries).
1335
1548
  * Pure — no I/O. */
1336
1549
  function childrenToDelegationEntries(children, md, now = Date.now()) {
1337
1550
  const byTaskID = /* @__PURE__ */ new Map();
@@ -1344,7 +1557,7 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
1344
1557
  const mdEntry = byTaskID.get(child.id);
1345
1558
  const state = mdEntry !== void 0 && mdEntry.state !== "running" ? mdEntry.state : childStatusToState(child.status);
1346
1559
  out.push({
1347
- alias: mdEntry?.alias ?? "task",
1560
+ alias: mdEntry?.alias ?? "native-task",
1348
1561
  sessionID: mdEntry?.sessionID ?? "",
1349
1562
  taskID: child.id,
1350
1563
  agent: mdEntry?.agent ?? child.agent ?? "agent",
@@ -1352,7 +1565,7 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
1352
1565
  startedAt: mdEntry?.startedAt ?? child.time?.created ?? now,
1353
1566
  updatedAt: mdEntry?.updatedAt ?? (state === "running" || state === "stale-running" ? null : child.time?.updated ?? null),
1354
1567
  timedOut: mdEntry?.timedOut ?? false,
1355
- description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title ?? "",
1568
+ description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title !== void 0 && child.title !== "" ? child.title : "task nativa",
1356
1569
  source: mdEntry !== void 0 ? "md" : "children-only"
1357
1570
  });
1358
1571
  }
@@ -1395,7 +1608,11 @@ function createTuiLogger(projectRoot, module = "pantheon-tui") {
1395
1608
  await mkdir(dirname(logPath), { recursive: true });
1396
1609
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
1397
1610
  await appendFile(logPath, `${lines.map((l) => `[${stamp}] [${module}] ${l}`).join("\n")}\n`, "utf8");
1398
- } catch {}
1611
+ } catch (err) {
1612
+ if (echo) try {
1613
+ process.stderr.write(`[${module}] file-log failed: ${formatArg(err)}\n`);
1614
+ } catch {}
1615
+ }
1399
1616
  })();
1400
1617
  if (echo) for (const line of lines) console.log(`[${module}] ${line}`);
1401
1618
  };
@@ -1429,56 +1646,64 @@ function DelegationRow(props) {
1429
1646
  const theme = () => props.api.theme.current;
1430
1647
  const color = createMemo(() => {
1431
1648
  const t = theme();
1432
- switch (props.job.state) {
1433
- case "running": return t.warning;
1434
- case "stale-running": return t.error;
1435
- case "completed": return t.success;
1649
+ switch (delegationStateTone(props.job.state)) {
1650
+ case "warning": return t.warning;
1436
1651
  case "error": return t.error;
1652
+ case "success": return t.success;
1437
1653
  default: return t.textMuted;
1438
1654
  }
1439
1655
  });
1440
- const marker = createMemo(() => {
1441
- if (props.job.state === "running") return `${delegationSpinnerFrame(props.animationNow)} `;
1442
- if (props.job.state === "stale-running") return "⚠ ";
1443
- switch (props.job.state) {
1444
- case "completed": return "✓ ";
1445
- case "error": return "✕ ";
1446
- default: return "○ ";
1447
- }
1448
- });
1656
+ const marker = createMemo(() => delegationStateMarker(props.job.state, props.animationNow));
1449
1657
  const tagColor = createMemo(() => props.job.source === "children-only" ? theme().info : color());
1450
- const detail = createMemo(() => {
1451
- const line = `${delegationElapsed(props.job, props.now)}${props.job.description !== "" ? ` \u2014 ${props.job.description}` : ""}`;
1452
- return line.length > 180 ? `${line.slice(0, 177)}\u2026` : line;
1453
- });
1658
+ const description = createMemo(() => truncateDelegationDescription(props.job.description));
1659
+ const elapsed = createMemo(() => formatDelegationElapsed(props.job, props.now));
1660
+ const activity = createMemo(() => latestToolActivityFor(latestToolActivity, props.job.taskID, props.now));
1454
1661
  const open = () => {
1455
1662
  navigateToDelegationSession(props.api.route, props.job.taskID);
1456
1663
  };
1457
1664
  return (() => {
1458
- 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");
1665
+ var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("box"), _el$18 = createElement("text");
1459
1666
  insertNode(_el$13, _el$14);
1460
- insertNode(_el$13, _el$18);
1667
+ insertNode(_el$13, _el$16);
1461
1668
  setProp(_el$13, "onMouseDown", open);
1462
1669
  insertNode(_el$14, _el$15);
1463
- insertNode(_el$14, _el$16);
1464
- insertNode(_el$14, _el$17);
1465
1670
  setProp(_el$14, "flexDirection", "row");
1466
- insert(_el$15, marker);
1467
- insert(_el$16, () => delegationTag(props.job));
1468
- insert(_el$17, () => ` ${props.job.agent} \u2014 ${delegationActivityLabel(props.job)}`);
1469
- insert(_el$18, detail);
1671
+ insert(_el$15, () => formatDelegationRow(props.job, marker()));
1672
+ insertNode(_el$16, _el$18);
1673
+ setProp(_el$16, "flexDirection", "row");
1674
+ insert(_el$16, createComponent(Show, {
1675
+ get when() {
1676
+ return description() !== "";
1677
+ },
1678
+ get children() {
1679
+ var _el$17 = createElement("text");
1680
+ insert(_el$17, description);
1681
+ effect((_$p) => setProp(_el$17, "fg", theme().textMuted, _$p));
1682
+ return _el$17;
1683
+ }
1684
+ }), _el$18);
1685
+ insert(_el$18, elapsed);
1686
+ insert(_el$13, createComponent(Show, {
1687
+ get when() {
1688
+ return activity();
1689
+ },
1690
+ children: (a) => (() => {
1691
+ var _el$19 = createElement("text");
1692
+ insert(_el$19, () => ` \u21b3 ${truncateDelegationDescription(`${a().tool}${a().summary !== "" ? ` ${a().summary}` : ""}`)}`);
1693
+ effect((_$p) => setProp(_el$19, "fg", theme().textMuted, _$p));
1694
+ return _el$19;
1695
+ })()
1696
+ }), null);
1470
1697
  effect((_p$) => {
1471
- var _v$5 = color(), _v$6 = tagColor(), _v$7 = color(), _v$8 = theme().textMuted;
1698
+ var _v$5 = tagColor(), _v$6 = description() !== "" ? "space-between" : "flex-end", _v$7 = theme().textMuted;
1472
1699
  _v$5 !== _p$.e && (_p$.e = setProp(_el$15, "fg", _v$5, _p$.e));
1473
- _v$6 !== _p$.t && (_p$.t = setProp(_el$16, "fg", _v$6, _p$.t));
1474
- _v$7 !== _p$.a && (_p$.a = setProp(_el$17, "fg", _v$7, _p$.a));
1475
- _v$8 !== _p$.o && (_p$.o = setProp(_el$18, "fg", _v$8, _p$.o));
1700
+ _v$6 !== _p$.t && (_p$.t = setProp(_el$16, "justifyContent", _v$6, _p$.t));
1701
+ _v$7 !== _p$.a && (_p$.a = setProp(_el$18, "fg", _v$7, _p$.a));
1476
1702
  return _p$;
1477
1703
  }, {
1478
1704
  e: void 0,
1479
1705
  t: void 0,
1480
- a: void 0,
1481
- o: void 0
1706
+ a: void 0
1482
1707
  });
1483
1708
  return _el$13;
1484
1709
  })();
@@ -1560,7 +1785,7 @@ function View(props) {
1560
1785
  });
1561
1786
  if (sessionID === null) {
1562
1787
  setChildDelegations(md);
1563
- panelLog.info(`panel: children=0 md=${md.length} events=${eventRefreshCount} (no sessionID — history shown)`);
1788
+ panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — history shown)`);
1564
1789
  return;
1565
1790
  }
1566
1791
  let children = [];
@@ -1587,14 +1812,23 @@ function View(props) {
1587
1812
  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);
1588
1813
  const liveEntries = [...props.liveStore.map.values()].filter((entry) => entry.sessionID === sessionID);
1589
1814
  setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries));
1590
- panelLog.info(`panel: children=${children.length} md=${md.length} events=${eventRefreshCount}`);
1815
+ const counts = countDelegationSources(childEntries);
1816
+ panelLog.info(formatPanelLogLine(children.length, counts.pantheon, counts.native, md.length, eventRefreshCount));
1591
1817
  } finally {
1592
1818
  delegationsInflight = null;
1593
1819
  }
1594
1820
  })();
1595
1821
  return delegationsInflight;
1596
1822
  };
1597
- const visibleDelegations = createMemo(() => visibleDelegationList(childDelegations()));
1823
+ const delegationSplit = createMemo(() => splitDelegationList(childDelegations()));
1824
+ const visibleDelegations = createMemo(() => [...delegationSplit().active, ...delegationSplit().recent]);
1825
+ const delegationHeader = createMemo(() => formatDelegationHeader(childDelegations()));
1826
+ const [archivedPage, setArchivedPage] = createSignal(archivedUiState.page);
1827
+ const clampArchivedPage = (page, pages) => {
1828
+ const clamped = Math.max(0, Math.min(page, Math.max(0, pages - 1)));
1829
+ archivedUiState.page = clamped;
1830
+ setArchivedPage(clamped);
1831
+ };
1598
1832
  onMount(() => {
1599
1833
  const cleanup = [];
1600
1834
  try {
@@ -1623,7 +1857,7 @@ function View(props) {
1623
1857
  refreshDelegations();
1624
1858
  }, 1e3);
1625
1859
  cleanup.push(() => clearInterval(poll));
1626
- const animation = setInterval(() => setAnimationNow(Date.now()), 140);
1860
+ const animation = setInterval(() => setAnimationNow(Date.now()), 1e3);
1627
1861
  cleanup.push(() => clearInterval(animation));
1628
1862
  try {
1629
1863
  const sdk = props.api.state?.session;
@@ -1643,78 +1877,78 @@ function View(props) {
1643
1877
  }));
1644
1878
  });
1645
1879
  const HR = () => (() => {
1646
- var _el$19 = createElement("text");
1647
- insertNode(_el$19, createTextNode(`────────────────────────────`));
1648
- effect((_$p) => setProp(_el$19, "fg", theme().textMuted, _$p));
1649
- return _el$19;
1880
+ var _el$20 = createElement("text");
1881
+ insertNode(_el$20, createTextNode(`────────────────────────────`));
1882
+ effect((_$p) => setProp(_el$20, "fg", theme().textMuted, _$p));
1883
+ return _el$20;
1650
1884
  })();
1651
1885
  return (() => {
1652
- var _el$21 = createElement("box"), _el$22 = createElement("text"), _el$23 = createElement("box"), _el$24 = createElement("text"), _el$25 = createElement("text"), _el$27 = createElement("box"), _el$28 = createElement("text"), _el$29 = createElement("text");
1653
- insertNode(_el$21, _el$22);
1654
- insertNode(_el$21, _el$23);
1655
- insertNode(_el$21, _el$27);
1656
- setProp(_el$21, "flexDirection", "column");
1657
- setProp(_el$21, "width", "100%");
1658
- setProp(_el$22, "attributes", 1);
1659
- insert(_el$22, () => `Pantheon${props.version ? ` v${props.version}` : ""}`);
1660
- insert(_el$21, createComponent(Show, {
1886
+ var _el$22 = createElement("box"), _el$23 = createElement("text"), _el$24 = createElement("box"), _el$25 = createElement("text"), _el$26 = createElement("text"), _el$28 = createElement("box"), _el$29 = createElement("text"), _el$30 = createElement("text");
1887
+ insertNode(_el$22, _el$23);
1888
+ insertNode(_el$22, _el$24);
1889
+ insertNode(_el$22, _el$28);
1890
+ setProp(_el$22, "flexDirection", "column");
1891
+ setProp(_el$22, "width", "100%");
1892
+ setProp(_el$23, "attributes", 1);
1893
+ insert(_el$23, () => `Pantheon${props.version ? ` v${props.version}` : ""}`);
1894
+ insert(_el$22, createComponent(Show, {
1661
1895
  get when() {
1662
1896
  return branch();
1663
1897
  },
1664
1898
  children: (b) => (() => {
1665
- var _el$31 = createElement("text");
1666
- insert(_el$31, b);
1667
- effect((_$p) => setProp(_el$31, "fg", theme().textMuted, _$p));
1668
- return _el$31;
1899
+ var _el$32 = createElement("text");
1900
+ insert(_el$32, b);
1901
+ effect((_$p) => setProp(_el$32, "fg", theme().textMuted, _$p));
1902
+ return _el$32;
1669
1903
  })()
1670
- }), _el$23);
1671
- insert(_el$21, createComponent(Show, {
1904
+ }), _el$24);
1905
+ insert(_el$22, createComponent(Show, {
1672
1906
  get when() {
1673
1907
  return preset().name;
1674
1908
  },
1675
1909
  get fallback() {
1676
1910
  return (() => {
1677
- var _el$32 = createElement("box"), _el$33 = createElement("text");
1678
- insertNode(_el$32, _el$33);
1679
- setProp(_el$32, "flexDirection", "row");
1680
- setProp(_el$32, "gap", 1);
1681
- insertNode(_el$33, createTextNode(`Preset: default`));
1682
- effect((_$p) => setProp(_el$33, "fg", theme().textMuted, _$p));
1683
- return _el$32;
1911
+ var _el$33 = createElement("box"), _el$34 = createElement("text");
1912
+ insertNode(_el$33, _el$34);
1913
+ setProp(_el$33, "flexDirection", "row");
1914
+ setProp(_el$33, "gap", 1);
1915
+ insertNode(_el$34, createTextNode(`Preset: default`));
1916
+ effect((_$p) => setProp(_el$34, "fg", theme().textMuted, _$p));
1917
+ return _el$33;
1684
1918
  })();
1685
1919
  },
1686
1920
  children: (name) => (() => {
1687
- var _el$35 = createElement("box"), _el$36 = createElement("text"), _el$38 = createElement("text"), _el$39 = createElement("text");
1688
- insertNode(_el$35, _el$36);
1689
- insertNode(_el$35, _el$38);
1690
- insertNode(_el$35, _el$39);
1691
- setProp(_el$35, "flexDirection", "row");
1692
- setProp(_el$35, "gap", 1);
1693
- insertNode(_el$36, createTextNode(`⚡ Preset:`));
1694
- insert(_el$38, name);
1695
- insert(_el$39, () => `(${preset().source ?? ""})`);
1921
+ var _el$36 = createElement("box"), _el$37 = createElement("text"), _el$39 = createElement("text"), _el$40 = createElement("text");
1922
+ insertNode(_el$36, _el$37);
1923
+ insertNode(_el$36, _el$39);
1924
+ insertNode(_el$36, _el$40);
1925
+ setProp(_el$36, "flexDirection", "row");
1926
+ setProp(_el$36, "gap", 1);
1927
+ insertNode(_el$37, createTextNode(`⚡ Preset:`));
1928
+ insert(_el$39, name);
1929
+ insert(_el$40, () => `(${preset().source ?? ""})`);
1696
1930
  effect((_p$) => {
1697
- var _v$12 = theme().textMuted, _v$13 = theme().accent, _v$14 = theme().textMuted;
1698
- _v$12 !== _p$.e && (_p$.e = setProp(_el$36, "fg", _v$12, _p$.e));
1699
- _v$13 !== _p$.t && (_p$.t = setProp(_el$38, "fg", _v$13, _p$.t));
1700
- _v$14 !== _p$.a && (_p$.a = setProp(_el$39, "fg", _v$14, _p$.a));
1931
+ var _v$11 = theme().textMuted, _v$12 = theme().accent, _v$13 = theme().textMuted;
1932
+ _v$11 !== _p$.e && (_p$.e = setProp(_el$37, "fg", _v$11, _p$.e));
1933
+ _v$12 !== _p$.t && (_p$.t = setProp(_el$39, "fg", _v$12, _p$.t));
1934
+ _v$13 !== _p$.a && (_p$.a = setProp(_el$40, "fg", _v$13, _p$.a));
1701
1935
  return _p$;
1702
1936
  }, {
1703
1937
  e: void 0,
1704
1938
  t: void 0,
1705
1939
  a: void 0
1706
1940
  });
1707
- return _el$35;
1941
+ return _el$36;
1708
1942
  })()
1709
- }), _el$23);
1710
- insert(_el$21, createComponent(HR, {}), _el$23);
1711
- insertNode(_el$23, _el$24);
1712
- insertNode(_el$23, _el$25);
1713
- setProp(_el$23, "onMouseDown", () => setShowSessions((x) => !x));
1714
- setProp(_el$24, "attributes", 1);
1715
- insert(_el$24, () => `${showSessions() ? "▼" : "▶"} Sessions`);
1716
- insert(_el$25, () => ` (${String(totalSessions())})`);
1717
- insert(_el$21, createComponent(Show, {
1943
+ }), _el$24);
1944
+ insert(_el$22, createComponent(HR, {}), _el$24);
1945
+ insertNode(_el$24, _el$25);
1946
+ insertNode(_el$24, _el$26);
1947
+ setProp(_el$24, "onMouseDown", () => setShowSessions((x) => !x));
1948
+ setProp(_el$25, "attributes", 1);
1949
+ insert(_el$25, () => `${showSessions() ? "▼" : "▶"} Sessions`);
1950
+ insert(_el$26, () => ` (${String(totalSessions())})`);
1951
+ insert(_el$22, createComponent(Show, {
1718
1952
  get when() {
1719
1953
  return showSessions();
1720
1954
  },
@@ -1725,19 +1959,19 @@ function View(props) {
1725
1959
  },
1726
1960
  get fallback() {
1727
1961
  return (() => {
1728
- var _el$40 = createElement("box"), _el$41 = createElement("text");
1729
- insertNode(_el$40, _el$41);
1730
- setProp(_el$40, "marginLeft", 1);
1731
- insertNode(_el$41, createTextNode(`No recent sessions`));
1732
- effect((_$p) => setProp(_el$41, "fg", theme().textMuted, _$p));
1733
- return _el$40;
1962
+ var _el$41 = createElement("box"), _el$42 = createElement("text");
1963
+ insertNode(_el$41, _el$42);
1964
+ setProp(_el$41, "marginLeft", 1);
1965
+ insertNode(_el$42, createTextNode(`No recent sessions`));
1966
+ effect((_$p) => setProp(_el$42, "fg", theme().textMuted, _$p));
1967
+ return _el$41;
1734
1968
  })();
1735
1969
  },
1736
1970
  get children() {
1737
- var _el$26 = createElement("box");
1738
- setProp(_el$26, "marginLeft", 1);
1739
- setProp(_el$26, "flexDirection", "column");
1740
- insert(_el$26, createComponent(For, {
1971
+ var _el$27 = createElement("box");
1972
+ setProp(_el$27, "marginLeft", 1);
1973
+ setProp(_el$27, "flexDirection", "column");
1974
+ insert(_el$27, createComponent(For, {
1741
1975
  get each() {
1742
1976
  return recentSessions();
1743
1977
  },
@@ -1748,18 +1982,18 @@ function View(props) {
1748
1982
  session: ses
1749
1983
  })
1750
1984
  }));
1751
- return _el$26;
1985
+ return _el$27;
1752
1986
  }
1753
1987
  });
1754
1988
  }
1755
- }), _el$27);
1756
- insertNode(_el$27, _el$28);
1757
- insertNode(_el$27, _el$29);
1758
- setProp(_el$27, "onMouseDown", () => setShowDelegations((x) => !x));
1759
- setProp(_el$28, "attributes", 1);
1760
- insert(_el$28, () => `${showDelegations() ? "▼" : "▶"} Delegations`);
1761
- insert(_el$29, () => ` (${String(visibleDelegations().length)})`);
1762
- insert(_el$21, createComponent(Show, {
1989
+ }), _el$28);
1990
+ insertNode(_el$28, _el$29);
1991
+ insertNode(_el$28, _el$30);
1992
+ setProp(_el$28, "onMouseDown", () => setShowDelegations((x) => !x));
1993
+ setProp(_el$29, "attributes", 1);
1994
+ insert(_el$29, () => `${showDelegations() ? "▼" : "▶"} Delegations`);
1995
+ insert(_el$30, () => ` ${delegationHeader()}`);
1996
+ insert(_el$22, createComponent(Show, {
1763
1997
  get when() {
1764
1998
  return showDelegations();
1765
1999
  },
@@ -1770,19 +2004,19 @@ function View(props) {
1770
2004
  },
1771
2005
  get fallback() {
1772
2006
  return (() => {
1773
- var _el$43 = createElement("box"), _el$44 = createElement("text");
1774
- insertNode(_el$43, _el$44);
1775
- setProp(_el$43, "marginLeft", 1);
1776
- insertNode(_el$44, createTextNode(`No delegations`));
1777
- effect((_$p) => setProp(_el$44, "fg", theme().textMuted, _$p));
1778
- return _el$43;
2007
+ var _el$44 = createElement("box"), _el$45 = createElement("text");
2008
+ insertNode(_el$44, _el$45);
2009
+ setProp(_el$44, "marginLeft", 1);
2010
+ insertNode(_el$45, createTextNode(`No delegations`));
2011
+ effect((_$p) => setProp(_el$45, "fg", theme().textMuted, _$p));
2012
+ return _el$44;
1779
2013
  })();
1780
2014
  },
1781
2015
  get children() {
1782
- var _el$30 = createElement("box");
1783
- setProp(_el$30, "marginLeft", 1);
1784
- setProp(_el$30, "flexDirection", "column");
1785
- insert(_el$30, createComponent(For, {
2016
+ var _el$31 = createElement("box");
2017
+ setProp(_el$31, "marginLeft", 1);
2018
+ setProp(_el$31, "flexDirection", "column");
2019
+ insert(_el$31, createComponent(For, {
1786
2020
  get each() {
1787
2021
  return visibleDelegations();
1788
2022
  },
@@ -1798,19 +2032,79 @@ function View(props) {
1798
2032
  return animationNow();
1799
2033
  }
1800
2034
  })
1801
- }));
1802
- return _el$30;
2035
+ }), null);
2036
+ insert(_el$31, createComponent(Show, {
2037
+ get when() {
2038
+ return delegationSplit().archived.length > 0;
2039
+ },
2040
+ get children() {
2041
+ return (() => {
2042
+ const archived = delegationSplit().archived;
2043
+ const pageSize = 8;
2044
+ const pages = Math.ceil(archived.length / pageSize);
2045
+ const page = Math.min(archivedPage(), pages - 1);
2046
+ const slice = archived.slice(page * pageSize, page * pageSize + pageSize);
2047
+ return (() => {
2048
+ 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");
2049
+ insertNode(_el$47, _el$48);
2050
+ setProp(_el$47, "flexDirection", "column");
2051
+ insertNode(_el$48, _el$49);
2052
+ insertNode(_el$48, _el$51);
2053
+ insertNode(_el$48, _el$52);
2054
+ insertNode(_el$48, _el$53);
2055
+ setProp(_el$48, "flexDirection", "row");
2056
+ setProp(_el$48, "marginLeft", 0);
2057
+ insertNode(_el$49, createTextNode(`‹ `));
2058
+ setProp(_el$49, "onMouseDown", () => clampArchivedPage(page - 1, pages));
2059
+ insert(_el$51, () => `Archived (${archived.length}) `);
2060
+ insert(_el$52, `${page + 1}/${pages}`);
2061
+ insertNode(_el$53, createTextNode(` ›`));
2062
+ setProp(_el$53, "onMouseDown", () => clampArchivedPage(page + 1, pages));
2063
+ insert(_el$47, createComponent(For, {
2064
+ each: slice,
2065
+ children: (job) => createComponent(DelegationRow, {
2066
+ get api() {
2067
+ return props.api;
2068
+ },
2069
+ job,
2070
+ get now() {
2071
+ return now();
2072
+ },
2073
+ get animationNow() {
2074
+ return animationNow();
2075
+ }
2076
+ })
2077
+ }), null);
2078
+ effect((_p$) => {
2079
+ var _v$14 = theme().textMuted, _v$15 = theme().textMuted, _v$16 = theme().textMuted, _v$17 = theme().textMuted;
2080
+ _v$14 !== _p$.e && (_p$.e = setProp(_el$49, "fg", _v$14, _p$.e));
2081
+ _v$15 !== _p$.t && (_p$.t = setProp(_el$51, "fg", _v$15, _p$.t));
2082
+ _v$16 !== _p$.a && (_p$.a = setProp(_el$52, "fg", _v$16, _p$.a));
2083
+ _v$17 !== _p$.o && (_p$.o = setProp(_el$53, "fg", _v$17, _p$.o));
2084
+ return _p$;
2085
+ }, {
2086
+ e: void 0,
2087
+ t: void 0,
2088
+ a: void 0,
2089
+ o: void 0
2090
+ });
2091
+ return _el$47;
2092
+ })();
2093
+ })();
2094
+ }
2095
+ }), null);
2096
+ return _el$31;
1803
2097
  }
1804
2098
  });
1805
2099
  }
1806
2100
  }), null);
1807
2101
  effect((_p$) => {
1808
- var _v$9 = theme().accent, _v$0 = theme().text, _v$1 = theme().textMuted, _v$10 = theme().text, _v$11 = theme().textMuted;
1809
- _v$9 !== _p$.e && (_p$.e = setProp(_el$22, "fg", _v$9, _p$.e));
1810
- _v$0 !== _p$.t && (_p$.t = setProp(_el$24, "fg", _v$0, _p$.t));
1811
- _v$1 !== _p$.a && (_p$.a = setProp(_el$25, "fg", _v$1, _p$.a));
1812
- _v$10 !== _p$.o && (_p$.o = setProp(_el$28, "fg", _v$10, _p$.o));
1813
- _v$11 !== _p$.i && (_p$.i = setProp(_el$29, "fg", _v$11, _p$.i));
2102
+ var _v$8 = theme().accent, _v$9 = theme().text, _v$0 = theme().textMuted, _v$1 = theme().text, _v$10 = theme().textMuted;
2103
+ _v$8 !== _p$.e && (_p$.e = setProp(_el$23, "fg", _v$8, _p$.e));
2104
+ _v$9 !== _p$.t && (_p$.t = setProp(_el$25, "fg", _v$9, _p$.t));
2105
+ _v$0 !== _p$.a && (_p$.a = setProp(_el$26, "fg", _v$0, _p$.a));
2106
+ _v$1 !== _p$.o && (_p$.o = setProp(_el$29, "fg", _v$1, _p$.o));
2107
+ _v$10 !== _p$.i && (_p$.i = setProp(_el$30, "fg", _v$10, _p$.i));
1814
2108
  return _p$;
1815
2109
  }, {
1816
2110
  e: void 0,
@@ -1819,7 +2113,7 @@ function View(props) {
1819
2113
  o: void 0,
1820
2114
  i: void 0
1821
2115
  });
1822
- return _el$21;
2116
+ return _el$22;
1823
2117
  })();
1824
2118
  }
1825
2119
  const tui = (api, _options, _meta) => {
@@ -1839,6 +2133,8 @@ const tui = (api, _options, _meta) => {
1839
2133
  const props = event?.properties ?? {};
1840
2134
  const part = props.part ?? props.info?.part;
1841
2135
  if (part === void 0) return;
2136
+ const sample = extractToolActivity(part);
2137
+ if (sample !== null) trackToolActivity(latestToolActivity, sample.sessionID, sample.activity);
1842
2138
  if (reduceDelegationToolPart(liveStore.map, part)) liveStore.bump();
1843
2139
  }));
1844
2140
  } catch {}
@@ -1877,6 +2173,6 @@ const plugin = {
1877
2173
  setup: async () => {}
1878
2174
  };
1879
2175
  //#endregion
1880
- export { IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame, delegationTag, fmtElapsed, isValidSessionId, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, toDelegationEntry, tuiLogPath, visibleDelegationList };
2176
+ export { DELEGATION_DESCRIPTION_MAX, DELEGATION_ELAPSED_WIDTH, DELEGATION_STATE_GLYPHS, IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationIcon, delegationSpinnerFrame, delegationStateGlyph, delegationStateMarker, delegationStateTone, delegationTag, extractToolActivity, fmtElapsed, formatDelegationElapsed, formatDelegationHeader, formatDelegationIdentity, formatDelegationRow, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
1881
2177
 
1882
2178
  //# sourceMappingURL=tui.js.map