pantheon-opencode 1.5.0-beta.9 → 1.5.1
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.
- package/AGENTS.md +14 -63
- package/CHANGELOG.md +326 -0
- package/README.md +77 -29
- package/README.pt-BR.md +5 -6
- package/commands/pantheon.md +1 -35
- package/package.json +2 -3
- package/plugin.json +1 -1
- package/pyproject.toml +3 -5
- package/scripts/doctor.mjs +260 -5
- package/scripts/install/plugin.mjs +9 -12
- package/scripts/mcp_persistence_server.py +1 -1
- package/scripts/sync-tui.mjs +8 -11
- package/src/agents/apollo.md +1 -0
- package/src/agents/gaia.md +1 -0
- package/src/agents/mnemosyne.md +11 -1
- package/src/agents/zeus.md +10 -52
- package/src/instructions/memory-protocol.instructions.md +6 -3
- package/src/instructions/zeus-anti-stall.instructions.md +2 -4
- package/src/instructions/zeus-council-synthesis.instructions.md +3 -28
- package/src/instructions/zeus-timeout-retry.instructions.md +3 -28
- package/src/mcp/mcp_persistence_server.py +1 -1
- package/src/mcp/memory_mcp_server.py +81 -27
- package/src/pantheon/background-job-board.ts +4 -4
- package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
- package/src/pantheon/cost-command.ts +1 -2
- package/src/pantheon/delegation-enforce.ts +59 -14
- package/src/pantheon/goal-loop.ts +1 -1
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/idle-continuation.ts +4 -6
- package/src/pantheon/model-command.ts +1 -2
- package/src/pantheon/native-task-status.ts +2 -2
- package/src/pantheon/presets.d.mts +0 -20
- package/src/pantheon/presets.mjs +0 -64
- package/src/pantheon/session-guard.ts +2 -2
- package/src/pantheon/session-id.ts +30 -0
- package/src/pantheon/task-result-guard.ts +2 -3
- package/src/pantheon/todo-enforcer.ts +3 -3
- package/src/pantheon/tool-context.ts +23 -0
- package/src/pantheon/v2-bridge.ts +1 -4
- package/src/plugin-v2.ts +8 -21
- package/src/plugin.ts +61 -245
- package/src/plugins/pantheon-hooks.ts +2 -2
- package/src/plugins/tui/dist/tui.d.ts +229 -103
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +495 -333
- package/src/plugins/tui/package-lock.json +5 -2
- package/src/plugins/tui/package.json +5 -2
- package/src/plugins/tui/src/index.tsx +710 -409
- package/src/routing.yml +0 -16
- package/src/skills/context-compression/SKILL.md +1 -1
- package/scripts/generate-preset-docs.mjs +0 -300
- package/scripts/generate-prompts.sh +0 -222
- package/scripts/hash_verify.py +0 -192
- package/scripts/install/component.mjs +0 -24
- package/scripts/install/migrate-config.mjs +0 -302
- package/scripts/test-context-compression.sh +0 -169
- package/src/mcp/toon_codec.py +0 -491
- package/src/pantheon/auto-wake.ts +0 -85
- package/src/pantheon/chat-reminders.ts +0 -9
- package/src/pantheon/comment-checker.ts +0 -48
- package/src/pantheon/compaction-assert.ts +0 -160
- package/src/pantheon/compaction-handoff.ts +0 -304
- package/src/pantheon/cost-tracker.ts +0 -108
- package/src/pantheon/delegate-manager.ts +0 -766
- package/src/pantheon/delegation-classifier.ts +0 -307
- package/src/pantheon/delegation-finalize.ts +0 -406
- package/src/pantheon/delegation-notify.ts +0 -212
- package/src/pantheon/delegation-toast.ts +0 -73
- package/src/pantheon/delegation.ts +0 -1337
- package/src/pantheon/dispatch-guard.ts +0 -109
- package/src/pantheon/native-probe.ts +0 -234
- package/src/pantheon/permission-eval.ts +0 -284
- package/src/pantheon/preemptive-compact.ts +0 -76
- package/src/pantheon/provider-capabilities.ts +0 -221
- package/src/pantheon/provider-stream-tracker.ts +0 -182
- package/src/pantheon/retry-policy.ts +0 -212
- package/src/pantheon/step-cap.ts +0 -110
- package/src/pantheon/token-opt.ts +0 -689
- package/src/pantheon/toon-codec.ts +0 -481
- package/src/pantheon/v2-events.ts +0 -148
- package/src/pantheon/v2-hooks.ts +0 -287
- package/src/pantheon/v2-tool-definitions.ts +0 -331
- package/src/pantheon/zeus-delegate-with-retry.ts +0 -406
- package/src/plugins/tui/dist/tui.tsx +0 -2853
|
@@ -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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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.
|
|
168
|
+
return "1.5.1";
|
|
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
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
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
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
|
|
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
|
|
852
|
-
return
|
|
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
|
|
879
|
-
*
|
|
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
|
|
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:
|
|
887
|
-
|
|
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
|
-
/**
|
|
891
|
-
* existing tests): active jobs first, then the most
|
|
892
|
-
* reports (capped)
|
|
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
|
|
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: "
|
|
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
|
|
929
|
-
if (hours > 0) return `${hours}h
|
|
930
|
-
if (minutes > 0) return `${minutes}m
|
|
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:
|
|
934
|
-
*
|
|
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
|
|
@@ -942,7 +980,6 @@ function delegationActivity(entry) {
|
|
|
942
980
|
if (entry.state === "completed") return "completed";
|
|
943
981
|
if (entry.state === "error" || entry.state === "startup_failed" || entry.state === "startup_unknown") return "error";
|
|
944
982
|
if (entry.state === "cancelled") return "cancelled";
|
|
945
|
-
if (entry.read) return "reading";
|
|
946
983
|
if (entry.alias.startsWith("live-") && entry.taskID === void 0) return "delegating";
|
|
947
984
|
return "working";
|
|
948
985
|
}
|
|
@@ -951,7 +988,6 @@ function delegationActivityLabel(entry) {
|
|
|
951
988
|
switch (delegationActivity(entry)) {
|
|
952
989
|
case "delegating": return "DELEGATING";
|
|
953
990
|
case "working": return "WORKING";
|
|
954
|
-
case "reading": return "READING RESULT";
|
|
955
991
|
case "completed": return entry.timedOut ? "DONE (TIMED OUT)" : "DONE";
|
|
956
992
|
case "error": return "ERROR";
|
|
957
993
|
default: return "CANCELLED";
|
|
@@ -969,18 +1005,42 @@ const DELEGATION_SPINNER_FRAMES = [
|
|
|
969
1005
|
"⠇",
|
|
970
1006
|
"⠏"
|
|
971
1007
|
];
|
|
972
|
-
/** Return
|
|
973
|
-
|
|
974
|
-
|
|
1008
|
+
/** Return the spinner glyph for the given frame counter. The frame counter
|
|
1009
|
+
* is incremented every ~80ms by a dedicated animation timer, decoupled from
|
|
1010
|
+
* the data poll cycle. Pure — no side effects. */
|
|
1011
|
+
function delegationSpinnerFrame(frame) {
|
|
1012
|
+
const index = Math.abs(Math.floor(frame)) % DELEGATION_SPINNER_FRAMES.length;
|
|
975
1013
|
return DELEGATION_SPINNER_FRAMES[index] ?? DELEGATION_SPINNER_FRAMES[0];
|
|
976
1014
|
}
|
|
1015
|
+
/** Every state the row knows how to draw: the delegation lifecycle states
|
|
1016
|
+
* derived from the children status + md reports (`completed`, `error`,
|
|
1017
|
+
* `cancelled`, `startup_failed`, `startup_unknown`) plus the TUI-only
|
|
1018
|
+
* states (`retry`, `stale-running`). Fase 1 deliberately omits speculative
|
|
1019
|
+
* blocked/paused/scheduled/skipped. There is no `pending` display state: a
|
|
1020
|
+
* pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
|
|
1021
|
+
/** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
|
|
1022
|
+
* Kept separate + pure so the color channel is testable without booting the
|
|
1023
|
+
* renderer. Every display state resolves to one of the three status colors;
|
|
1024
|
+
* the row paints its whole content with it (see {@link DelegationRow}). */
|
|
1025
|
+
/** Status → color mapping for the whole-row tone. Red = failure, green =
|
|
1026
|
+
* terminal, yellow = in flight. The glyph remains a redundant channel so the
|
|
1027
|
+
* state stays legible in monochrome. Display-only; no behavior change. */
|
|
1028
|
+
function delegationStateTone(state) {
|
|
1029
|
+
switch (state) {
|
|
1030
|
+
case "running":
|
|
1031
|
+
case "retry":
|
|
1032
|
+
case "startup_unknown":
|
|
1033
|
+
case "stale-running": return "warning";
|
|
1034
|
+
case "error":
|
|
1035
|
+
case "startup_failed": return "error";
|
|
1036
|
+
case "completed":
|
|
1037
|
+
case "cancelled": return "success";
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
977
1040
|
/** Bounded tracker: child session id → latest running tool call. */
|
|
978
1041
|
const latestToolActivity = /* @__PURE__ */ new Map();
|
|
979
1042
|
const MAX_TOOL_ACTIVITY_ENTRIES = 200;
|
|
980
1043
|
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
1044
|
/** First usable summary string from common tool input fields. */
|
|
985
1045
|
function summarizeToolInput(input) {
|
|
986
1046
|
if (!input) return "";
|
|
@@ -1049,8 +1109,11 @@ function mergeChildDelegationSources(children, live) {
|
|
|
1049
1109
|
bySessionAlias.set(key(entry.sessionID, entry.alias), index);
|
|
1050
1110
|
});
|
|
1051
1111
|
for (const liveEntry of live) {
|
|
1052
|
-
|
|
1112
|
+
const aliaslessTTL = liveEntry.tool === "task" ? NATIVE_LIVE_ALIASLESS_TTL_MS : 3e4;
|
|
1113
|
+
if (liveEntry.alias === null && liveEntry.taskID === null && Date.now() - liveEntry.startedAt > aliaslessTTL) continue;
|
|
1053
1114
|
const incoming = toDelegationEntry(liveEntry);
|
|
1115
|
+
const isNativeLive = liveEntry.tool === "task" && liveEntry.alias === null;
|
|
1116
|
+
if (isNativeLive) incoming.source = "children-only";
|
|
1054
1117
|
const index = (incoming.taskID !== void 0 ? byTask.get(incoming.taskID) : void 0) ?? (incoming.alias !== "" ? bySessionAlias.get(key(incoming.sessionID, incoming.alias)) : void 0);
|
|
1055
1118
|
if (index === void 0) {
|
|
1056
1119
|
result.push(incoming);
|
|
@@ -1064,7 +1127,6 @@ function mergeChildDelegationSources(children, live) {
|
|
|
1064
1127
|
if (existing.state !== "running") {
|
|
1065
1128
|
if (liveEntry.alias !== null && existing.alias !== incoming.alias) existing.alias = incoming.alias;
|
|
1066
1129
|
if (incoming.agent !== "agent" && existing.agent !== incoming.agent) existing.agent = incoming.agent;
|
|
1067
|
-
if (incoming.read && !existing.read) existing.read = true;
|
|
1068
1130
|
continue;
|
|
1069
1131
|
}
|
|
1070
1132
|
result[index] = {
|
|
@@ -1077,30 +1139,26 @@ function mergeChildDelegationSources(children, live) {
|
|
|
1077
1139
|
state: incoming.state,
|
|
1078
1140
|
startedAt: Math.min(existing.startedAt, incoming.startedAt),
|
|
1079
1141
|
updatedAt: incoming.updatedAt,
|
|
1080
|
-
|
|
1081
|
-
source: "live"
|
|
1142
|
+
source: isNativeLive ? existing.source : "live"
|
|
1082
1143
|
};
|
|
1083
1144
|
}
|
|
1084
1145
|
result.sort(compareDelegationEntries);
|
|
1085
1146
|
return result;
|
|
1086
1147
|
}
|
|
1087
1148
|
/** Duck-typed subset of a tool part (SDK v2 `ToolPart` / `ToolState`). */
|
|
1088
|
-
/** One live delegation tracked in-memory, keyed by the
|
|
1149
|
+
/** One live delegation tracked in-memory, keyed by the task callID. */
|
|
1089
1150
|
/** Result of parsing one tool part into lifecycle-relevant fields. */
|
|
1090
|
-
/** Alias in the
|
|
1151
|
+
/** Alias in the task output: "Delegated to apollo: [apo-1] (task …)". */
|
|
1091
1152
|
const DELEGATE_ALIAS_PATTERN = /\[([a-z]{2,8}-\d+)\]/i;
|
|
1092
|
-
/** Child task id in the
|
|
1153
|
+
/** Child task id in the task output: "(task ses_child_9)". */
|
|
1093
1154
|
const DELEGATE_TASKID_PATTERN = /\(task\s+([a-z0-9_]+)\)/i;
|
|
1094
|
-
/** Plain alias, as passed to pantheon_delegation_read input.id: "apo-1". */
|
|
1095
|
-
const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i;
|
|
1096
1155
|
/** Extract the tool name + args from a `message.part.updated` part and
|
|
1097
1156
|
* reduce it to what the panel needs. Returns null for anything that is
|
|
1098
|
-
* not
|
|
1099
|
-
*
|
|
1100
|
-
* or is missing its callID. */
|
|
1157
|
+
* not the native `task` subagent tool (parentID === caller mechanism —
|
|
1158
|
+
* its children render `nat:`), or is missing its callID. */
|
|
1101
1159
|
function parseDelegationToolPart(part, now = Date.now()) {
|
|
1102
1160
|
if (part.type !== "tool") return null;
|
|
1103
|
-
if (part.tool !== "
|
|
1161
|
+
if (part.tool !== "task") return null;
|
|
1104
1162
|
const callID = part.callID;
|
|
1105
1163
|
if (callID === void 0 || callID === "") return null;
|
|
1106
1164
|
const sessionID = part.sessionID ?? "";
|
|
@@ -1110,28 +1168,6 @@ function parseDelegationToolPart(part, now = Date.now()) {
|
|
|
1110
1168
|
const input = state.input ?? {};
|
|
1111
1169
|
const startedAt = state.time?.start ?? now;
|
|
1112
1170
|
const endAt = state.time?.end ?? null;
|
|
1113
|
-
if (part.tool === "pantheon_delegation_read") {
|
|
1114
|
-
const target = typeof input.id === "string" ? input.id : null;
|
|
1115
|
-
let alias = null;
|
|
1116
|
-
let taskID = null;
|
|
1117
|
-
if (target !== null) {
|
|
1118
|
-
if (READ_ALIAS_PATTERN.test(target)) alias = target;
|
|
1119
|
-
else if (target.startsWith("ses_")) taskID = target;
|
|
1120
|
-
}
|
|
1121
|
-
return {
|
|
1122
|
-
callID,
|
|
1123
|
-
partID: part.id ?? "",
|
|
1124
|
-
sessionID,
|
|
1125
|
-
tool: "pantheon_delegation_read",
|
|
1126
|
-
agent: null,
|
|
1127
|
-
description: "",
|
|
1128
|
-
status,
|
|
1129
|
-
alias,
|
|
1130
|
-
taskID,
|
|
1131
|
-
startedAt,
|
|
1132
|
-
endAt
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
1171
|
const agent = typeof input.agent === "string" ? input.agent : typeof input.subagent_type === "string" ? input.subagent_type : "agent";
|
|
1136
1172
|
const description = typeof input.description === "string" ? input.description : typeof input.prompt === "string" ? input.prompt.slice(0, 120) : "";
|
|
1137
1173
|
let alias = null;
|
|
@@ -1145,7 +1181,7 @@ function parseDelegationToolPart(part, now = Date.now()) {
|
|
|
1145
1181
|
callID,
|
|
1146
1182
|
partID: part.id ?? "",
|
|
1147
1183
|
sessionID,
|
|
1148
|
-
tool:
|
|
1184
|
+
tool: "task",
|
|
1149
1185
|
agent,
|
|
1150
1186
|
description,
|
|
1151
1187
|
status,
|
|
@@ -1155,38 +1191,11 @@ function parseDelegationToolPart(part, now = Date.now()) {
|
|
|
1155
1191
|
endAt
|
|
1156
1192
|
};
|
|
1157
1193
|
}
|
|
1158
|
-
/** Find a live entry by alias or taskID (read parts resolve by id). */
|
|
1159
|
-
function findLiveByTarget(map, alias, taskID) {
|
|
1160
|
-
if (alias === null && taskID === null) return void 0;
|
|
1161
|
-
for (const entry of map.values()) {
|
|
1162
|
-
if (alias !== null && entry.alias === alias) return entry;
|
|
1163
|
-
if (taskID !== null && entry.taskID === taskID) return entry;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
1194
|
/** Apply one tool part to the live map. Returns true when the map changed.
|
|
1167
1195
|
* Pure w.r.t. I/O — only mutates `map`. */
|
|
1168
1196
|
function reduceDelegationToolPart(map, part, now = Date.now()) {
|
|
1169
1197
|
const parsed = parseDelegationToolPart(part, now);
|
|
1170
1198
|
if (parsed === null) return false;
|
|
1171
|
-
if (parsed.tool === "pantheon_delegation_read") {
|
|
1172
|
-
const target = findLiveByTarget(map, parsed.alias, parsed.taskID);
|
|
1173
|
-
if (target === void 0) return false;
|
|
1174
|
-
let changed = false;
|
|
1175
|
-
if (!target.read) {
|
|
1176
|
-
target.read = true;
|
|
1177
|
-
changed = true;
|
|
1178
|
-
}
|
|
1179
|
-
if (parsed.status === "completed" && target.state === "running") {
|
|
1180
|
-
target.state = "completed";
|
|
1181
|
-
target.updatedAt = parsed.endAt ?? now;
|
|
1182
|
-
changed = true;
|
|
1183
|
-
} else if (parsed.status === "error" && target.state === "running") {
|
|
1184
|
-
target.state = "error";
|
|
1185
|
-
target.updatedAt = parsed.endAt ?? now;
|
|
1186
|
-
changed = true;
|
|
1187
|
-
}
|
|
1188
|
-
return changed;
|
|
1189
|
-
}
|
|
1190
1199
|
const existing = map.get(parsed.callID);
|
|
1191
1200
|
if (parsed.status === "error") {
|
|
1192
1201
|
if (existing !== void 0 && existing.state === "error" && existing.updatedAt === parsed.endAt) return false;
|
|
@@ -1195,14 +1204,13 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
|
|
|
1195
1204
|
partID: parsed.partID,
|
|
1196
1205
|
sessionID: parsed.sessionID,
|
|
1197
1206
|
tool: parsed.tool,
|
|
1198
|
-
agent: parsed.agent
|
|
1207
|
+
agent: parsed.agent,
|
|
1199
1208
|
description: parsed.description,
|
|
1200
1209
|
alias: existing?.alias ?? null,
|
|
1201
1210
|
taskID: existing?.taskID ?? null,
|
|
1202
1211
|
state: "error",
|
|
1203
1212
|
startedAt: existing?.startedAt ?? parsed.startedAt,
|
|
1204
|
-
updatedAt: parsed.endAt ?? now
|
|
1205
|
-
read: existing?.read ?? false
|
|
1213
|
+
updatedAt: parsed.endAt ?? now
|
|
1206
1214
|
});
|
|
1207
1215
|
return true;
|
|
1208
1216
|
}
|
|
@@ -1212,14 +1220,13 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
|
|
|
1212
1220
|
partID: parsed.partID,
|
|
1213
1221
|
sessionID: parsed.sessionID,
|
|
1214
1222
|
tool: parsed.tool,
|
|
1215
|
-
agent: parsed.agent
|
|
1223
|
+
agent: parsed.agent,
|
|
1216
1224
|
description: parsed.description,
|
|
1217
1225
|
alias: parsed.alias,
|
|
1218
1226
|
taskID: parsed.taskID,
|
|
1219
1227
|
state: "running",
|
|
1220
1228
|
startedAt: parsed.startedAt,
|
|
1221
|
-
updatedAt: null
|
|
1222
|
-
read: false
|
|
1229
|
+
updatedAt: null
|
|
1223
1230
|
});
|
|
1224
1231
|
return true;
|
|
1225
1232
|
}
|
|
@@ -1256,14 +1263,14 @@ function removeDelegationEntry(map, partIDOrCallID) {
|
|
|
1256
1263
|
}
|
|
1257
1264
|
return false;
|
|
1258
1265
|
}
|
|
1259
|
-
/** Collect
|
|
1266
|
+
/** Collect native task tool parts from a session's messages.
|
|
1260
1267
|
* Messages may carry their parts inline (duck-typed `msg.parts`); when
|
|
1261
1268
|
* they don't, the optional `getParts(messageID)` callback is used (the TUI
|
|
1262
1269
|
* SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
|
|
1263
|
-
* child session with parentID = caller
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1270
|
+
* child session with parentID = caller, so its parts feed the live-map as
|
|
1271
|
+
* the refresh signal (rows come from the children channel). Pure w.r.t.
|
|
1272
|
+
* I/O — used by the mount re-scan to re-seed the live map after
|
|
1273
|
+
* compaction/attach. */
|
|
1267
1274
|
function collectDelegationToolParts(messages, getParts) {
|
|
1268
1275
|
const out = [];
|
|
1269
1276
|
for (const msg of messages ?? []) {
|
|
@@ -1273,7 +1280,7 @@ function collectDelegationToolParts(messages, getParts) {
|
|
|
1273
1280
|
if (!parts) continue;
|
|
1274
1281
|
for (const raw of parts) {
|
|
1275
1282
|
const part = raw;
|
|
1276
|
-
if (part?.type === "tool" &&
|
|
1283
|
+
if (part?.type === "tool" && part.tool === "task") out.push(part);
|
|
1277
1284
|
}
|
|
1278
1285
|
}
|
|
1279
1286
|
return out;
|
|
@@ -1288,7 +1295,7 @@ function seedLiveDelegationMap(map, parts, now = Date.now()) {
|
|
|
1288
1295
|
return changed;
|
|
1289
1296
|
}
|
|
1290
1297
|
/** Convert a live entry into the shared display shape. Alias falls back to
|
|
1291
|
-
* a `live-<callID>` prefix while the
|
|
1298
|
+
* a `live-<callID>` prefix while the task call has not completed yet. */
|
|
1292
1299
|
function toDelegationEntry(live) {
|
|
1293
1300
|
return {
|
|
1294
1301
|
alias: live.alias ?? `live-${live.callID.slice(0, 8)}`,
|
|
@@ -1300,7 +1307,6 @@ function toDelegationEntry(live) {
|
|
|
1300
1307
|
updatedAt: live.updatedAt,
|
|
1301
1308
|
timedOut: false,
|
|
1302
1309
|
description: live.description,
|
|
1303
|
-
read: live.read,
|
|
1304
1310
|
source: "live"
|
|
1305
1311
|
};
|
|
1306
1312
|
}
|
|
@@ -1333,6 +1339,26 @@ function mergeDelegationSources(live, md) {
|
|
|
1333
1339
|
all.sort(compareDelegationEntries);
|
|
1334
1340
|
return all;
|
|
1335
1341
|
}
|
|
1342
|
+
/** Scope a fully-merged display list to the ACTIVE session only.
|
|
1343
|
+
*
|
|
1344
|
+
* The panel is session-scoped: rows from other sessions (md history under
|
|
1345
|
+
* `.pantheon/delegations/<other-session>/`) and rows with no
|
|
1346
|
+
* attributable session (empty sessionID) are DROPPED. The previous
|
|
1347
|
+
* cross-session behavior rendered those rows and clicking them led to
|
|
1348
|
+
* "Session not found" — there is no cross-session channel anymore.
|
|
1349
|
+
*
|
|
1350
|
+
* The md channel still feeds the merge UNFILTERED so it can ENRICH an
|
|
1351
|
+
* active-session row with state/alias/agent (dedup by taskID), but it can
|
|
1352
|
+
* never introduce a row for another session: this filter is applied
|
|
1353
|
+
* ONCE, after every merge (children + live + md).
|
|
1354
|
+
*
|
|
1355
|
+
* Returns [] when no active session resolves (null/placeholder) — there is no
|
|
1356
|
+
* scope to show. Native children always carry the active session id (stamped
|
|
1357
|
+
* by `childrenToDelegationEntries`), so they survive the filter. Pure. */
|
|
1358
|
+
function filterDelegationsToSession(entries, activeSessionID) {
|
|
1359
|
+
if (!isValidSessionId(activeSessionID)) return [];
|
|
1360
|
+
return entries.filter((entry) => entry.sessionID === activeSessionID);
|
|
1361
|
+
}
|
|
1336
1362
|
/** Server-aligned session id validity: opencode rejects anything not starting
|
|
1337
1363
|
* with "ses" (SchemaError). This deliberately mirrors that exact contract —
|
|
1338
1364
|
* nothing stricter, nothing looser — so a template placeholder ("{sessionID}"),
|
|
@@ -1362,45 +1388,202 @@ function resolveCurrentSessionID(sources) {
|
|
|
1362
1388
|
return null;
|
|
1363
1389
|
}
|
|
1364
1390
|
/** THE single choke point for every `session.children` / session-API path.
|
|
1365
|
-
* Returns
|
|
1366
|
-
* null for anything else (placeholder,
|
|
1367
|
-
* skips the call entirely instead of
|
|
1368
|
-
* (the "%7BsessionID%7D" regression).
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1391
|
+
* Returns the v2 SDK parameter shape `{ sessionID }` ONLY for a
|
|
1392
|
+
* server-valid session id; returns null for anything else (placeholder,
|
|
1393
|
+
* empty, foreign id) so the caller skips the call entirely instead of
|
|
1394
|
+
* sending an unsubstituted placeholder (the "%7BsessionID%7D" regression).
|
|
1395
|
+
* The TUI client is `@opencode-ai/sdk/v2`, whose session methods take a
|
|
1396
|
+
* FLAT parameter object (`{ sessionID }`), NOT the v1 `{ path: { id } }`
|
|
1397
|
+
* envelope — passing the v1 shape left the v2 `{sessionID}` URL template
|
|
1398
|
+
* unsubstituted (`/session/%7BsessionID%7D/children`). Every session-API
|
|
1399
|
+
* call site MUST go through this function (enforced by the source-scan
|
|
1400
|
+
* test in tests/pantheon/tui-delegations.test.ts). */
|
|
1371
1401
|
function safeSessionPath(id) {
|
|
1372
1402
|
if (!isValidSessionId(id)) return null;
|
|
1373
|
-
return {
|
|
1403
|
+
return { sessionID: id };
|
|
1374
1404
|
}
|
|
1375
|
-
/** Build the `session.children`
|
|
1405
|
+
/** Build the `session.children` parameters ONLY from a validated session id.
|
|
1376
1406
|
* Delegates to {@link safeSessionPath} — the single choke point. Returns
|
|
1377
|
-
* null for null/invalid ids so the caller
|
|
1378
|
-
* sending an unsubstituted placeholder (the
|
|
1407
|
+
* the v2 SDK shape `{ sessionID }`; null for null/invalid ids so the caller
|
|
1408
|
+
* skips the fetch instead of sending an unsubstituted placeholder (the
|
|
1409
|
+
* "%7BsessionID%7D" regression). */
|
|
1379
1410
|
function buildChildrenPath(id) {
|
|
1380
1411
|
return safeSessionPath(id);
|
|
1381
1412
|
}
|
|
1382
1413
|
/** Duck-typed subset of a child Session (+ its live status type). */
|
|
1383
|
-
/** Map a child status type to a display state.
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
|
|
1388
|
-
|
|
1414
|
+
/** Map a child status type to a display state.
|
|
1415
|
+
*
|
|
1416
|
+
* Only `busy`/`retry` are EXPLICIT live states. `idle` is terminal and so is
|
|
1417
|
+
* any other/unknown status — a status the panel does not recognise is not
|
|
1418
|
+
* evidence of activity. An ABSENT status is the historical case: the status
|
|
1419
|
+
* map (`api.state.session.status`) contains only currently-alive sessions, so
|
|
1420
|
+
* every finished child of a long session is missing from it. Defaulting that
|
|
1421
|
+
* to running made the whole session history render as "active" (the
|
|
1422
|
+
* "197 children, all active" regression), so an absent status is terminal
|
|
1423
|
+
* (done) UNLESS the child was active within `graceMs` — a just-spawned child
|
|
1424
|
+
* that the status API has not registered yet must still render as running.
|
|
1425
|
+
*
|
|
1426
|
+
* @param status the `api.state.session.status(id)?.type`, or undefined
|
|
1427
|
+
* @param time the child session time fields (`created`/`updated`)
|
|
1428
|
+
* @param now current wall-clock ms (injectable for tests)
|
|
1429
|
+
* @param graceMs recency window for the absent-status running assumption
|
|
1430
|
+
* Pure — no I/O. */
|
|
1431
|
+
function childStatusToState(status, time, now = Date.now(), graceMs = DELEGATION_CHILD_STATUS_GRACE_MS) {
|
|
1389
1432
|
if (status === "retry") return "retry";
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
*
|
|
1433
|
+
if (status === "busy") {
|
|
1434
|
+
if (time) {
|
|
1435
|
+
const lastActivity = Math.max(time.updated ?? 0, time.created ?? 0);
|
|
1436
|
+
if (lastActivity > 0 && now - lastActivity > 18e5) return "completed";
|
|
1437
|
+
}
|
|
1438
|
+
return "running";
|
|
1439
|
+
}
|
|
1440
|
+
if (status !== void 0) return "completed";
|
|
1441
|
+
const lastActivity = Math.max(time?.updated ?? 0, time?.created ?? 0);
|
|
1442
|
+
if (lastActivity > 0 && now - lastActivity < graceMs) return "running";
|
|
1443
|
+
return "completed";
|
|
1444
|
+
}
|
|
1445
|
+
/** Status-only row model: ONE glyph + ONE identity per row.
|
|
1446
|
+
*
|
|
1447
|
+
* A row is
|
|
1448
|
+
* `{glyph} {alias:7} {elapsed:>5}` plus a muted description line. The short
|
|
1449
|
+
* alias (`apo-1`) is the single identity; rows without a report alias show
|
|
1450
|
+
* the agent instead (never both). */
|
|
1451
|
+
/** Map every FSM/display state to one of the 4 row kinds. reconciled never
|
|
1452
|
+
* reaches the panel (the md parser drops it) but reads as done;
|
|
1453
|
+
* cancelled reads as done; startup_failed reads as failed while
|
|
1454
|
+
* startup_unknown (no error known) reads as retry. Pure. */
|
|
1455
|
+
function delegationRowStatus(state) {
|
|
1456
|
+
switch (state) {
|
|
1457
|
+
case "running":
|
|
1458
|
+
case "stale-running": return "active";
|
|
1459
|
+
case "retry":
|
|
1460
|
+
case "startup_unknown": return "retry";
|
|
1461
|
+
case "completed":
|
|
1462
|
+
case "cancelled": return "done";
|
|
1463
|
+
case "error":
|
|
1464
|
+
case "startup_failed": return "failed";
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
/** The only 4 glyphs a row may show: animated active, done, failed, retry.
|
|
1468
|
+
* Shape is redundant with color (never the only signal). Pure. */
|
|
1469
|
+
const DELEGATION_ROW_GLYPHS = {
|
|
1470
|
+
active: "⠋",
|
|
1471
|
+
done: "✓",
|
|
1472
|
+
failed: "✕",
|
|
1473
|
+
retry: "⟳"
|
|
1474
|
+
};
|
|
1475
|
+
function delegationRowGlyph(status) {
|
|
1476
|
+
return DELEGATION_ROW_GLYPHS[status];
|
|
1477
|
+
}
|
|
1478
|
+
/** Row marker (`<glyph> `) — the single state channel. `active` animates
|
|
1479
|
+
* through the spinner for the given frame counter (incremented every ~80ms);
|
|
1480
|
+
* every other kind is static. Pure. */
|
|
1481
|
+
function delegationRowMarker(state, frame = 0) {
|
|
1482
|
+
const status = delegationRowStatus(state);
|
|
1483
|
+
return `${status === "active" ? delegationSpinnerFrame(frame) : delegationRowGlyph(status)} `;
|
|
1484
|
+
}
|
|
1485
|
+
/** ONE identity per row: the short report alias (`apo-1`); a row without a
|
|
1486
|
+
* report alias (native task() child `native-task` / `native-<id>`, or an
|
|
1487
|
+
* alias-less native live row `live-<callID>` kept as children-only) shows
|
|
1488
|
+
* the agent instead — never alias + agent stacked. Pure. */
|
|
1489
|
+
function delegationRowIdentity(entry) {
|
|
1490
|
+
if (entry.agent === "") return entry.alias;
|
|
1491
|
+
if (entry.alias === "native-task" || entry.alias.startsWith("native-")) return entry.agent;
|
|
1492
|
+
if (entry.alias.startsWith("live-") && entry.source === "children-only") return entry.agent;
|
|
1493
|
+
return entry.alias;
|
|
1494
|
+
}
|
|
1495
|
+
/** Fixed alias cell, pad-right + hard-truncate to `width` (default 7) so
|
|
1496
|
+
* identities line up across rows. Pure. */
|
|
1497
|
+
const DELEGATION_ALIAS_WIDTH = 7;
|
|
1498
|
+
function formatDelegationAlias(identity, width = 7) {
|
|
1499
|
+
if (identity.length >= width) return identity.slice(0, width);
|
|
1500
|
+
return `${identity}${" ".repeat(width - identity.length)}`;
|
|
1501
|
+
}
|
|
1502
|
+
/** Max description width on the row detail line — the old 180-char slice
|
|
1503
|
+
* wrapped the sidebar; 44 keeps one readable line. */
|
|
1504
|
+
const DELEGATION_DESCRIPTION_MAX = 44;
|
|
1505
|
+
/** Lazily-built grapheme segmenter. `Intl.Segmenter` keeps ZWJ emoji families
|
|
1506
|
+
* (and skin-tone/variation sequences) intact; `Array.from` (code points) is
|
|
1507
|
+
* the fallback for runtimes without it. */
|
|
1508
|
+
let graphemeSegmenter;
|
|
1509
|
+
/** Split `text` into display graphemes so truncation never cuts a multi-unit
|
|
1510
|
+
* emoji in half (surrogate pair → mojibake). */
|
|
1511
|
+
function splitGraphemes(text) {
|
|
1512
|
+
if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
|
|
1513
|
+
graphemeSegmenter ??= new Intl.Segmenter("pt", { granularity: "grapheme" });
|
|
1514
|
+
return Array.from(graphemeSegmenter.segment(text), (s) => s.segment);
|
|
1515
|
+
}
|
|
1516
|
+
return Array.from(text);
|
|
1517
|
+
}
|
|
1518
|
+
/** Truncate a description to `max` graphemes appending `…` when cut. Exact
|
|
1519
|
+
* `max`-length text is left untouched. Grapheme-granular, so an emoji made of
|
|
1520
|
+
* several code points is never split into mojibake at the boundary. Pure. */
|
|
1521
|
+
function truncateDelegationDescription(text, max = 44) {
|
|
1522
|
+
const graphemes = splitGraphemes(text);
|
|
1523
|
+
if (graphemes.length <= max) return text;
|
|
1524
|
+
return `${graphemes.slice(0, Math.max(0, max - 1)).join("")}\u2026`;
|
|
1525
|
+
}
|
|
1526
|
+
/** Fixed elapsed-time box: right-aligned to `width` (default 5) so elapsed
|
|
1527
|
+
* values line up across rows, e.g. ` 12s`. A label that already fills
|
|
1528
|
+
* the box is returned untouched. Pure. */
|
|
1529
|
+
const DELEGATION_ELAPSED_WIDTH = 5;
|
|
1530
|
+
function formatDelegationElapsed(entry, now, width = 5) {
|
|
1531
|
+
const label = delegationElapsed(entry, now);
|
|
1532
|
+
if (label.length >= width) return label;
|
|
1533
|
+
return `${" ".repeat(width - label.length)}${label}`;
|
|
1534
|
+
}
|
|
1535
|
+
/** Colored left half of a row line: `<marker><alias:7> ` — the state glyph
|
|
1536
|
+
* plus the single identity, padded so the muted elapsed column aligns. The
|
|
1537
|
+
* row renders this colored lead separately from the muted elapsed tail. Pure. */
|
|
1538
|
+
function formatDelegationRowLead(entry, marker) {
|
|
1539
|
+
return `${marker}${formatDelegationAlias(delegationRowIdentity(entry))} `;
|
|
1540
|
+
}
|
|
1541
|
+
/** Visible-row ceiling for the panel: the remainder collapses into a single
|
|
1542
|
+
* "… +N more" line. Live rows render first, so a running job is never hidden
|
|
1543
|
+
* by the cap. */
|
|
1544
|
+
const DELEGATION_VISIBLE_CEILING = 8;
|
|
1545
|
+
/** Header summary: `(N active · M done)` plus `· K failed` only when K > 0.
|
|
1546
|
+
* Active counts running/retry AND display-only stale-running (a stale row is
|
|
1547
|
+
* still a live job — it must never read as done); failed counts
|
|
1548
|
+
* error/startup_failed; cancelled reads as done. Pure. */
|
|
1549
|
+
function formatDelegationHeader(entries) {
|
|
1550
|
+
let active = 0;
|
|
1551
|
+
let done = 0;
|
|
1552
|
+
let failed = 0;
|
|
1553
|
+
for (const e of entries) switch (delegationRowStatus(e.state)) {
|
|
1554
|
+
case "active":
|
|
1555
|
+
case "retry":
|
|
1556
|
+
active++;
|
|
1557
|
+
break;
|
|
1558
|
+
case "failed":
|
|
1559
|
+
failed++;
|
|
1560
|
+
break;
|
|
1561
|
+
case "done":
|
|
1562
|
+
done++;
|
|
1563
|
+
break;
|
|
1564
|
+
}
|
|
1565
|
+
const tail = failed > 0 ? ` · ${failed} failed` : "";
|
|
1566
|
+
return `(${active} active · ${done} done${tail})`;
|
|
1567
|
+
}
|
|
1568
|
+
/** Cap the panel: live rows first, then most-recent retained terminal rows, at
|
|
1569
|
+
* most `maxVisible` total. Hidden counts describe the retained render list,
|
|
1570
|
+
* not expired history. */
|
|
1571
|
+
function ceilingDelegationList(all, maxVisible = 8, now = Date.now()) {
|
|
1572
|
+
const { active, recent } = splitDelegationList(all, all.length, now);
|
|
1573
|
+
const visibleActive = active.slice(0, maxVisible);
|
|
1574
|
+
const visible = [...visibleActive, ...recent.slice(0, Math.max(0, maxVisible - visibleActive.length))];
|
|
1575
|
+
const hiddenActive = Math.max(0, active.length - visibleActive.length);
|
|
1576
|
+
const hiddenTerminal = Math.max(0, recent.length - (visible.length - visibleActive.length));
|
|
1577
|
+
return {
|
|
1578
|
+
visible,
|
|
1579
|
+
hidden: hiddenActive + hiddenTerminal,
|
|
1580
|
+
hiddenActive,
|
|
1581
|
+
hiddenTerminal
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
/** Split a display list into native task() rows vs pantheon report rows.
|
|
1585
|
+
* Native = source 'children-only' (no delegate report); everything else counts
|
|
1586
|
+
* as pantheon. Pure — powers the hooks.log line. */
|
|
1404
1587
|
function countDelegationSources(entries) {
|
|
1405
1588
|
let native = 0;
|
|
1406
1589
|
for (const e of entries) if (e.source === "children-only") native++;
|
|
@@ -1411,7 +1594,7 @@ function countDelegationSources(entries) {
|
|
|
1411
1594
|
};
|
|
1412
1595
|
}
|
|
1413
1596
|
/** Diagnostic hooks.log line for a panel re-fetch, with the children
|
|
1414
|
-
* breakdown (pantheon = children WITH a
|
|
1597
|
+
* breakdown (pantheon = children WITH a delegate report, native = children
|
|
1415
1598
|
* WITHOUT one). Pure — the View logs the returned string verbatim. */
|
|
1416
1599
|
function formatPanelLogLine(children, pantheon, native, md, events) {
|
|
1417
1600
|
return `panel: children=${children}(pantheon=${pantheon} native=${native}) md=${md} events=${events}`;
|
|
@@ -1423,17 +1606,39 @@ function formatPanelLogLine(children, pantheon, native, md, events) {
|
|
|
1423
1606
|
* report still renders: description from its title, agent from the child
|
|
1424
1607
|
* itself (fallback 'agent'), state derived from its status, startedAt from
|
|
1425
1608
|
* time.created. A report-less child is a NATIVE task() child (every
|
|
1426
|
-
* child of the current session —
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1430
|
-
* non-empty when the child carries no title.
|
|
1431
|
-
*
|
|
1609
|
+
* child of the current session — the native `task()` tool — carries
|
|
1610
|
+
* parentID = caller), so it gets source 'children-only', a
|
|
1611
|
+
* per-child alias 'native-<last4 of the child id>' (one identity per native
|
|
1612
|
+
* row) instead of a report alias. The 'task nativa' description fallback
|
|
1613
|
+
* keeps the row non-empty when the child carries no title.
|
|
1614
|
+
* A FRESH terminal md state wins over the derived state (a stale md report
|
|
1615
|
+
* from a previous incarnation never flips a live child — see
|
|
1616
|
+
* mdTerminalCoversChild); a running md defers to
|
|
1432
1617
|
* the child's live status. A running child is NEVER archived — it always
|
|
1433
1618
|
* lands in the active split (splitDelegationList active = running/retry).
|
|
1434
|
-
*
|
|
1619
|
+
* `parentSessionID` (the focused session id) is stamped on report-less
|
|
1620
|
+
* children so the row carries its origin session; omit it and they stay
|
|
1621
|
+
* unscoped ('').
|
|
1622
|
+
* Children outside {@link DELEGATION_CHILDREN_RECENCY_MS} are dropped before
|
|
1623
|
+
* they can enter the list (the per-session child list grows for the whole
|
|
1624
|
+
* session lifetime); the survivors are sorted running-first, most recent
|
|
1625
|
+
* first (compareDelegationEntries), so a ceiling hides the OLDEST rows.
|
|
1626
|
+
* Pure — no I/O. */
|
|
1627
|
+
/** Temporal guard for the children channel (same recycled-alias class as the
|
|
1628
|
+
* md merge): an md terminal report matched by taskID may still belong to
|
|
1629
|
+
* a PREVIOUS job incarnation. Accept it only when its Finalized timestamp
|
|
1630
|
+
* covers the child's latest activity, when either side has no timestamp to
|
|
1631
|
+
* compare, or when both startedAt agree on a single job incarnation.
|
|
1435
1632
|
* Pure — no I/O. */
|
|
1436
|
-
function
|
|
1633
|
+
function mdTerminalCoversChild(md, child) {
|
|
1634
|
+
const childUpdated = child.time?.updated;
|
|
1635
|
+
if (md.updatedAt !== null && childUpdated !== void 0) {
|
|
1636
|
+
if (md.updatedAt >= childUpdated) return true;
|
|
1637
|
+
return md.startedAt === child.time?.created;
|
|
1638
|
+
}
|
|
1639
|
+
return true;
|
|
1640
|
+
}
|
|
1641
|
+
function childrenToDelegationEntries(children, md, now = Date.now(), parentSessionID = "") {
|
|
1437
1642
|
const byTaskID = /* @__PURE__ */ new Map();
|
|
1438
1643
|
for (const m of md) if (m.taskID !== void 0 && !byTaskID.has(m.taskID)) byTaskID.set(m.taskID, m);
|
|
1439
1644
|
const out = [];
|
|
@@ -1442,10 +1647,12 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
|
|
|
1442
1647
|
if (child.id === "" || seen.has(child.id)) continue;
|
|
1443
1648
|
seen.add(child.id);
|
|
1444
1649
|
const mdEntry = byTaskID.get(child.id);
|
|
1445
|
-
const
|
|
1650
|
+
const lastActivity = Math.max(child.time?.updated ?? 0, child.time?.created ?? 0, mdEntry?.updatedAt ?? 0);
|
|
1651
|
+
if (lastActivity > 0 && now - lastActivity > 864e5) continue;
|
|
1652
|
+
const state = mdEntry !== void 0 && mdEntry.state !== "running" && mdTerminalCoversChild(mdEntry, child) ? mdEntry.state : childStatusToState(child.status, child.time, now);
|
|
1446
1653
|
out.push({
|
|
1447
|
-
alias: mdEntry?.alias ??
|
|
1448
|
-
sessionID: mdEntry?.sessionID ??
|
|
1654
|
+
alias: mdEntry?.alias ?? `native-${child.id.slice(-4)}`,
|
|
1655
|
+
sessionID: mdEntry?.sessionID ?? parentSessionID,
|
|
1449
1656
|
taskID: child.id,
|
|
1450
1657
|
agent: mdEntry?.agent ?? child.agent ?? "agent",
|
|
1451
1658
|
state,
|
|
@@ -1466,8 +1673,19 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
|
|
|
1466
1673
|
* unsubstituted "{sessionID}" placeholder can never be routed. */
|
|
1467
1674
|
function navigateToDelegationSession(route, taskID) {
|
|
1468
1675
|
if (typeof route?.navigate !== "function" || !isValidSessionId(taskID)) return false;
|
|
1469
|
-
|
|
1470
|
-
|
|
1676
|
+
try {
|
|
1677
|
+
const navigation = route.navigate("session", { sessionID: taskID });
|
|
1678
|
+
if (navigation !== void 0) Promise.resolve(navigation).catch(() => void 0);
|
|
1679
|
+
return true;
|
|
1680
|
+
} catch {
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
/** Build the mouse handler used by each delegation row. */
|
|
1685
|
+
function createDelegationRowOpenHandler(route, taskID) {
|
|
1686
|
+
return () => {
|
|
1687
|
+
navigateToDelegationSession(route, taskID);
|
|
1688
|
+
};
|
|
1471
1689
|
}
|
|
1472
1690
|
/** Silence-by-default panel logger.
|
|
1473
1691
|
* @param projectRoot the PROJECT ROOT — the logger appends to
|
|
@@ -1495,7 +1713,11 @@ function createTuiLogger(projectRoot, module = "pantheon-tui") {
|
|
|
1495
1713
|
await mkdir(dirname(logPath), { recursive: true });
|
|
1496
1714
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1497
1715
|
await appendFile(logPath, `${lines.map((l) => `[${stamp}] [${module}] ${l}`).join("\n")}\n`, "utf8");
|
|
1498
|
-
} catch {
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
if (echo) try {
|
|
1718
|
+
process.stderr.write(`[${module}] file-log failed: ${formatArg(err)}\n`);
|
|
1719
|
+
} catch {}
|
|
1720
|
+
}
|
|
1499
1721
|
})();
|
|
1500
1722
|
if (echo) for (const line of lines) console.log(`[${module}] ${line}`);
|
|
1501
1723
|
};
|
|
@@ -1527,72 +1749,61 @@ function SessionRow(props) {
|
|
|
1527
1749
|
}
|
|
1528
1750
|
function DelegationRow(props) {
|
|
1529
1751
|
const theme = () => props.api.theme.current;
|
|
1530
|
-
const
|
|
1752
|
+
const tone = createMemo(() => {
|
|
1531
1753
|
const t = theme();
|
|
1532
|
-
switch (props.job.state) {
|
|
1533
|
-
case "
|
|
1534
|
-
case "retry": return t.warning;
|
|
1535
|
-
case "stale-running": return t.error;
|
|
1536
|
-
case "completed": return t.success;
|
|
1754
|
+
switch (delegationStateTone(props.job.state)) {
|
|
1755
|
+
case "warning": return t.warning;
|
|
1537
1756
|
case "error": return t.error;
|
|
1757
|
+
case "success": return t.success;
|
|
1538
1758
|
default: return t.textMuted;
|
|
1539
1759
|
}
|
|
1540
1760
|
});
|
|
1541
|
-
const marker = createMemo(() =>
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
switch (props.job.state) {
|
|
1546
|
-
case "completed": return "✓ ";
|
|
1547
|
-
case "error": return "✕ ";
|
|
1548
|
-
default: return "○ ";
|
|
1549
|
-
}
|
|
1550
|
-
});
|
|
1551
|
-
const tagColor = createMemo(() => props.job.source === "children-only" ? theme().info : color());
|
|
1552
|
-
const detail = createMemo(() => {
|
|
1553
|
-
const line = `${delegationElapsed(props.job, props.now)}${props.job.description !== "" ? ` \u2014 ${props.job.description}` : ""}`;
|
|
1554
|
-
return line.length > 180 ? `${line.slice(0, 177)}\u2026` : line;
|
|
1555
|
-
});
|
|
1761
|
+
const marker = createMemo(() => delegationRowMarker(props.job.state, props.frame));
|
|
1762
|
+
const lead = createMemo(() => formatDelegationRowLead(props.job, marker()));
|
|
1763
|
+
const elapsed = createMemo(() => formatDelegationElapsed(props.job, props.now));
|
|
1764
|
+
const description = createMemo(() => truncateDelegationDescription(props.job.description));
|
|
1556
1765
|
const activity = createMemo(() => latestToolActivityFor(latestToolActivity, props.job.taskID, props.now));
|
|
1557
|
-
const open = ()
|
|
1558
|
-
navigateToDelegationSession(props.api.route, props.job.taskID);
|
|
1559
|
-
};
|
|
1766
|
+
const open = createDelegationRowOpenHandler(props.api.route, props.job.taskID);
|
|
1560
1767
|
return (() => {
|
|
1561
|
-
var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("
|
|
1768
|
+
var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("span"), _el$17 = createElement("span");
|
|
1562
1769
|
insertNode(_el$13, _el$14);
|
|
1563
|
-
insertNode(_el$13, _el$18);
|
|
1564
1770
|
setProp(_el$13, "onMouseDown", open);
|
|
1565
1771
|
insertNode(_el$14, _el$15);
|
|
1566
|
-
insertNode(_el$14, _el$16);
|
|
1567
|
-
insertNode(_el$14, _el$17);
|
|
1568
1772
|
setProp(_el$14, "flexDirection", "row");
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
insert(_el$
|
|
1572
|
-
insert(_el$
|
|
1773
|
+
insertNode(_el$15, _el$16);
|
|
1774
|
+
insertNode(_el$15, _el$17);
|
|
1775
|
+
insert(_el$16, lead);
|
|
1776
|
+
insert(_el$17, elapsed);
|
|
1777
|
+
insert(_el$13, createComponent(Show, {
|
|
1778
|
+
get when() {
|
|
1779
|
+
return description() !== "";
|
|
1780
|
+
},
|
|
1781
|
+
get children() {
|
|
1782
|
+
var _el$18 = createElement("text");
|
|
1783
|
+
insert(_el$18, () => ` ${description()}`);
|
|
1784
|
+
effect((_$p) => setProp(_el$18, "fg", tone(), _$p));
|
|
1785
|
+
return _el$18;
|
|
1786
|
+
}
|
|
1787
|
+
}), null);
|
|
1573
1788
|
insert(_el$13, createComponent(Show, {
|
|
1574
1789
|
get when() {
|
|
1575
1790
|
return activity();
|
|
1576
1791
|
},
|
|
1577
1792
|
children: (a) => (() => {
|
|
1578
1793
|
var _el$19 = createElement("text");
|
|
1579
|
-
insert(_el$19, () => `
|
|
1580
|
-
effect((_$p) => setProp(_el$19, "fg",
|
|
1794
|
+
insert(_el$19, () => ` ↳ ${truncateDelegationDescription(`${a().tool}${a().summary !== "" ? ` ${a().summary}` : ""}`)}`);
|
|
1795
|
+
effect((_$p) => setProp(_el$19, "fg", tone(), _$p));
|
|
1581
1796
|
return _el$19;
|
|
1582
1797
|
})()
|
|
1583
1798
|
}), null);
|
|
1584
1799
|
effect((_p$) => {
|
|
1585
|
-
var _v$5 =
|
|
1586
|
-
_v$5 !== _p$.e && (_p$.e = setProp(_el$
|
|
1587
|
-
_v$6 !== _p$.t && (_p$.t = setProp(_el$
|
|
1588
|
-
_v$7 !== _p$.a && (_p$.a = setProp(_el$17, "fg", _v$7, _p$.a));
|
|
1589
|
-
_v$8 !== _p$.o && (_p$.o = setProp(_el$18, "fg", _v$8, _p$.o));
|
|
1800
|
+
var _v$5 = tone(), _v$6 = tone();
|
|
1801
|
+
_v$5 !== _p$.e && (_p$.e = setProp(_el$16, "fg", _v$5, _p$.e));
|
|
1802
|
+
_v$6 !== _p$.t && (_p$.t = setProp(_el$17, "fg", _v$6, _p$.t));
|
|
1590
1803
|
return _p$;
|
|
1591
1804
|
}, {
|
|
1592
1805
|
e: void 0,
|
|
1593
|
-
t: void 0
|
|
1594
|
-
a: void 0,
|
|
1595
|
-
o: void 0
|
|
1806
|
+
t: void 0
|
|
1596
1807
|
});
|
|
1597
1808
|
return _el$13;
|
|
1598
1809
|
})();
|
|
@@ -1650,11 +1861,11 @@ function View(props) {
|
|
|
1650
1861
|
return (b.time?.updated ?? b.updated ?? 0) - ta;
|
|
1651
1862
|
}).slice(0, 8);
|
|
1652
1863
|
});
|
|
1653
|
-
const
|
|
1654
|
-
const panelLog = createTuiLogger(
|
|
1864
|
+
const projectRoot = createMemo(() => resolvePantheonRoot(props.api.state.path));
|
|
1865
|
+
const panelLog = createTuiLogger(projectRoot());
|
|
1655
1866
|
const [childDelegations, setChildDelegations] = createSignal([]);
|
|
1656
1867
|
const [now, setNow] = createSignal(Date.now());
|
|
1657
|
-
const [
|
|
1868
|
+
const [frame, setFrame] = createSignal(0);
|
|
1658
1869
|
let eventRefreshCount = 0;
|
|
1659
1870
|
let delegationsInflight = null;
|
|
1660
1871
|
const refreshDelegations = () => {
|
|
@@ -1664,7 +1875,7 @@ function View(props) {
|
|
|
1664
1875
|
const state = props.api.state;
|
|
1665
1876
|
let md = [];
|
|
1666
1877
|
try {
|
|
1667
|
-
md = await readAllDelegationEntries(
|
|
1878
|
+
md = await readAllDelegationEntries(projectRoot());
|
|
1668
1879
|
} catch {
|
|
1669
1880
|
md = [];
|
|
1670
1881
|
}
|
|
@@ -1673,13 +1884,14 @@ function View(props) {
|
|
|
1673
1884
|
api: props.api
|
|
1674
1885
|
});
|
|
1675
1886
|
if (sessionID === null) {
|
|
1676
|
-
setChildDelegations(
|
|
1677
|
-
panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID —
|
|
1887
|
+
setChildDelegations([]);
|
|
1888
|
+
panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — inactive panel)`);
|
|
1678
1889
|
return;
|
|
1679
1890
|
}
|
|
1680
1891
|
let children = [];
|
|
1681
1892
|
try {
|
|
1682
|
-
const
|
|
1893
|
+
const childrenPath = buildChildrenPath(sessionID);
|
|
1894
|
+
const result = childrenPath ? await props.api.client.session.children(childrenPath) : void 0;
|
|
1683
1895
|
const data = result?.data ?? result;
|
|
1684
1896
|
children = Array.isArray(data) ? data : [];
|
|
1685
1897
|
} catch (err) {
|
|
@@ -1689,7 +1901,7 @@ function View(props) {
|
|
|
1689
1901
|
const resolveStatus = (childID) => {
|
|
1690
1902
|
if (!isValidSessionId(childID)) return void 0;
|
|
1691
1903
|
try {
|
|
1692
|
-
return state
|
|
1904
|
+
return state.session.status(childID)?.type;
|
|
1693
1905
|
} catch {
|
|
1694
1906
|
return;
|
|
1695
1907
|
}
|
|
@@ -1697,10 +1909,10 @@ function View(props) {
|
|
|
1697
1909
|
const childEntries = childrenToDelegationEntries(children.map((c) => ({
|
|
1698
1910
|
...c,
|
|
1699
1911
|
status: resolveStatus(c.id)
|
|
1700
|
-
})), md, now());
|
|
1912
|
+
})), md, now(), sessionID);
|
|
1701
1913
|
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);
|
|
1702
1914
|
const liveEntries = [...props.liveStore.map.values()].filter((entry) => entry.sessionID === sessionID);
|
|
1703
|
-
setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries));
|
|
1915
|
+
setChildDelegations(filterDelegationsToSession(mergeChildDelegationSources(childEntries, liveEntries), sessionID));
|
|
1704
1916
|
const counts = countDelegationSources(childEntries);
|
|
1705
1917
|
panelLog.info(formatPanelLogLine(children.length, counts.pantheon, counts.native, md.length, eventRefreshCount));
|
|
1706
1918
|
} finally {
|
|
@@ -1709,15 +1921,8 @@ function View(props) {
|
|
|
1709
1921
|
})();
|
|
1710
1922
|
return delegationsInflight;
|
|
1711
1923
|
};
|
|
1712
|
-
const
|
|
1713
|
-
const
|
|
1714
|
-
const delegationCounts = createMemo(() => countDelegationSources(visibleDelegations()));
|
|
1715
|
-
const [archivedPage, setArchivedPage] = createSignal(archivedUiState.page);
|
|
1716
|
-
const clampArchivedPage = (page, pages) => {
|
|
1717
|
-
const clamped = Math.max(0, Math.min(page, Math.max(0, pages - 1)));
|
|
1718
|
-
archivedUiState.page = clamped;
|
|
1719
|
-
setArchivedPage(clamped);
|
|
1720
|
-
};
|
|
1924
|
+
const delegationCeiling = createMemo(() => ceilingDelegationList(childDelegations(), 8, now()));
|
|
1925
|
+
const delegationHeader = createMemo(() => formatDelegationHeader(delegationCeiling().visible));
|
|
1721
1926
|
onMount(() => {
|
|
1722
1927
|
const cleanup = [];
|
|
1723
1928
|
try {
|
|
@@ -1746,7 +1951,7 @@ function View(props) {
|
|
|
1746
1951
|
refreshDelegations();
|
|
1747
1952
|
}, 1e3);
|
|
1748
1953
|
cleanup.push(() => clearInterval(poll));
|
|
1749
|
-
const animation = setInterval(() =>
|
|
1954
|
+
const animation = setInterval(() => setFrame((f) => f + 1), 80);
|
|
1750
1955
|
cleanup.push(() => clearInterval(animation));
|
|
1751
1956
|
try {
|
|
1752
1957
|
const sdk = props.api.state?.session;
|
|
@@ -1785,10 +1990,10 @@ function View(props) {
|
|
|
1785
1990
|
return branch();
|
|
1786
1991
|
},
|
|
1787
1992
|
children: (b) => (() => {
|
|
1788
|
-
var _el$
|
|
1789
|
-
insert(_el$
|
|
1790
|
-
effect((_$p) => setProp(_el$
|
|
1791
|
-
return _el$
|
|
1993
|
+
var _el$33 = createElement("text");
|
|
1994
|
+
insert(_el$33, b);
|
|
1995
|
+
effect((_$p) => setProp(_el$33, "fg", theme().textMuted, _$p));
|
|
1996
|
+
return _el$33;
|
|
1792
1997
|
})()
|
|
1793
1998
|
}), _el$24);
|
|
1794
1999
|
insert(_el$22, createComponent(Show, {
|
|
@@ -1797,37 +2002,37 @@ function View(props) {
|
|
|
1797
2002
|
},
|
|
1798
2003
|
get fallback() {
|
|
1799
2004
|
return (() => {
|
|
1800
|
-
var _el$
|
|
1801
|
-
insertNode(_el$
|
|
1802
|
-
setProp(_el$
|
|
1803
|
-
setProp(_el$
|
|
1804
|
-
insertNode(_el$
|
|
1805
|
-
effect((_$p) => setProp(_el$
|
|
1806
|
-
return _el$
|
|
2005
|
+
var _el$34 = createElement("box"), _el$35 = createElement("text");
|
|
2006
|
+
insertNode(_el$34, _el$35);
|
|
2007
|
+
setProp(_el$34, "flexDirection", "row");
|
|
2008
|
+
setProp(_el$34, "gap", 1);
|
|
2009
|
+
insertNode(_el$35, createTextNode(`Preset: default`));
|
|
2010
|
+
effect((_$p) => setProp(_el$35, "fg", theme().textMuted, _$p));
|
|
2011
|
+
return _el$34;
|
|
1807
2012
|
})();
|
|
1808
2013
|
},
|
|
1809
2014
|
children: (name) => (() => {
|
|
1810
|
-
var _el$
|
|
1811
|
-
insertNode(_el$
|
|
1812
|
-
insertNode(_el$
|
|
1813
|
-
insertNode(_el$
|
|
1814
|
-
setProp(_el$
|
|
1815
|
-
setProp(_el$
|
|
1816
|
-
insertNode(_el$
|
|
1817
|
-
insert(_el$
|
|
1818
|
-
insert(_el$
|
|
2015
|
+
var _el$37 = createElement("box"), _el$38 = createElement("text"), _el$40 = createElement("text"), _el$41 = createElement("text");
|
|
2016
|
+
insertNode(_el$37, _el$38);
|
|
2017
|
+
insertNode(_el$37, _el$40);
|
|
2018
|
+
insertNode(_el$37, _el$41);
|
|
2019
|
+
setProp(_el$37, "flexDirection", "row");
|
|
2020
|
+
setProp(_el$37, "gap", 1);
|
|
2021
|
+
insertNode(_el$38, createTextNode(`⚡ Preset:`));
|
|
2022
|
+
insert(_el$40, name);
|
|
2023
|
+
insert(_el$41, () => `(${preset().source ?? ""})`);
|
|
1819
2024
|
effect((_p$) => {
|
|
1820
|
-
var _v$
|
|
1821
|
-
_v$
|
|
1822
|
-
_v$
|
|
1823
|
-
_v$
|
|
2025
|
+
var _v$10 = theme().textMuted, _v$11 = theme().accent, _v$12 = theme().textMuted;
|
|
2026
|
+
_v$10 !== _p$.e && (_p$.e = setProp(_el$38, "fg", _v$10, _p$.e));
|
|
2027
|
+
_v$11 !== _p$.t && (_p$.t = setProp(_el$40, "fg", _v$11, _p$.t));
|
|
2028
|
+
_v$12 !== _p$.a && (_p$.a = setProp(_el$41, "fg", _v$12, _p$.a));
|
|
1824
2029
|
return _p$;
|
|
1825
2030
|
}, {
|
|
1826
2031
|
e: void 0,
|
|
1827
2032
|
t: void 0,
|
|
1828
2033
|
a: void 0
|
|
1829
2034
|
});
|
|
1830
|
-
return _el$
|
|
2035
|
+
return _el$37;
|
|
1831
2036
|
})()
|
|
1832
2037
|
}), _el$24);
|
|
1833
2038
|
insert(_el$22, createComponent(HR, {}), _el$24);
|
|
@@ -1848,12 +2053,12 @@ function View(props) {
|
|
|
1848
2053
|
},
|
|
1849
2054
|
get fallback() {
|
|
1850
2055
|
return (() => {
|
|
1851
|
-
var _el$
|
|
1852
|
-
insertNode(_el$
|
|
1853
|
-
setProp(_el$
|
|
1854
|
-
insertNode(_el$
|
|
1855
|
-
effect((_$p) => setProp(_el$
|
|
1856
|
-
return _el$
|
|
2056
|
+
var _el$42 = createElement("box"), _el$43 = createElement("text");
|
|
2057
|
+
insertNode(_el$42, _el$43);
|
|
2058
|
+
setProp(_el$42, "marginLeft", 1);
|
|
2059
|
+
insertNode(_el$43, createTextNode(`No recent sessions`));
|
|
2060
|
+
effect((_$p) => setProp(_el$43, "fg", theme().textMuted, _$p));
|
|
2061
|
+
return _el$42;
|
|
1857
2062
|
})();
|
|
1858
2063
|
},
|
|
1859
2064
|
get children() {
|
|
@@ -1881,7 +2086,10 @@ function View(props) {
|
|
|
1881
2086
|
setProp(_el$28, "onMouseDown", () => setShowDelegations((x) => !x));
|
|
1882
2087
|
setProp(_el$29, "attributes", 1);
|
|
1883
2088
|
insert(_el$29, () => `${showDelegations() ? "▼" : "▶"} Delegations`);
|
|
1884
|
-
insert(_el$30, () =>
|
|
2089
|
+
insert(_el$30, (() => {
|
|
2090
|
+
var _c$ = memo(() => delegationCeiling().visible.length > 0);
|
|
2091
|
+
return () => _c$() ? ` ${delegationHeader()}` : " — idle";
|
|
2092
|
+
})());
|
|
1885
2093
|
insert(_el$22, createComponent(Show, {
|
|
1886
2094
|
get when() {
|
|
1887
2095
|
return showDelegations();
|
|
@@ -1889,16 +2097,16 @@ function View(props) {
|
|
|
1889
2097
|
get children() {
|
|
1890
2098
|
return createComponent(Show, {
|
|
1891
2099
|
get when() {
|
|
1892
|
-
return
|
|
2100
|
+
return delegationCeiling().visible.length > 0;
|
|
1893
2101
|
},
|
|
1894
2102
|
get fallback() {
|
|
1895
2103
|
return (() => {
|
|
1896
|
-
var _el$
|
|
1897
|
-
insertNode(_el$
|
|
1898
|
-
setProp(_el$
|
|
1899
|
-
insertNode(_el$
|
|
1900
|
-
effect((_$p) => setProp(_el$
|
|
1901
|
-
return _el$
|
|
2104
|
+
var _el$45 = createElement("box"), _el$46 = createElement("text");
|
|
2105
|
+
insertNode(_el$45, _el$46);
|
|
2106
|
+
setProp(_el$45, "marginLeft", 1);
|
|
2107
|
+
insertNode(_el$46, createTextNode(`No delegations`));
|
|
2108
|
+
effect((_$p) => setProp(_el$46, "fg", theme().textMuted, _$p));
|
|
2109
|
+
return _el$45;
|
|
1902
2110
|
})();
|
|
1903
2111
|
},
|
|
1904
2112
|
get children() {
|
|
@@ -1907,7 +2115,7 @@ function View(props) {
|
|
|
1907
2115
|
setProp(_el$31, "flexDirection", "column");
|
|
1908
2116
|
insert(_el$31, createComponent(For, {
|
|
1909
2117
|
get each() {
|
|
1910
|
-
return
|
|
2118
|
+
return delegationCeiling().visible;
|
|
1911
2119
|
},
|
|
1912
2120
|
children: (job) => createComponent(DelegationRow, {
|
|
1913
2121
|
get api() {
|
|
@@ -1917,69 +2125,23 @@ function View(props) {
|
|
|
1917
2125
|
get now() {
|
|
1918
2126
|
return now();
|
|
1919
2127
|
},
|
|
1920
|
-
get
|
|
1921
|
-
return
|
|
2128
|
+
get frame() {
|
|
2129
|
+
return frame();
|
|
1922
2130
|
}
|
|
1923
2131
|
})
|
|
1924
2132
|
}), null);
|
|
1925
2133
|
insert(_el$31, createComponent(Show, {
|
|
1926
2134
|
get when() {
|
|
1927
|
-
return
|
|
2135
|
+
return delegationCeiling().hidden > 0;
|
|
1928
2136
|
},
|
|
1929
2137
|
get children() {
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
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");
|
|
1938
|
-
insertNode(_el$47, _el$48);
|
|
1939
|
-
setProp(_el$47, "flexDirection", "column");
|
|
1940
|
-
insertNode(_el$48, _el$49);
|
|
1941
|
-
insertNode(_el$48, _el$51);
|
|
1942
|
-
insertNode(_el$48, _el$52);
|
|
1943
|
-
insertNode(_el$48, _el$53);
|
|
1944
|
-
setProp(_el$48, "flexDirection", "row");
|
|
1945
|
-
setProp(_el$48, "marginLeft", 0);
|
|
1946
|
-
insertNode(_el$49, createTextNode(`‹ `));
|
|
1947
|
-
setProp(_el$49, "onMouseDown", () => clampArchivedPage(page - 1, pages));
|
|
1948
|
-
insert(_el$51, () => `Archived (${archived.length}) `);
|
|
1949
|
-
insert(_el$52, `${page + 1}/${pages}`);
|
|
1950
|
-
insertNode(_el$53, createTextNode(` ›`));
|
|
1951
|
-
setProp(_el$53, "onMouseDown", () => clampArchivedPage(page + 1, pages));
|
|
1952
|
-
insert(_el$47, createComponent(For, {
|
|
1953
|
-
each: slice,
|
|
1954
|
-
children: (job) => createComponent(DelegationRow, {
|
|
1955
|
-
get api() {
|
|
1956
|
-
return props.api;
|
|
1957
|
-
},
|
|
1958
|
-
job,
|
|
1959
|
-
get now() {
|
|
1960
|
-
return now();
|
|
1961
|
-
},
|
|
1962
|
-
get animationNow() {
|
|
1963
|
-
return animationNow();
|
|
1964
|
-
}
|
|
1965
|
-
})
|
|
1966
|
-
}), null);
|
|
1967
|
-
effect((_p$) => {
|
|
1968
|
-
var _v$15 = theme().textMuted, _v$16 = theme().textMuted, _v$17 = theme().textMuted, _v$18 = theme().textMuted;
|
|
1969
|
-
_v$15 !== _p$.e && (_p$.e = setProp(_el$49, "fg", _v$15, _p$.e));
|
|
1970
|
-
_v$16 !== _p$.t && (_p$.t = setProp(_el$51, "fg", _v$16, _p$.t));
|
|
1971
|
-
_v$17 !== _p$.a && (_p$.a = setProp(_el$52, "fg", _v$17, _p$.a));
|
|
1972
|
-
_v$18 !== _p$.o && (_p$.o = setProp(_el$53, "fg", _v$18, _p$.o));
|
|
1973
|
-
return _p$;
|
|
1974
|
-
}, {
|
|
1975
|
-
e: void 0,
|
|
1976
|
-
t: void 0,
|
|
1977
|
-
a: void 0,
|
|
1978
|
-
o: void 0
|
|
1979
|
-
});
|
|
1980
|
-
return _el$47;
|
|
1981
|
-
})();
|
|
1982
|
-
})();
|
|
2138
|
+
var _el$32 = createElement("text");
|
|
2139
|
+
insert(_el$32, (() => {
|
|
2140
|
+
var _c$2 = memo(() => delegationCeiling().hiddenActive > 0);
|
|
2141
|
+
return () => _c$2() ? `… +${delegationCeiling().hiddenActive} active` : `… +${delegationCeiling().hiddenTerminal} more`;
|
|
2142
|
+
})());
|
|
2143
|
+
effect((_$p) => setProp(_el$32, "fg", theme().textMuted, _$p));
|
|
2144
|
+
return _el$32;
|
|
1983
2145
|
}
|
|
1984
2146
|
}), null);
|
|
1985
2147
|
return _el$31;
|
|
@@ -1988,12 +2150,12 @@ function View(props) {
|
|
|
1988
2150
|
}
|
|
1989
2151
|
}), null);
|
|
1990
2152
|
effect((_p$) => {
|
|
1991
|
-
var _v$
|
|
1992
|
-
_v$
|
|
1993
|
-
_v$
|
|
1994
|
-
_v$
|
|
1995
|
-
_v$
|
|
1996
|
-
_v$
|
|
2153
|
+
var _v$7 = theme().accent, _v$8 = theme().text, _v$9 = theme().textMuted, _v$0 = theme().text, _v$1 = theme().textMuted;
|
|
2154
|
+
_v$7 !== _p$.e && (_p$.e = setProp(_el$23, "fg", _v$7, _p$.e));
|
|
2155
|
+
_v$8 !== _p$.t && (_p$.t = setProp(_el$25, "fg", _v$8, _p$.t));
|
|
2156
|
+
_v$9 !== _p$.a && (_p$.a = setProp(_el$26, "fg", _v$9, _p$.a));
|
|
2157
|
+
_v$0 !== _p$.o && (_p$.o = setProp(_el$29, "fg", _v$0, _p$.o));
|
|
2158
|
+
_v$1 !== _p$.i && (_p$.i = setProp(_el$30, "fg", _v$1, _p$.i));
|
|
1997
2159
|
return _p$;
|
|
1998
2160
|
}, {
|
|
1999
2161
|
e: void 0,
|
|
@@ -2062,6 +2224,6 @@ const plugin = {
|
|
|
2062
2224
|
setup: async () => {}
|
|
2063
2225
|
};
|
|
2064
2226
|
//#endregion
|
|
2065
|
-
export { IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame,
|
|
2227
|
+
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 };
|
|
2066
2228
|
|
|
2067
2229
|
//# sourceMappingURL=tui.js.map
|