pantheon-opencode 1.5.0-beta.2 → 1.5.0-beta.20
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/.pantheon/code-mode/manifest.json +21 -0
- package/AGENTS.md +14 -63
- package/CHANGELOG.md +1260 -0
- package/README.md +162 -23
- package/README.pt-BR.md +104 -19
- package/bin/pantheon-init.mjs +178 -21
- package/commands/pantheon.md +1 -35
- package/package.json +23 -15
- package/plugin.json +1 -1
- package/pyproject.toml +62 -0
- package/scripts/code_mode_server.py +431 -55
- package/scripts/doctor.mjs +207 -9
- package/scripts/install/cli-ui.mjs +17 -11
- package/scripts/install/config-migration.mjs +4 -6
- package/scripts/install/opencode.mjs +137 -35
- package/scripts/install/plugin.mjs +12 -45
- package/scripts/install/shared.mjs +43 -2
- package/scripts/install/strings.mjs +129 -0
- package/scripts/install-mcp.mjs +1 -0
- package/scripts/manifest-inventory.mjs +410 -0
- package/scripts/mcp_persistence_server.py +536 -164
- package/scripts/package-evidence.mjs +242 -0
- package/scripts/probe-pantheon-cost.mjs +10 -1
- package/scripts/release-beta-version.mjs +56 -60
- package/scripts/sync-artifacts.mjs +169 -0
- package/scripts/sync-tui.mjs +129 -64
- package/scripts/test-opencode-v1-v2-sandbox.sh +79 -161
- package/scripts/validation-policy.mjs +7 -7
- package/scripts/version-check.mjs +113 -108
- package/scripts/versioning.mjs +230 -86
- package/src/agents/apollo.md +1 -0
- package/src/agents/gaia.md +1 -0
- package/src/agents/mnemosyne.md +1 -1
- package/src/agents/zeus.md +11 -54
- 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/code_mode_server.py +431 -55
- package/src/mcp/mcp_persistence_server.py +536 -164
- package/src/mcp/toon_codec.py +2 -4
- package/src/pantheon/background-job-board.ts +47 -5
- package/src/pantheon/{delegation-compaction.ts → compaction-context.ts} +10 -100
- package/src/pantheon/cost-command.ts +309 -44
- package/src/pantheon/delegation-enforce.ts +59 -14
- package/src/pantheon/file-persistence-salvage.ts +64 -0
- package/src/pantheon/file-persistence.ts +194 -45
- 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 +139 -0
- 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/shared-board.ts +37 -0
- package/src/pantheon/task-result-guard.ts +106 -0
- package/src/pantheon/todo-enforcer.ts +3 -3
- package/src/pantheon/tool-ceiling.ts +159 -0
- 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 +83 -251
- package/src/plugins/pantheon-hooks.ts +51 -0
- package/src/plugins/tui/dist/server.d.ts +1 -1
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/server.js +1 -1
- package/src/plugins/tui/dist/server.js.map +1 -1
- package/src/plugins/tui/dist/tui.d.ts +261 -77
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +640 -234
- package/src/plugins/tui/dist/tui.js.map +1 -1
- package/src/plugins/tui/package-lock.json +2 -2
- package/src/plugins/tui/package.json +2 -2
- package/src/plugins/tui/src/index.tsx +872 -226
- 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/pantheon/auto-wake.ts +0 -89
- 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/cost-tracker.ts +0 -108
- package/src/pantheon/delegate-manager.ts +0 -669
- package/src/pantheon/delegation-classifier.ts +0 -307
- package/src/pantheon/delegation-finalize.ts +0 -374
- package/src/pantheon/delegation-notify.ts +0 -212
- package/src/pantheon/delegation-toast.ts +0 -73
- package/src/pantheon/delegation.ts +0 -1120
- package/src/pantheon/dispatch-guard.ts +0 -109
- package/src/pantheon/permission-eval.ts +0 -284
- package/src/pantheon/preemptive-compact.ts +0 -76
- package/src/pantheon/retry-policy.ts +0 -212
- package/src/pantheon/step-cap.ts +0 -110
- package/src/pantheon/token-opt.ts +0 -781
- package/src/pantheon/toon-codec.ts +0 -481
- package/src/pantheon/v2-events.ts +0 -131
- 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 -2605
|
@@ -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.0";
|
|
168
|
+
return "1.5.0-beta.20";
|
|
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
|
|
@@ -868,19 +875,34 @@ function tuiLogPath(projectRoot) {
|
|
|
868
875
|
* falling back to startedAt, descending). Shared by the md reader and
|
|
869
876
|
* mergeDelegationSources. */
|
|
870
877
|
function compareDelegationEntries(a, b) {
|
|
871
|
-
const
|
|
872
|
-
const
|
|
878
|
+
const isActive = (st) => st === "running" || st === "retry" || st === "stale-running" ? 1 : 0;
|
|
879
|
+
const aRun = isActive(a.state);
|
|
880
|
+
const bRun = isActive(b.state);
|
|
873
881
|
if (aRun !== bRun) return bRun - aRun;
|
|
874
882
|
return (b.updatedAt ?? b.startedAt) - (a.updatedAt ?? a.startedAt);
|
|
875
883
|
}
|
|
876
|
-
/**
|
|
877
|
-
* recent terminal reports
|
|
878
|
-
*
|
|
879
|
-
*
|
|
884
|
+
/** Split the panel list: active jobs (running/retry, stale-marked) first,
|
|
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
|
|
887
|
+
* without the TUI runtime. */
|
|
888
|
+
function splitDelegationList(all, maxRecent = 8, now = Date.now(), staleThresholdMs = 1800 * 1e3) {
|
|
889
|
+
const isActiveState = (st) => st === "running" || st === "retry" || st === "stale-running";
|
|
890
|
+
return {
|
|
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)
|
|
898
|
+
};
|
|
899
|
+
}
|
|
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. */
|
|
880
903
|
function visibleDelegationList(all, maxTerminal = 8, now = Date.now(), staleThresholdMs = 1800 * 1e3) {
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
return [...running, ...terminal];
|
|
904
|
+
const { active, recent } = splitDelegationList(all, maxTerminal, now, staleThresholdMs);
|
|
905
|
+
return [...active, ...recent];
|
|
884
906
|
}
|
|
885
907
|
/** Default stale-running threshold: 30 minutes. */
|
|
886
908
|
const STALE_RUNNING_THRESHOLD_MS = 1800 * 1e3;
|
|
@@ -888,10 +910,37 @@ const STALE_RUNNING_THRESHOLD_MS = 1800 * 1e3;
|
|
|
888
910
|
* considered stale. Combined with the stale-running threshold to produce the
|
|
889
911
|
* display-only `stale-running` state. */
|
|
890
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;
|
|
891
940
|
/**
|
|
892
941
|
* Mark a running entry as `stale-running` if it has been running longer than
|
|
893
942
|
* the threshold AND has no recent activity (no `updatedAt` change in the last
|
|
894
|
-
* `IDLE_SILENCE_MS`). This is DISPLAY-ONLY — the
|
|
943
|
+
* `IDLE_SILENCE_MS`). This is DISPLAY-ONLY — the persisted state is unchanged.
|
|
895
944
|
*
|
|
896
945
|
* A `stale-running` entry renders with a warning indicator but the underlying
|
|
897
946
|
* delegation is still treated as running by the backend.
|
|
@@ -905,7 +954,7 @@ function markStaleIfRunning(entry, now, thresholdMs = STALE_RUNNING_THRESHOLD_MS
|
|
|
905
954
|
state: "stale-running"
|
|
906
955
|
};
|
|
907
956
|
}
|
|
908
|
-
/** Compact elapsed-time label: "
|
|
957
|
+
/** Compact elapsed-time label, single unit only: "12s"/"3m"/"1h"/"2d" — ticks every
|
|
909
958
|
* second for running jobs. */
|
|
910
959
|
function fmtElapsed(ms) {
|
|
911
960
|
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
@@ -913,15 +962,16 @@ function fmtElapsed(ms) {
|
|
|
913
962
|
const hours = Math.floor(total % 86400 / 3600);
|
|
914
963
|
const minutes = Math.floor(total % 3600 / 60);
|
|
915
964
|
const seconds = total % 60;
|
|
916
|
-
if (days > 0) return `${days}d
|
|
917
|
-
if (hours > 0) return `${hours}h
|
|
918
|
-
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`;
|
|
919
968
|
return `${seconds}s`;
|
|
920
969
|
}
|
|
921
|
-
/** Elapsed label for one entry:
|
|
922
|
-
*
|
|
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). */
|
|
923
973
|
function delegationElapsed(entry, now) {
|
|
924
|
-
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);
|
|
925
975
|
return entry.updatedAt !== null ? fmtElapsed(entry.updatedAt - entry.startedAt) : "—";
|
|
926
976
|
}
|
|
927
977
|
/** The activity labels shown by the animated row. Keeping this pure makes the
|
|
@@ -935,6 +985,7 @@ function delegationActivity(entry) {
|
|
|
935
985
|
return "working";
|
|
936
986
|
}
|
|
937
987
|
function delegationActivityLabel(entry) {
|
|
988
|
+
if (entry.state === "retry") return "RETRYING";
|
|
938
989
|
switch (delegationActivity(entry)) {
|
|
939
990
|
case "delegating": return "DELEGATING";
|
|
940
991
|
case "working": return "WORKING";
|
|
@@ -956,11 +1007,94 @@ const DELEGATION_SPINNER_FRAMES = [
|
|
|
956
1007
|
"⠇",
|
|
957
1008
|
"⠏"
|
|
958
1009
|
];
|
|
959
|
-
/** Return
|
|
960
|
-
|
|
961
|
-
|
|
1010
|
+
/** Return the spinner glyph for the given frame counter. The frame counter
|
|
1011
|
+
* is incremented every ~80ms by a dedicated animation timer, decoupled from
|
|
1012
|
+
* the data poll cycle. Pure — no side effects. */
|
|
1013
|
+
function delegationSpinnerFrame(frame) {
|
|
1014
|
+
const index = Math.abs(Math.floor(frame)) % DELEGATION_SPINNER_FRAMES.length;
|
|
962
1015
|
return DELEGATION_SPINNER_FRAMES[index] ?? DELEGATION_SPINNER_FRAMES[0];
|
|
963
1016
|
}
|
|
1017
|
+
/** Every state the row knows how to draw: the delegation lifecycle states
|
|
1018
|
+
* derived from the children status + md reports (`completed`, `error`,
|
|
1019
|
+
* `cancelled`, `startup_failed`, `startup_unknown`) plus the TUI-only
|
|
1020
|
+
* states (`retry`, `stale-running`). Fase 1 deliberately omits speculative
|
|
1021
|
+
* blocked/paused/scheduled/skipped. There is no `pending` display state: a
|
|
1022
|
+
* pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
|
|
1023
|
+
/** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
|
|
1024
|
+
* Kept separate + pure so the color channel is testable without booting the
|
|
1025
|
+
* renderer. Every display state resolves to one of the three status colors;
|
|
1026
|
+
* the row paints its whole content with it (see {@link DelegationRow}). */
|
|
1027
|
+
/** Status → color mapping for the whole-row tone. Red = failure, green =
|
|
1028
|
+
* terminal, yellow = in flight. The glyph remains a redundant channel so the
|
|
1029
|
+
* state stays legible in monochrome. Display-only; no behavior change. */
|
|
1030
|
+
function delegationStateTone(state) {
|
|
1031
|
+
switch (state) {
|
|
1032
|
+
case "running":
|
|
1033
|
+
case "retry":
|
|
1034
|
+
case "startup_unknown":
|
|
1035
|
+
case "stale-running": return "warning";
|
|
1036
|
+
case "error":
|
|
1037
|
+
case "startup_failed": return "error";
|
|
1038
|
+
case "completed":
|
|
1039
|
+
case "cancelled": return "success";
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
/** Bounded tracker: child session id → latest running tool call. */
|
|
1043
|
+
const latestToolActivity = /* @__PURE__ */ new Map();
|
|
1044
|
+
const MAX_TOOL_ACTIVITY_ENTRIES = 200;
|
|
1045
|
+
const TOOL_ACTIVITY_TTL_MS = 300 * 1e3;
|
|
1046
|
+
/** First usable summary string from common tool input fields. */
|
|
1047
|
+
function summarizeToolInput(input) {
|
|
1048
|
+
if (!input) return "";
|
|
1049
|
+
for (const key of [
|
|
1050
|
+
"command",
|
|
1051
|
+
"description",
|
|
1052
|
+
"prompt",
|
|
1053
|
+
"filePath",
|
|
1054
|
+
"pattern",
|
|
1055
|
+
"url",
|
|
1056
|
+
"query"
|
|
1057
|
+
]) {
|
|
1058
|
+
const value = input[key];
|
|
1059
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
1060
|
+
}
|
|
1061
|
+
return "";
|
|
1062
|
+
}
|
|
1063
|
+
/** Reduce one message.part.updated tool part to displayable activity.
|
|
1064
|
+
* Returns null for non-tool parts, completed/error parts (no live activity)
|
|
1065
|
+
* or parts without a session id. Pure. */
|
|
1066
|
+
function extractToolActivity(part, now = Date.now()) {
|
|
1067
|
+
if (part?.type !== "tool") return null;
|
|
1068
|
+
const status = part.state?.status;
|
|
1069
|
+
if (status !== "pending" && status !== "running") return null;
|
|
1070
|
+
const sessionID = typeof part.sessionID === "string" ? part.sessionID : "";
|
|
1071
|
+
if (sessionID === "") return null;
|
|
1072
|
+
return {
|
|
1073
|
+
sessionID,
|
|
1074
|
+
activity: {
|
|
1075
|
+
tool: typeof part.tool === "string" && part.tool !== "" ? part.tool : "tool",
|
|
1076
|
+
summary: summarizeToolInput(part.state?.input).replace(/\s+/g, " ").trim().slice(0, 48),
|
|
1077
|
+
at: now
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
/** Record an activity sample (bounded map, oldest dropped). */
|
|
1082
|
+
function trackToolActivity(map, sessionID, activity) {
|
|
1083
|
+
if (sessionID === "") return;
|
|
1084
|
+
if (map.size >= MAX_TOOL_ACTIVITY_ENTRIES) {
|
|
1085
|
+
const oldest = map.keys().next().value;
|
|
1086
|
+
if (oldest !== void 0) map.delete(oldest);
|
|
1087
|
+
}
|
|
1088
|
+
map.set(sessionID, activity);
|
|
1089
|
+
}
|
|
1090
|
+
/** Latest live activity for a child session, or null when absent/stale. */
|
|
1091
|
+
function latestToolActivityFor(map, sessionID, now = Date.now(), ttlMs = TOOL_ACTIVITY_TTL_MS) {
|
|
1092
|
+
if (!sessionID) return null;
|
|
1093
|
+
const hit = map.get(sessionID);
|
|
1094
|
+
if (hit === void 0) return null;
|
|
1095
|
+
if (now - hit.at > ttlMs) return null;
|
|
1096
|
+
return hit;
|
|
1097
|
+
}
|
|
964
1098
|
/** Merge the immediate tool-event channel into the child-session channel.
|
|
965
1099
|
*
|
|
966
1100
|
* Children remain the durable source, while live entries make a delegation
|
|
@@ -977,8 +1111,11 @@ function mergeChildDelegationSources(children, live) {
|
|
|
977
1111
|
bySessionAlias.set(key(entry.sessionID, entry.alias), index);
|
|
978
1112
|
});
|
|
979
1113
|
for (const liveEntry of live) {
|
|
980
|
-
|
|
1114
|
+
const aliaslessTTL = liveEntry.tool === "task" ? NATIVE_LIVE_ALIASLESS_TTL_MS : 3e4;
|
|
1115
|
+
if (liveEntry.alias === null && liveEntry.taskID === null && Date.now() - liveEntry.startedAt > aliaslessTTL) continue;
|
|
981
1116
|
const incoming = toDelegationEntry(liveEntry);
|
|
1117
|
+
const isNativeLive = liveEntry.tool === "task" && liveEntry.alias === null;
|
|
1118
|
+
if (isNativeLive) incoming.source = "children-only";
|
|
982
1119
|
const index = (incoming.taskID !== void 0 ? byTask.get(incoming.taskID) : void 0) ?? (incoming.alias !== "" ? bySessionAlias.get(key(incoming.sessionID, incoming.alias)) : void 0);
|
|
983
1120
|
if (index === void 0) {
|
|
984
1121
|
result.push(incoming);
|
|
@@ -1006,7 +1143,7 @@ function mergeChildDelegationSources(children, live) {
|
|
|
1006
1143
|
startedAt: Math.min(existing.startedAt, incoming.startedAt),
|
|
1007
1144
|
updatedAt: incoming.updatedAt,
|
|
1008
1145
|
read: incoming.read,
|
|
1009
|
-
source: "live"
|
|
1146
|
+
source: isNativeLive ? existing.source : "live"
|
|
1010
1147
|
};
|
|
1011
1148
|
}
|
|
1012
1149
|
result.sort(compareDelegationEntries);
|
|
@@ -1023,10 +1160,12 @@ const DELEGATE_TASKID_PATTERN = /\(task\s+([a-z0-9_]+)\)/i;
|
|
|
1023
1160
|
const READ_ALIAS_PATTERN = /^[a-z]{2,8}-\d+$/i;
|
|
1024
1161
|
/** Extract the tool name + args from a `message.part.updated` part and
|
|
1025
1162
|
* reduce it to what the panel needs. Returns null for anything that is
|
|
1026
|
-
* not a pantheon delegation tool part
|
|
1163
|
+
* not a pantheon delegation tool part, the native `task` subagent tool
|
|
1164
|
+
* (same parentID === caller mechanism — its children render `nat:`),
|
|
1165
|
+
* or is missing its callID. */
|
|
1027
1166
|
function parseDelegationToolPart(part, now = Date.now()) {
|
|
1028
1167
|
if (part.type !== "tool") return null;
|
|
1029
|
-
if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read") return null;
|
|
1168
|
+
if (part.tool !== "pantheon_delegate" && part.tool !== "pantheon_delegation_read" && part.tool !== "task") return null;
|
|
1030
1169
|
const callID = part.callID;
|
|
1031
1170
|
if (callID === void 0 || callID === "") return null;
|
|
1032
1171
|
const sessionID = part.sessionID ?? "";
|
|
@@ -1058,8 +1197,8 @@ function parseDelegationToolPart(part, now = Date.now()) {
|
|
|
1058
1197
|
endAt
|
|
1059
1198
|
};
|
|
1060
1199
|
}
|
|
1061
|
-
const agent = typeof input.agent === "string" ? input.agent : "agent";
|
|
1062
|
-
const description = typeof input.description === "string" ? input.description : "";
|
|
1200
|
+
const agent = typeof input.agent === "string" ? input.agent : typeof input.subagent_type === "string" ? input.subagent_type : "agent";
|
|
1201
|
+
const description = typeof input.description === "string" ? input.description : typeof input.prompt === "string" ? input.prompt.slice(0, 120) : "";
|
|
1063
1202
|
let alias = null;
|
|
1064
1203
|
let taskID = null;
|
|
1065
1204
|
if (status === "completed") {
|
|
@@ -1071,7 +1210,7 @@ function parseDelegationToolPart(part, now = Date.now()) {
|
|
|
1071
1210
|
callID,
|
|
1072
1211
|
partID: part.id ?? "",
|
|
1073
1212
|
sessionID,
|
|
1074
|
-
tool: "pantheon_delegate",
|
|
1213
|
+
tool: part.tool === "task" ? "task" : "pantheon_delegate",
|
|
1075
1214
|
agent,
|
|
1076
1215
|
description,
|
|
1077
1216
|
status,
|
|
@@ -1120,7 +1259,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
|
|
|
1120
1259
|
callID: parsed.callID,
|
|
1121
1260
|
partID: parsed.partID,
|
|
1122
1261
|
sessionID: parsed.sessionID,
|
|
1123
|
-
tool:
|
|
1262
|
+
tool: parsed.tool,
|
|
1124
1263
|
agent: parsed.agent ?? "agent",
|
|
1125
1264
|
description: parsed.description,
|
|
1126
1265
|
alias: existing?.alias ?? null,
|
|
@@ -1137,7 +1276,7 @@ function reduceDelegationToolPart(map, part, now = Date.now()) {
|
|
|
1137
1276
|
callID: parsed.callID,
|
|
1138
1277
|
partID: parsed.partID,
|
|
1139
1278
|
sessionID: parsed.sessionID,
|
|
1140
|
-
tool:
|
|
1279
|
+
tool: parsed.tool,
|
|
1141
1280
|
agent: parsed.agent ?? "agent",
|
|
1142
1281
|
description: parsed.description,
|
|
1143
1282
|
alias: parsed.alias,
|
|
@@ -1182,11 +1321,14 @@ function removeDelegationEntry(map, partIDOrCallID) {
|
|
|
1182
1321
|
}
|
|
1183
1322
|
return false;
|
|
1184
1323
|
}
|
|
1185
|
-
/** Collect pantheon delegation tool parts from a session's messages.
|
|
1324
|
+
/** Collect pantheon delegation + native task tool parts from a session's messages.
|
|
1186
1325
|
* Messages may carry their parts inline (duck-typed `msg.parts`); when
|
|
1187
1326
|
* they don't, the optional `getParts(messageID)` callback is used (the TUI
|
|
1188
|
-
* SDK exposes `api.state.part(messageID)`).
|
|
1189
|
-
*
|
|
1327
|
+
* SDK exposes `api.state.part(messageID)`). The native `task` tool spawns a
|
|
1328
|
+
* child session with parentID = caller — the same mechanism as
|
|
1329
|
+
* pantheon_delegate — so its parts feed the live-map as the native signal
|
|
1330
|
+
* (rows come from the children channel). Pure w.r.t. I/O — used
|
|
1331
|
+
* by the mount re-scan to re-seed the live map after compaction/attach. */
|
|
1190
1332
|
function collectDelegationToolParts(messages, getParts) {
|
|
1191
1333
|
const out = [];
|
|
1192
1334
|
for (const msg of messages ?? []) {
|
|
@@ -1196,7 +1338,7 @@ function collectDelegationToolParts(messages, getParts) {
|
|
|
1196
1338
|
if (!parts) continue;
|
|
1197
1339
|
for (const raw of parts) {
|
|
1198
1340
|
const part = raw;
|
|
1199
|
-
if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read")) out.push(part);
|
|
1341
|
+
if (part?.type === "tool" && (part.tool === "pantheon_delegate" || part.tool === "pantheon_delegation_read" || part.tool === "task")) out.push(part);
|
|
1200
1342
|
}
|
|
1201
1343
|
}
|
|
1202
1344
|
return out;
|
|
@@ -1256,6 +1398,26 @@ function mergeDelegationSources(live, md) {
|
|
|
1256
1398
|
all.sort(compareDelegationEntries);
|
|
1257
1399
|
return all;
|
|
1258
1400
|
}
|
|
1401
|
+
/** Scope a fully-merged display list to the ACTIVE session only.
|
|
1402
|
+
*
|
|
1403
|
+
* The panel is session-scoped: rows from other sessions (md history under
|
|
1404
|
+
* `.pantheon/delegations/<other-session>/`) and rows with no
|
|
1405
|
+
* attributable session (empty sessionID) are DROPPED. The previous
|
|
1406
|
+
* cross-session behavior rendered those rows and clicking them led to
|
|
1407
|
+
* "Session not found" — there is no cross-session channel anymore.
|
|
1408
|
+
*
|
|
1409
|
+
* The md channel still feeds the merge UNFILTERED so it can ENRICH an
|
|
1410
|
+
* active-session row with state/alias/agent (dedup by taskID), but it can
|
|
1411
|
+
* never introduce a row for another session: this filter is applied
|
|
1412
|
+
* ONCE, after every merge (children + live + md).
|
|
1413
|
+
*
|
|
1414
|
+
* Returns [] when no active session resolves (null/placeholder) — there is no
|
|
1415
|
+
* scope to show. Native children always carry the active session id (stamped
|
|
1416
|
+
* by `childrenToDelegationEntries`), so they survive the filter. Pure. */
|
|
1417
|
+
function filterDelegationsToSession(entries, activeSessionID) {
|
|
1418
|
+
if (!isValidSessionId(activeSessionID)) return [];
|
|
1419
|
+
return entries.filter((entry) => entry.sessionID === activeSessionID);
|
|
1420
|
+
}
|
|
1259
1421
|
/** Server-aligned session id validity: opencode rejects anything not starting
|
|
1260
1422
|
* with "ses" (SchemaError). This deliberately mirrors that exact contract —
|
|
1261
1423
|
* nothing stricter, nothing looser — so a template placeholder ("{sessionID}"),
|
|
@@ -1285,40 +1447,216 @@ function resolveCurrentSessionID(sources) {
|
|
|
1285
1447
|
return null;
|
|
1286
1448
|
}
|
|
1287
1449
|
/** THE single choke point for every `session.children` / session-API path.
|
|
1288
|
-
* Returns
|
|
1289
|
-
* null for anything else (placeholder,
|
|
1290
|
-
* skips the call entirely instead of
|
|
1291
|
-
* (the "%7BsessionID%7D" regression).
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1450
|
+
* Returns the v2 SDK parameter shape `{ sessionID }` ONLY for a
|
|
1451
|
+
* server-valid session id; returns null for anything else (placeholder,
|
|
1452
|
+
* empty, foreign id) so the caller skips the call entirely instead of
|
|
1453
|
+
* sending an unsubstituted placeholder (the "%7BsessionID%7D" regression).
|
|
1454
|
+
* The TUI client is `@opencode-ai/sdk/v2`, whose session methods take a
|
|
1455
|
+
* FLAT parameter object (`{ sessionID }`), NOT the v1 `{ path: { id } }`
|
|
1456
|
+
* envelope — passing the v1 shape left the v2 `{sessionID}` URL template
|
|
1457
|
+
* unsubstituted (`/session/%7BsessionID%7D/children`). Every session-API
|
|
1458
|
+
* call site MUST go through this function (enforced by the source-scan
|
|
1459
|
+
* test in tests/pantheon/tui-delegations.test.ts). */
|
|
1294
1460
|
function safeSessionPath(id) {
|
|
1295
1461
|
if (!isValidSessionId(id)) return null;
|
|
1296
|
-
return {
|
|
1462
|
+
return { sessionID: id };
|
|
1297
1463
|
}
|
|
1298
|
-
/** Build the `session.children`
|
|
1464
|
+
/** Build the `session.children` parameters ONLY from a validated session id.
|
|
1299
1465
|
* Delegates to {@link safeSessionPath} — the single choke point. Returns
|
|
1300
|
-
* null for null/invalid ids so the caller
|
|
1301
|
-
* sending an unsubstituted placeholder (the
|
|
1466
|
+
* the v2 SDK shape `{ sessionID }`; null for null/invalid ids so the caller
|
|
1467
|
+
* skips the fetch instead of sending an unsubstituted placeholder (the
|
|
1468
|
+
* "%7BsessionID%7D" regression). */
|
|
1302
1469
|
function buildChildrenPath(id) {
|
|
1303
1470
|
return safeSessionPath(id);
|
|
1304
1471
|
}
|
|
1305
1472
|
/** Duck-typed subset of a child Session (+ its live status type). */
|
|
1306
|
-
/** Map a child status type to a display state.
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
* (
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
|
|
1321
|
-
|
|
1473
|
+
/** Map a child status type to a display state.
|
|
1474
|
+
*
|
|
1475
|
+
* Only `busy`/`retry` are EXPLICIT live states. `idle` is terminal and so is
|
|
1476
|
+
* any other/unknown status — a status the panel does not recognise is not
|
|
1477
|
+
* evidence of activity. An ABSENT status is the historical case: the status
|
|
1478
|
+
* map (`api.state.session.status`) contains only currently-alive sessions, so
|
|
1479
|
+
* every finished child of a long session is missing from it. Defaulting that
|
|
1480
|
+
* to running made the whole session history render as "active" (the
|
|
1481
|
+
* "197 children, all active" regression), so an absent status is terminal
|
|
1482
|
+
* (done) UNLESS the child was active within `graceMs` — a just-spawned child
|
|
1483
|
+
* that the status API has not registered yet must still render as running.
|
|
1484
|
+
*
|
|
1485
|
+
* @param status the `api.state.session.status(id)?.type`, or undefined
|
|
1486
|
+
* @param time the child session time fields (`created`/`updated`)
|
|
1487
|
+
* @param now current wall-clock ms (injectable for tests)
|
|
1488
|
+
* @param graceMs recency window for the absent-status running assumption
|
|
1489
|
+
* Pure — no I/O. */
|
|
1490
|
+
function childStatusToState(status, time, now = Date.now(), graceMs = DELEGATION_CHILD_STATUS_GRACE_MS) {
|
|
1491
|
+
if (status === "retry") return "retry";
|
|
1492
|
+
if (status === "busy") {
|
|
1493
|
+
if (time) {
|
|
1494
|
+
const lastActivity = Math.max(time.updated ?? 0, time.created ?? 0);
|
|
1495
|
+
if (lastActivity > 0 && now - lastActivity > 18e5) return "completed";
|
|
1496
|
+
}
|
|
1497
|
+
return "running";
|
|
1498
|
+
}
|
|
1499
|
+
if (status !== void 0) return "completed";
|
|
1500
|
+
const lastActivity = Math.max(time?.updated ?? 0, time?.created ?? 0);
|
|
1501
|
+
if (lastActivity > 0 && now - lastActivity < graceMs) return "running";
|
|
1502
|
+
return "completed";
|
|
1503
|
+
}
|
|
1504
|
+
/** Status-only row model: ONE glyph + ONE identity per row.
|
|
1505
|
+
*
|
|
1506
|
+
* A row is
|
|
1507
|
+
* `{glyph} {alias:7} {elapsed:>5}` plus a muted description line. The short
|
|
1508
|
+
* alias (`apo-1`) is the single identity; rows without a report alias show
|
|
1509
|
+
* the agent instead (never both). */
|
|
1510
|
+
/** Map every FSM/display state to one of the 4 row kinds. reconciled never
|
|
1511
|
+
* reaches the panel (the md parser drops it) but reads as done;
|
|
1512
|
+
* cancelled reads as done; startup_failed reads as failed while
|
|
1513
|
+
* startup_unknown (no error known) reads as retry. Pure. */
|
|
1514
|
+
function delegationRowStatus(state) {
|
|
1515
|
+
switch (state) {
|
|
1516
|
+
case "running":
|
|
1517
|
+
case "stale-running": return "active";
|
|
1518
|
+
case "retry":
|
|
1519
|
+
case "startup_unknown": return "retry";
|
|
1520
|
+
case "completed":
|
|
1521
|
+
case "cancelled": return "done";
|
|
1522
|
+
case "error":
|
|
1523
|
+
case "startup_failed": return "failed";
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
/** The only 4 glyphs a row may show: animated active, done, failed, retry.
|
|
1527
|
+
* Shape is redundant with color (never the only signal). Pure. */
|
|
1528
|
+
const DELEGATION_ROW_GLYPHS = {
|
|
1529
|
+
active: "⠋",
|
|
1530
|
+
done: "✓",
|
|
1531
|
+
failed: "✕",
|
|
1532
|
+
retry: "⟳"
|
|
1533
|
+
};
|
|
1534
|
+
function delegationRowGlyph(status) {
|
|
1535
|
+
return DELEGATION_ROW_GLYPHS[status];
|
|
1536
|
+
}
|
|
1537
|
+
/** Row marker (`<glyph> `) — the single state channel. `active` animates
|
|
1538
|
+
* through the spinner for the given frame counter (incremented every ~80ms);
|
|
1539
|
+
* every other kind is static. Pure. */
|
|
1540
|
+
function delegationRowMarker(state, frame = 0) {
|
|
1541
|
+
const status = delegationRowStatus(state);
|
|
1542
|
+
return `${status === "active" ? delegationSpinnerFrame(frame) : delegationRowGlyph(status)} `;
|
|
1543
|
+
}
|
|
1544
|
+
/** ONE identity per row: the short report alias (`apo-1`); a row without a
|
|
1545
|
+
* report alias (native task() child `native-task` / `native-<id>`, or an
|
|
1546
|
+
* alias-less native live row `live-<callID>` kept as children-only) shows
|
|
1547
|
+
* the agent instead — never alias + agent stacked. Pure. */
|
|
1548
|
+
function delegationRowIdentity(entry) {
|
|
1549
|
+
if (entry.agent === "") return entry.alias;
|
|
1550
|
+
if (entry.alias === "native-task" || entry.alias.startsWith("native-")) return entry.agent;
|
|
1551
|
+
if (entry.alias.startsWith("live-") && entry.source === "children-only") return entry.agent;
|
|
1552
|
+
return entry.alias;
|
|
1553
|
+
}
|
|
1554
|
+
/** Fixed alias cell, pad-right + hard-truncate to `width` (default 7) so
|
|
1555
|
+
* identities line up across rows. Pure. */
|
|
1556
|
+
const DELEGATION_ALIAS_WIDTH = 7;
|
|
1557
|
+
function formatDelegationAlias(identity, width = 7) {
|
|
1558
|
+
if (identity.length >= width) return identity.slice(0, width);
|
|
1559
|
+
return `${identity}${" ".repeat(width - identity.length)}`;
|
|
1560
|
+
}
|
|
1561
|
+
/** Max description width on the row detail line — the old 180-char slice
|
|
1562
|
+
* wrapped the sidebar; 44 keeps one readable line. */
|
|
1563
|
+
const DELEGATION_DESCRIPTION_MAX = 44;
|
|
1564
|
+
/** Lazily-built grapheme segmenter. `Intl.Segmenter` keeps ZWJ emoji families
|
|
1565
|
+
* (and skin-tone/variation sequences) intact; `Array.from` (code points) is
|
|
1566
|
+
* the fallback for runtimes without it. */
|
|
1567
|
+
let graphemeSegmenter;
|
|
1568
|
+
/** Split `text` into display graphemes so truncation never cuts a multi-unit
|
|
1569
|
+
* emoji in half (surrogate pair → mojibake). */
|
|
1570
|
+
function splitGraphemes(text) {
|
|
1571
|
+
if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
|
|
1572
|
+
graphemeSegmenter ??= new Intl.Segmenter("pt", { granularity: "grapheme" });
|
|
1573
|
+
return Array.from(graphemeSegmenter.segment(text), (s) => s.segment);
|
|
1574
|
+
}
|
|
1575
|
+
return Array.from(text);
|
|
1576
|
+
}
|
|
1577
|
+
/** Truncate a description to `max` graphemes appending `…` when cut. Exact
|
|
1578
|
+
* `max`-length text is left untouched. Grapheme-granular, so an emoji made of
|
|
1579
|
+
* several code points is never split into mojibake at the boundary. Pure. */
|
|
1580
|
+
function truncateDelegationDescription(text, max = 44) {
|
|
1581
|
+
const graphemes = splitGraphemes(text);
|
|
1582
|
+
if (graphemes.length <= max) return text;
|
|
1583
|
+
return `${graphemes.slice(0, Math.max(0, max - 1)).join("")}\u2026`;
|
|
1584
|
+
}
|
|
1585
|
+
/** Fixed elapsed-time box: right-aligned to `width` (default 5) so elapsed
|
|
1586
|
+
* values line up across rows, e.g. ` 12s`. A label that already fills
|
|
1587
|
+
* the box is returned untouched. Pure. */
|
|
1588
|
+
const DELEGATION_ELAPSED_WIDTH = 5;
|
|
1589
|
+
function formatDelegationElapsed(entry, now, width = 5) {
|
|
1590
|
+
const label = delegationElapsed(entry, now);
|
|
1591
|
+
if (label.length >= width) return label;
|
|
1592
|
+
return `${" ".repeat(width - label.length)}${label}`;
|
|
1593
|
+
}
|
|
1594
|
+
/** Colored left half of a row line: `<marker><alias:7> ` — the state glyph
|
|
1595
|
+
* plus the single identity, padded so the muted elapsed column aligns. The
|
|
1596
|
+
* row renders this colored lead separately from the muted elapsed tail. Pure. */
|
|
1597
|
+
function formatDelegationRowLead(entry, marker) {
|
|
1598
|
+
return `${marker}${formatDelegationAlias(delegationRowIdentity(entry))} `;
|
|
1599
|
+
}
|
|
1600
|
+
/** Visible-row ceiling for the panel: the remainder collapses into a single
|
|
1601
|
+
* "… +N more" line. Live rows render first, so a running job is never hidden
|
|
1602
|
+
* by the cap. */
|
|
1603
|
+
const DELEGATION_VISIBLE_CEILING = 8;
|
|
1604
|
+
/** Header summary: `(N active · M done)` plus `· K failed` only when K > 0.
|
|
1605
|
+
* Active counts running/retry AND display-only stale-running (a stale row is
|
|
1606
|
+
* still a live job — it must never read as done); failed counts
|
|
1607
|
+
* error/startup_failed; cancelled reads as done. Pure. */
|
|
1608
|
+
function formatDelegationHeader(entries) {
|
|
1609
|
+
let active = 0;
|
|
1610
|
+
let done = 0;
|
|
1611
|
+
let failed = 0;
|
|
1612
|
+
for (const e of entries) switch (delegationRowStatus(e.state)) {
|
|
1613
|
+
case "active":
|
|
1614
|
+
case "retry":
|
|
1615
|
+
active++;
|
|
1616
|
+
break;
|
|
1617
|
+
case "failed":
|
|
1618
|
+
failed++;
|
|
1619
|
+
break;
|
|
1620
|
+
case "done":
|
|
1621
|
+
done++;
|
|
1622
|
+
break;
|
|
1623
|
+
}
|
|
1624
|
+
const tail = failed > 0 ? ` · ${failed} failed` : "";
|
|
1625
|
+
return `(${active} active · ${done} done${tail})`;
|
|
1626
|
+
}
|
|
1627
|
+
/** Cap the panel: live rows first, then most-recent retained terminal rows, at
|
|
1628
|
+
* most `maxVisible` total. Hidden counts describe the retained render list,
|
|
1629
|
+
* not expired history. */
|
|
1630
|
+
function ceilingDelegationList(all, maxVisible = 8, now = Date.now()) {
|
|
1631
|
+
const { active, recent } = splitDelegationList(all, all.length, now);
|
|
1632
|
+
const visibleActive = active.slice(0, maxVisible);
|
|
1633
|
+
const visible = [...visibleActive, ...recent.slice(0, Math.max(0, maxVisible - visibleActive.length))];
|
|
1634
|
+
const hiddenActive = Math.max(0, active.length - visibleActive.length);
|
|
1635
|
+
const hiddenTerminal = Math.max(0, recent.length - (visible.length - visibleActive.length));
|
|
1636
|
+
return {
|
|
1637
|
+
visible,
|
|
1638
|
+
hidden: hiddenActive + hiddenTerminal,
|
|
1639
|
+
hiddenActive,
|
|
1640
|
+
hiddenTerminal
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
/** Split a display list into native task() rows vs pantheon_delegate rows.
|
|
1644
|
+
* Native = source 'children-only' (no delegate report); everything else counts
|
|
1645
|
+
* as pantheon. Pure — powers the hooks.log line. */
|
|
1646
|
+
function countDelegationSources(entries) {
|
|
1647
|
+
let native = 0;
|
|
1648
|
+
for (const e of entries) if (e.source === "children-only") native++;
|
|
1649
|
+
return {
|
|
1650
|
+
native,
|
|
1651
|
+
pantheon: entries.length - native,
|
|
1652
|
+
total: entries.length
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
/** Diagnostic hooks.log line for a panel re-fetch, with the children
|
|
1656
|
+
* breakdown (pantheon = children WITH a delegate report, native = children
|
|
1657
|
+
* WITHOUT one). Pure — the View logs the returned string verbatim. */
|
|
1658
|
+
function formatPanelLogLine(children, pantheon, native, md, events) {
|
|
1659
|
+
return `panel: children=${children}(pantheon=${pantheon} native=${native}) md=${md} events=${events}`;
|
|
1322
1660
|
}
|
|
1323
1661
|
/** Turn child sessions (PRIMARY) enriched with md reports into the display
|
|
1324
1662
|
* list. One entry per child id (duplicates across re-fetches collapse).
|
|
@@ -1328,12 +1666,38 @@ function delegationTag(entry) {
|
|
|
1328
1666
|
* itself (fallback 'agent'), state derived from its status, startedAt from
|
|
1329
1667
|
* time.created. A report-less child is a NATIVE task() child (every
|
|
1330
1668
|
* child of the current session — pantheon_delegate OR the native `task()`
|
|
1331
|
-
* tool — carries parentID = caller), so it gets source 'children-only'
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
* the
|
|
1669
|
+
* tool — carries parentID = caller), so it gets source 'children-only', a
|
|
1670
|
+
* per-child alias 'native-<last4 of the child id>' (one identity per native
|
|
1671
|
+
* row) instead of a report alias. The 'task nativa' description fallback
|
|
1672
|
+
* keeps the row non-empty when the child carries no title.
|
|
1673
|
+
* A FRESH terminal md state wins over the derived state (a stale md report
|
|
1674
|
+
* from a previous incarnation never flips a live child — see
|
|
1675
|
+
* mdTerminalCoversChild); a running md defers to
|
|
1676
|
+
* the child's live status. A running child is NEVER archived — it always
|
|
1677
|
+
* lands in the active split (splitDelegationList active = running/retry).
|
|
1678
|
+
* `parentSessionID` (the focused session id) is stamped on report-less
|
|
1679
|
+
* children so the row carries its origin session; omit it and they stay
|
|
1680
|
+
* unscoped ('').
|
|
1681
|
+
* Children outside {@link DELEGATION_CHILDREN_RECENCY_MS} are dropped before
|
|
1682
|
+
* they can enter the list (the per-session child list grows for the whole
|
|
1683
|
+
* session lifetime); the survivors are sorted running-first, most recent
|
|
1684
|
+
* first (compareDelegationEntries), so a ceiling hides the OLDEST rows.
|
|
1685
|
+
* Pure — no I/O. */
|
|
1686
|
+
/** Temporal guard for the children channel (same recycled-alias class as the
|
|
1687
|
+
* md merge): an md terminal report matched by taskID may still belong to
|
|
1688
|
+
* a PREVIOUS job incarnation. Accept it only when its Finalized timestamp
|
|
1689
|
+
* covers the child's latest activity, when either side has no timestamp to
|
|
1690
|
+
* compare, or when both startedAt agree on a single job incarnation.
|
|
1335
1691
|
* Pure — no I/O. */
|
|
1336
|
-
function
|
|
1692
|
+
function mdTerminalCoversChild(md, child) {
|
|
1693
|
+
const childUpdated = child.time?.updated;
|
|
1694
|
+
if (md.updatedAt !== null && childUpdated !== void 0) {
|
|
1695
|
+
if (md.updatedAt >= childUpdated) return true;
|
|
1696
|
+
return md.startedAt === child.time?.created;
|
|
1697
|
+
}
|
|
1698
|
+
return true;
|
|
1699
|
+
}
|
|
1700
|
+
function childrenToDelegationEntries(children, md, now = Date.now(), parentSessionID = "") {
|
|
1337
1701
|
const byTaskID = /* @__PURE__ */ new Map();
|
|
1338
1702
|
for (const m of md) if (m.taskID !== void 0 && !byTaskID.has(m.taskID)) byTaskID.set(m.taskID, m);
|
|
1339
1703
|
const out = [];
|
|
@@ -1342,17 +1706,19 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
|
|
|
1342
1706
|
if (child.id === "" || seen.has(child.id)) continue;
|
|
1343
1707
|
seen.add(child.id);
|
|
1344
1708
|
const mdEntry = byTaskID.get(child.id);
|
|
1345
|
-
const
|
|
1709
|
+
const lastActivity = Math.max(child.time?.updated ?? 0, child.time?.created ?? 0, mdEntry?.updatedAt ?? 0);
|
|
1710
|
+
if (lastActivity > 0 && now - lastActivity > 864e5) continue;
|
|
1711
|
+
const state = mdEntry !== void 0 && mdEntry.state !== "running" && mdTerminalCoversChild(mdEntry, child) ? mdEntry.state : childStatusToState(child.status, child.time, now);
|
|
1346
1712
|
out.push({
|
|
1347
|
-
alias: mdEntry?.alias ??
|
|
1348
|
-
sessionID: mdEntry?.sessionID ??
|
|
1713
|
+
alias: mdEntry?.alias ?? `native-${child.id.slice(-4)}`,
|
|
1714
|
+
sessionID: mdEntry?.sessionID ?? parentSessionID,
|
|
1349
1715
|
taskID: child.id,
|
|
1350
1716
|
agent: mdEntry?.agent ?? child.agent ?? "agent",
|
|
1351
1717
|
state,
|
|
1352
1718
|
startedAt: mdEntry?.startedAt ?? child.time?.created ?? now,
|
|
1353
1719
|
updatedAt: mdEntry?.updatedAt ?? (state === "running" || state === "stale-running" ? null : child.time?.updated ?? null),
|
|
1354
1720
|
timedOut: mdEntry?.timedOut ?? false,
|
|
1355
|
-
description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title
|
|
1721
|
+
description: mdEntry !== void 0 && mdEntry.description !== "" ? mdEntry.description : child.title !== void 0 && child.title !== "" ? child.title : "task nativa",
|
|
1356
1722
|
source: mdEntry !== void 0 ? "md" : "children-only"
|
|
1357
1723
|
});
|
|
1358
1724
|
}
|
|
@@ -1366,8 +1732,19 @@ function childrenToDelegationEntries(children, md, now = Date.now()) {
|
|
|
1366
1732
|
* unsubstituted "{sessionID}" placeholder can never be routed. */
|
|
1367
1733
|
function navigateToDelegationSession(route, taskID) {
|
|
1368
1734
|
if (typeof route?.navigate !== "function" || !isValidSessionId(taskID)) return false;
|
|
1369
|
-
|
|
1370
|
-
|
|
1735
|
+
try {
|
|
1736
|
+
const navigation = route.navigate("session", { sessionID: taskID });
|
|
1737
|
+
if (navigation !== void 0) Promise.resolve(navigation).catch(() => void 0);
|
|
1738
|
+
return true;
|
|
1739
|
+
} catch {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
/** Build the mouse handler used by each delegation row. */
|
|
1744
|
+
function createDelegationRowOpenHandler(route, taskID) {
|
|
1745
|
+
return () => {
|
|
1746
|
+
navigateToDelegationSession(route, taskID);
|
|
1747
|
+
};
|
|
1371
1748
|
}
|
|
1372
1749
|
/** Silence-by-default panel logger.
|
|
1373
1750
|
* @param projectRoot the PROJECT ROOT — the logger appends to
|
|
@@ -1395,7 +1772,11 @@ function createTuiLogger(projectRoot, module = "pantheon-tui") {
|
|
|
1395
1772
|
await mkdir(dirname(logPath), { recursive: true });
|
|
1396
1773
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1397
1774
|
await appendFile(logPath, `${lines.map((l) => `[${stamp}] [${module}] ${l}`).join("\n")}\n`, "utf8");
|
|
1398
|
-
} catch {
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
if (echo) try {
|
|
1777
|
+
process.stderr.write(`[${module}] file-log failed: ${formatArg(err)}\n`);
|
|
1778
|
+
} catch {}
|
|
1779
|
+
}
|
|
1399
1780
|
})();
|
|
1400
1781
|
if (echo) for (const line of lines) console.log(`[${module}] ${line}`);
|
|
1401
1782
|
};
|
|
@@ -1427,58 +1808,61 @@ function SessionRow(props) {
|
|
|
1427
1808
|
}
|
|
1428
1809
|
function DelegationRow(props) {
|
|
1429
1810
|
const theme = () => props.api.theme.current;
|
|
1430
|
-
const
|
|
1811
|
+
const tone = createMemo(() => {
|
|
1431
1812
|
const t = theme();
|
|
1432
|
-
switch (props.job.state) {
|
|
1433
|
-
case "
|
|
1434
|
-
case "stale-running": return t.error;
|
|
1435
|
-
case "completed": return t.success;
|
|
1813
|
+
switch (delegationStateTone(props.job.state)) {
|
|
1814
|
+
case "warning": return t.warning;
|
|
1436
1815
|
case "error": return t.error;
|
|
1816
|
+
case "success": return t.success;
|
|
1437
1817
|
default: return t.textMuted;
|
|
1438
1818
|
}
|
|
1439
1819
|
});
|
|
1440
|
-
const marker = createMemo(() =>
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
default: return "○ ";
|
|
1447
|
-
}
|
|
1448
|
-
});
|
|
1449
|
-
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
|
-
});
|
|
1454
|
-
const open = () => {
|
|
1455
|
-
navigateToDelegationSession(props.api.route, props.job.taskID);
|
|
1456
|
-
};
|
|
1820
|
+
const marker = createMemo(() => delegationRowMarker(props.job.state, props.frame));
|
|
1821
|
+
const lead = createMemo(() => formatDelegationRowLead(props.job, marker()));
|
|
1822
|
+
const elapsed = createMemo(() => formatDelegationElapsed(props.job, props.now));
|
|
1823
|
+
const description = createMemo(() => truncateDelegationDescription(props.job.description));
|
|
1824
|
+
const activity = createMemo(() => latestToolActivityFor(latestToolActivity, props.job.taskID, props.now));
|
|
1825
|
+
const open = createDelegationRowOpenHandler(props.api.route, props.job.taskID);
|
|
1457
1826
|
return (() => {
|
|
1458
|
-
var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("
|
|
1827
|
+
var _el$13 = createElement("box"), _el$14 = createElement("box"), _el$15 = createElement("text"), _el$16 = createElement("span"), _el$17 = createElement("span");
|
|
1459
1828
|
insertNode(_el$13, _el$14);
|
|
1460
|
-
insertNode(_el$13, _el$18);
|
|
1461
1829
|
setProp(_el$13, "onMouseDown", open);
|
|
1462
1830
|
insertNode(_el$14, _el$15);
|
|
1463
|
-
insertNode(_el$14, _el$16);
|
|
1464
|
-
insertNode(_el$14, _el$17);
|
|
1465
1831
|
setProp(_el$14, "flexDirection", "row");
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
insert(_el$
|
|
1469
|
-
insert(_el$
|
|
1832
|
+
insertNode(_el$15, _el$16);
|
|
1833
|
+
insertNode(_el$15, _el$17);
|
|
1834
|
+
insert(_el$16, lead);
|
|
1835
|
+
insert(_el$17, elapsed);
|
|
1836
|
+
insert(_el$13, createComponent(Show, {
|
|
1837
|
+
get when() {
|
|
1838
|
+
return description() !== "";
|
|
1839
|
+
},
|
|
1840
|
+
get children() {
|
|
1841
|
+
var _el$18 = createElement("text");
|
|
1842
|
+
insert(_el$18, () => ` ${description()}`);
|
|
1843
|
+
effect((_$p) => setProp(_el$18, "fg", tone(), _$p));
|
|
1844
|
+
return _el$18;
|
|
1845
|
+
}
|
|
1846
|
+
}), null);
|
|
1847
|
+
insert(_el$13, createComponent(Show, {
|
|
1848
|
+
get when() {
|
|
1849
|
+
return activity();
|
|
1850
|
+
},
|
|
1851
|
+
children: (a) => (() => {
|
|
1852
|
+
var _el$19 = createElement("text");
|
|
1853
|
+
insert(_el$19, () => ` ↳ ${truncateDelegationDescription(`${a().tool}${a().summary !== "" ? ` ${a().summary}` : ""}`)}`);
|
|
1854
|
+
effect((_$p) => setProp(_el$19, "fg", tone(), _$p));
|
|
1855
|
+
return _el$19;
|
|
1856
|
+
})()
|
|
1857
|
+
}), null);
|
|
1470
1858
|
effect((_p$) => {
|
|
1471
|
-
var _v$5 =
|
|
1472
|
-
_v$5 !== _p$.e && (_p$.e = setProp(_el$
|
|
1473
|
-
_v$6 !== _p$.t && (_p$.t = setProp(_el$
|
|
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));
|
|
1859
|
+
var _v$5 = tone(), _v$6 = tone();
|
|
1860
|
+
_v$5 !== _p$.e && (_p$.e = setProp(_el$16, "fg", _v$5, _p$.e));
|
|
1861
|
+
_v$6 !== _p$.t && (_p$.t = setProp(_el$17, "fg", _v$6, _p$.t));
|
|
1476
1862
|
return _p$;
|
|
1477
1863
|
}, {
|
|
1478
1864
|
e: void 0,
|
|
1479
|
-
t: void 0
|
|
1480
|
-
a: void 0,
|
|
1481
|
-
o: void 0
|
|
1865
|
+
t: void 0
|
|
1482
1866
|
});
|
|
1483
1867
|
return _el$13;
|
|
1484
1868
|
})();
|
|
@@ -1536,11 +1920,11 @@ function View(props) {
|
|
|
1536
1920
|
return (b.time?.updated ?? b.updated ?? 0) - ta;
|
|
1537
1921
|
}).slice(0, 8);
|
|
1538
1922
|
});
|
|
1539
|
-
const
|
|
1540
|
-
const panelLog = createTuiLogger(
|
|
1923
|
+
const projectRoot = createMemo(() => resolvePantheonRoot(props.api.state.path));
|
|
1924
|
+
const panelLog = createTuiLogger(projectRoot());
|
|
1541
1925
|
const [childDelegations, setChildDelegations] = createSignal([]);
|
|
1542
1926
|
const [now, setNow] = createSignal(Date.now());
|
|
1543
|
-
const [
|
|
1927
|
+
const [frame, setFrame] = createSignal(0);
|
|
1544
1928
|
let eventRefreshCount = 0;
|
|
1545
1929
|
let delegationsInflight = null;
|
|
1546
1930
|
const refreshDelegations = () => {
|
|
@@ -1550,7 +1934,7 @@ function View(props) {
|
|
|
1550
1934
|
const state = props.api.state;
|
|
1551
1935
|
let md = [];
|
|
1552
1936
|
try {
|
|
1553
|
-
md = await readAllDelegationEntries(
|
|
1937
|
+
md = await readAllDelegationEntries(projectRoot());
|
|
1554
1938
|
} catch {
|
|
1555
1939
|
md = [];
|
|
1556
1940
|
}
|
|
@@ -1559,13 +1943,14 @@ function View(props) {
|
|
|
1559
1943
|
api: props.api
|
|
1560
1944
|
});
|
|
1561
1945
|
if (sessionID === null) {
|
|
1562
|
-
setChildDelegations(
|
|
1563
|
-
panelLog.info(
|
|
1946
|
+
setChildDelegations([]);
|
|
1947
|
+
panelLog.info(`${formatPanelLogLine(0, 0, 0, md.length, eventRefreshCount)} (no sessionID — inactive panel)`);
|
|
1564
1948
|
return;
|
|
1565
1949
|
}
|
|
1566
1950
|
let children = [];
|
|
1567
1951
|
try {
|
|
1568
|
-
const
|
|
1952
|
+
const childrenPath = buildChildrenPath(sessionID);
|
|
1953
|
+
const result = childrenPath ? await props.api.client.session.children(childrenPath) : void 0;
|
|
1569
1954
|
const data = result?.data ?? result;
|
|
1570
1955
|
children = Array.isArray(data) ? data : [];
|
|
1571
1956
|
} catch (err) {
|
|
@@ -1575,7 +1960,7 @@ function View(props) {
|
|
|
1575
1960
|
const resolveStatus = (childID) => {
|
|
1576
1961
|
if (!isValidSessionId(childID)) return void 0;
|
|
1577
1962
|
try {
|
|
1578
|
-
return state
|
|
1963
|
+
return state.session.status(childID)?.type;
|
|
1579
1964
|
} catch {
|
|
1580
1965
|
return;
|
|
1581
1966
|
}
|
|
@@ -1583,18 +1968,20 @@ function View(props) {
|
|
|
1583
1968
|
const childEntries = childrenToDelegationEntries(children.map((c) => ({
|
|
1584
1969
|
...c,
|
|
1585
1970
|
status: resolveStatus(c.id)
|
|
1586
|
-
})), md, now());
|
|
1971
|
+
})), md, now(), sessionID);
|
|
1587
1972
|
for (const [k, v] of props.liveStore.map) if (v.alias === null && v.taskID === null && Date.now() - v.startedAt > 3e4) props.liveStore.map.delete(k);
|
|
1588
1973
|
const liveEntries = [...props.liveStore.map.values()].filter((entry) => entry.sessionID === sessionID);
|
|
1589
|
-
setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries));
|
|
1590
|
-
|
|
1974
|
+
setChildDelegations(filterDelegationsToSession(mergeChildDelegationSources(childEntries, liveEntries), sessionID));
|
|
1975
|
+
const counts = countDelegationSources(childEntries);
|
|
1976
|
+
panelLog.info(formatPanelLogLine(children.length, counts.pantheon, counts.native, md.length, eventRefreshCount));
|
|
1591
1977
|
} finally {
|
|
1592
1978
|
delegationsInflight = null;
|
|
1593
1979
|
}
|
|
1594
1980
|
})();
|
|
1595
1981
|
return delegationsInflight;
|
|
1596
1982
|
};
|
|
1597
|
-
const
|
|
1983
|
+
const delegationCeiling = createMemo(() => ceilingDelegationList(childDelegations(), 8, now()));
|
|
1984
|
+
const delegationHeader = createMemo(() => formatDelegationHeader(delegationCeiling().visible));
|
|
1598
1985
|
onMount(() => {
|
|
1599
1986
|
const cleanup = [];
|
|
1600
1987
|
try {
|
|
@@ -1623,7 +2010,7 @@ function View(props) {
|
|
|
1623
2010
|
refreshDelegations();
|
|
1624
2011
|
}, 1e3);
|
|
1625
2012
|
cleanup.push(() => clearInterval(poll));
|
|
1626
|
-
const animation = setInterval(() =>
|
|
2013
|
+
const animation = setInterval(() => setFrame((f) => f + 1), 80);
|
|
1627
2014
|
cleanup.push(() => clearInterval(animation));
|
|
1628
2015
|
try {
|
|
1629
2016
|
const sdk = props.api.state?.session;
|
|
@@ -1643,78 +2030,78 @@ function View(props) {
|
|
|
1643
2030
|
}));
|
|
1644
2031
|
});
|
|
1645
2032
|
const HR = () => (() => {
|
|
1646
|
-
var _el$
|
|
1647
|
-
insertNode(_el$
|
|
1648
|
-
effect((_$p) => setProp(_el$
|
|
1649
|
-
return _el$
|
|
2033
|
+
var _el$20 = createElement("text");
|
|
2034
|
+
insertNode(_el$20, createTextNode(`────────────────────────────`));
|
|
2035
|
+
effect((_$p) => setProp(_el$20, "fg", theme().textMuted, _$p));
|
|
2036
|
+
return _el$20;
|
|
1650
2037
|
})();
|
|
1651
2038
|
return (() => {
|
|
1652
|
-
var _el$
|
|
1653
|
-
insertNode(_el$
|
|
1654
|
-
insertNode(_el$
|
|
1655
|
-
insertNode(_el$
|
|
1656
|
-
setProp(_el$
|
|
1657
|
-
setProp(_el$
|
|
1658
|
-
setProp(_el$
|
|
1659
|
-
insert(_el$
|
|
1660
|
-
insert(_el$
|
|
2039
|
+
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");
|
|
2040
|
+
insertNode(_el$22, _el$23);
|
|
2041
|
+
insertNode(_el$22, _el$24);
|
|
2042
|
+
insertNode(_el$22, _el$28);
|
|
2043
|
+
setProp(_el$22, "flexDirection", "column");
|
|
2044
|
+
setProp(_el$22, "width", "100%");
|
|
2045
|
+
setProp(_el$23, "attributes", 1);
|
|
2046
|
+
insert(_el$23, () => `Pantheon${props.version ? ` v${props.version}` : ""}`);
|
|
2047
|
+
insert(_el$22, createComponent(Show, {
|
|
1661
2048
|
get when() {
|
|
1662
2049
|
return branch();
|
|
1663
2050
|
},
|
|
1664
2051
|
children: (b) => (() => {
|
|
1665
|
-
var _el$
|
|
1666
|
-
insert(_el$
|
|
1667
|
-
effect((_$p) => setProp(_el$
|
|
1668
|
-
return _el$
|
|
2052
|
+
var _el$33 = createElement("text");
|
|
2053
|
+
insert(_el$33, b);
|
|
2054
|
+
effect((_$p) => setProp(_el$33, "fg", theme().textMuted, _$p));
|
|
2055
|
+
return _el$33;
|
|
1669
2056
|
})()
|
|
1670
|
-
}), _el$
|
|
1671
|
-
insert(_el$
|
|
2057
|
+
}), _el$24);
|
|
2058
|
+
insert(_el$22, createComponent(Show, {
|
|
1672
2059
|
get when() {
|
|
1673
2060
|
return preset().name;
|
|
1674
2061
|
},
|
|
1675
2062
|
get fallback() {
|
|
1676
2063
|
return (() => {
|
|
1677
|
-
var _el$
|
|
1678
|
-
insertNode(_el$
|
|
1679
|
-
setProp(_el$
|
|
1680
|
-
setProp(_el$
|
|
1681
|
-
insertNode(_el$
|
|
1682
|
-
effect((_$p) => setProp(_el$
|
|
1683
|
-
return _el$
|
|
2064
|
+
var _el$34 = createElement("box"), _el$35 = createElement("text");
|
|
2065
|
+
insertNode(_el$34, _el$35);
|
|
2066
|
+
setProp(_el$34, "flexDirection", "row");
|
|
2067
|
+
setProp(_el$34, "gap", 1);
|
|
2068
|
+
insertNode(_el$35, createTextNode(`Preset: default`));
|
|
2069
|
+
effect((_$p) => setProp(_el$35, "fg", theme().textMuted, _$p));
|
|
2070
|
+
return _el$34;
|
|
1684
2071
|
})();
|
|
1685
2072
|
},
|
|
1686
2073
|
children: (name) => (() => {
|
|
1687
|
-
var _el$
|
|
1688
|
-
insertNode(_el$
|
|
1689
|
-
insertNode(_el$
|
|
1690
|
-
insertNode(_el$
|
|
1691
|
-
setProp(_el$
|
|
1692
|
-
setProp(_el$
|
|
1693
|
-
insertNode(_el$
|
|
1694
|
-
insert(_el$
|
|
1695
|
-
insert(_el$
|
|
2074
|
+
var _el$37 = createElement("box"), _el$38 = createElement("text"), _el$40 = createElement("text"), _el$41 = createElement("text");
|
|
2075
|
+
insertNode(_el$37, _el$38);
|
|
2076
|
+
insertNode(_el$37, _el$40);
|
|
2077
|
+
insertNode(_el$37, _el$41);
|
|
2078
|
+
setProp(_el$37, "flexDirection", "row");
|
|
2079
|
+
setProp(_el$37, "gap", 1);
|
|
2080
|
+
insertNode(_el$38, createTextNode(`⚡ Preset:`));
|
|
2081
|
+
insert(_el$40, name);
|
|
2082
|
+
insert(_el$41, () => `(${preset().source ?? ""})`);
|
|
1696
2083
|
effect((_p$) => {
|
|
1697
|
-
var _v$
|
|
1698
|
-
_v$
|
|
1699
|
-
_v$
|
|
1700
|
-
_v$
|
|
2084
|
+
var _v$10 = theme().textMuted, _v$11 = theme().accent, _v$12 = theme().textMuted;
|
|
2085
|
+
_v$10 !== _p$.e && (_p$.e = setProp(_el$38, "fg", _v$10, _p$.e));
|
|
2086
|
+
_v$11 !== _p$.t && (_p$.t = setProp(_el$40, "fg", _v$11, _p$.t));
|
|
2087
|
+
_v$12 !== _p$.a && (_p$.a = setProp(_el$41, "fg", _v$12, _p$.a));
|
|
1701
2088
|
return _p$;
|
|
1702
2089
|
}, {
|
|
1703
2090
|
e: void 0,
|
|
1704
2091
|
t: void 0,
|
|
1705
2092
|
a: void 0
|
|
1706
2093
|
});
|
|
1707
|
-
return _el$
|
|
2094
|
+
return _el$37;
|
|
1708
2095
|
})()
|
|
1709
|
-
}), _el$
|
|
1710
|
-
insert(_el$
|
|
1711
|
-
insertNode(_el$
|
|
1712
|
-
insertNode(_el$
|
|
1713
|
-
setProp(_el$
|
|
1714
|
-
setProp(_el$
|
|
1715
|
-
insert(_el$
|
|
1716
|
-
insert(_el$
|
|
1717
|
-
insert(_el$
|
|
2096
|
+
}), _el$24);
|
|
2097
|
+
insert(_el$22, createComponent(HR, {}), _el$24);
|
|
2098
|
+
insertNode(_el$24, _el$25);
|
|
2099
|
+
insertNode(_el$24, _el$26);
|
|
2100
|
+
setProp(_el$24, "onMouseDown", () => setShowSessions((x) => !x));
|
|
2101
|
+
setProp(_el$25, "attributes", 1);
|
|
2102
|
+
insert(_el$25, () => `${showSessions() ? "▼" : "▶"} Sessions`);
|
|
2103
|
+
insert(_el$26, () => ` (${String(totalSessions())})`);
|
|
2104
|
+
insert(_el$22, createComponent(Show, {
|
|
1718
2105
|
get when() {
|
|
1719
2106
|
return showSessions();
|
|
1720
2107
|
},
|
|
@@ -1725,19 +2112,19 @@ function View(props) {
|
|
|
1725
2112
|
},
|
|
1726
2113
|
get fallback() {
|
|
1727
2114
|
return (() => {
|
|
1728
|
-
var _el$
|
|
1729
|
-
insertNode(_el$
|
|
1730
|
-
setProp(_el$
|
|
1731
|
-
insertNode(_el$
|
|
1732
|
-
effect((_$p) => setProp(_el$
|
|
1733
|
-
return _el$
|
|
2115
|
+
var _el$42 = createElement("box"), _el$43 = createElement("text");
|
|
2116
|
+
insertNode(_el$42, _el$43);
|
|
2117
|
+
setProp(_el$42, "marginLeft", 1);
|
|
2118
|
+
insertNode(_el$43, createTextNode(`No recent sessions`));
|
|
2119
|
+
effect((_$p) => setProp(_el$43, "fg", theme().textMuted, _$p));
|
|
2120
|
+
return _el$42;
|
|
1734
2121
|
})();
|
|
1735
2122
|
},
|
|
1736
2123
|
get children() {
|
|
1737
|
-
var _el$
|
|
1738
|
-
setProp(_el$
|
|
1739
|
-
setProp(_el$
|
|
1740
|
-
insert(_el$
|
|
2124
|
+
var _el$27 = createElement("box");
|
|
2125
|
+
setProp(_el$27, "marginLeft", 1);
|
|
2126
|
+
setProp(_el$27, "flexDirection", "column");
|
|
2127
|
+
insert(_el$27, createComponent(For, {
|
|
1741
2128
|
get each() {
|
|
1742
2129
|
return recentSessions();
|
|
1743
2130
|
},
|
|
@@ -1748,43 +2135,46 @@ function View(props) {
|
|
|
1748
2135
|
session: ses
|
|
1749
2136
|
})
|
|
1750
2137
|
}));
|
|
1751
|
-
return _el$
|
|
2138
|
+
return _el$27;
|
|
1752
2139
|
}
|
|
1753
2140
|
});
|
|
1754
2141
|
}
|
|
1755
|
-
}), _el$
|
|
1756
|
-
insertNode(_el$
|
|
1757
|
-
insertNode(_el$
|
|
1758
|
-
setProp(_el$
|
|
1759
|
-
setProp(_el$
|
|
1760
|
-
insert(_el$
|
|
1761
|
-
insert(_el$
|
|
1762
|
-
|
|
2142
|
+
}), _el$28);
|
|
2143
|
+
insertNode(_el$28, _el$29);
|
|
2144
|
+
insertNode(_el$28, _el$30);
|
|
2145
|
+
setProp(_el$28, "onMouseDown", () => setShowDelegations((x) => !x));
|
|
2146
|
+
setProp(_el$29, "attributes", 1);
|
|
2147
|
+
insert(_el$29, () => `${showDelegations() ? "▼" : "▶"} Delegations`);
|
|
2148
|
+
insert(_el$30, (() => {
|
|
2149
|
+
var _c$ = memo(() => delegationCeiling().visible.length > 0);
|
|
2150
|
+
return () => _c$() ? ` ${delegationHeader()}` : " — idle";
|
|
2151
|
+
})());
|
|
2152
|
+
insert(_el$22, createComponent(Show, {
|
|
1763
2153
|
get when() {
|
|
1764
2154
|
return showDelegations();
|
|
1765
2155
|
},
|
|
1766
2156
|
get children() {
|
|
1767
2157
|
return createComponent(Show, {
|
|
1768
2158
|
get when() {
|
|
1769
|
-
return
|
|
2159
|
+
return delegationCeiling().visible.length > 0;
|
|
1770
2160
|
},
|
|
1771
2161
|
get fallback() {
|
|
1772
2162
|
return (() => {
|
|
1773
|
-
var _el$
|
|
1774
|
-
insertNode(_el$
|
|
1775
|
-
setProp(_el$
|
|
1776
|
-
insertNode(_el$
|
|
1777
|
-
effect((_$p) => setProp(_el$
|
|
1778
|
-
return _el$
|
|
2163
|
+
var _el$45 = createElement("box"), _el$46 = createElement("text");
|
|
2164
|
+
insertNode(_el$45, _el$46);
|
|
2165
|
+
setProp(_el$45, "marginLeft", 1);
|
|
2166
|
+
insertNode(_el$46, createTextNode(`No delegations`));
|
|
2167
|
+
effect((_$p) => setProp(_el$46, "fg", theme().textMuted, _$p));
|
|
2168
|
+
return _el$45;
|
|
1779
2169
|
})();
|
|
1780
2170
|
},
|
|
1781
2171
|
get children() {
|
|
1782
|
-
var _el$
|
|
1783
|
-
setProp(_el$
|
|
1784
|
-
setProp(_el$
|
|
1785
|
-
insert(_el$
|
|
2172
|
+
var _el$31 = createElement("box");
|
|
2173
|
+
setProp(_el$31, "marginLeft", 1);
|
|
2174
|
+
setProp(_el$31, "flexDirection", "column");
|
|
2175
|
+
insert(_el$31, createComponent(For, {
|
|
1786
2176
|
get each() {
|
|
1787
|
-
return
|
|
2177
|
+
return delegationCeiling().visible;
|
|
1788
2178
|
},
|
|
1789
2179
|
children: (job) => createComponent(DelegationRow, {
|
|
1790
2180
|
get api() {
|
|
@@ -1794,23 +2184,37 @@ function View(props) {
|
|
|
1794
2184
|
get now() {
|
|
1795
2185
|
return now();
|
|
1796
2186
|
},
|
|
1797
|
-
get
|
|
1798
|
-
return
|
|
2187
|
+
get frame() {
|
|
2188
|
+
return frame();
|
|
1799
2189
|
}
|
|
1800
2190
|
})
|
|
1801
|
-
}));
|
|
1802
|
-
|
|
2191
|
+
}), null);
|
|
2192
|
+
insert(_el$31, createComponent(Show, {
|
|
2193
|
+
get when() {
|
|
2194
|
+
return delegationCeiling().hidden > 0;
|
|
2195
|
+
},
|
|
2196
|
+
get children() {
|
|
2197
|
+
var _el$32 = createElement("text");
|
|
2198
|
+
insert(_el$32, (() => {
|
|
2199
|
+
var _c$2 = memo(() => delegationCeiling().hiddenActive > 0);
|
|
2200
|
+
return () => _c$2() ? `… +${delegationCeiling().hiddenActive} active` : `… +${delegationCeiling().hiddenTerminal} more`;
|
|
2201
|
+
})());
|
|
2202
|
+
effect((_$p) => setProp(_el$32, "fg", theme().textMuted, _$p));
|
|
2203
|
+
return _el$32;
|
|
2204
|
+
}
|
|
2205
|
+
}), null);
|
|
2206
|
+
return _el$31;
|
|
1803
2207
|
}
|
|
1804
2208
|
});
|
|
1805
2209
|
}
|
|
1806
2210
|
}), null);
|
|
1807
2211
|
effect((_p$) => {
|
|
1808
|
-
var _v$
|
|
1809
|
-
_v$
|
|
1810
|
-
_v$
|
|
1811
|
-
_v$
|
|
1812
|
-
_v$
|
|
1813
|
-
_v$
|
|
2212
|
+
var _v$7 = theme().accent, _v$8 = theme().text, _v$9 = theme().textMuted, _v$0 = theme().text, _v$1 = theme().textMuted;
|
|
2213
|
+
_v$7 !== _p$.e && (_p$.e = setProp(_el$23, "fg", _v$7, _p$.e));
|
|
2214
|
+
_v$8 !== _p$.t && (_p$.t = setProp(_el$25, "fg", _v$8, _p$.t));
|
|
2215
|
+
_v$9 !== _p$.a && (_p$.a = setProp(_el$26, "fg", _v$9, _p$.a));
|
|
2216
|
+
_v$0 !== _p$.o && (_p$.o = setProp(_el$29, "fg", _v$0, _p$.o));
|
|
2217
|
+
_v$1 !== _p$.i && (_p$.i = setProp(_el$30, "fg", _v$1, _p$.i));
|
|
1814
2218
|
return _p$;
|
|
1815
2219
|
}, {
|
|
1816
2220
|
e: void 0,
|
|
@@ -1819,7 +2223,7 @@ function View(props) {
|
|
|
1819
2223
|
o: void 0,
|
|
1820
2224
|
i: void 0
|
|
1821
2225
|
});
|
|
1822
|
-
return _el$
|
|
2226
|
+
return _el$22;
|
|
1823
2227
|
})();
|
|
1824
2228
|
}
|
|
1825
2229
|
const tui = (api, _options, _meta) => {
|
|
@@ -1839,6 +2243,8 @@ const tui = (api, _options, _meta) => {
|
|
|
1839
2243
|
const props = event?.properties ?? {};
|
|
1840
2244
|
const part = props.part ?? props.info?.part;
|
|
1841
2245
|
if (part === void 0) return;
|
|
2246
|
+
const sample = extractToolActivity(part);
|
|
2247
|
+
if (sample !== null) trackToolActivity(latestToolActivity, sample.sessionID, sample.activity);
|
|
1842
2248
|
if (reduceDelegationToolPart(liveStore.map, part)) liveStore.bump();
|
|
1843
2249
|
}));
|
|
1844
2250
|
} catch {}
|
|
@@ -1877,6 +2283,6 @@ const plugin = {
|
|
|
1877
2283
|
setup: async () => {}
|
|
1878
2284
|
};
|
|
1879
2285
|
//#endregion
|
|
1880
|
-
export { IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationSpinnerFrame,
|
|
2286
|
+
export { DELEGATION_ALIAS_WIDTH, DELEGATION_CHILDREN_RECENCY_MS, DELEGATION_CHILD_STATUS_GRACE_MS, DELEGATION_DESCRIPTION_MAX, DELEGATION_DONE_RETENTION_MS, DELEGATION_ELAPSED_WIDTH, DELEGATION_FAILED_RETENTION_MS, DELEGATION_ROW_GLYPHS, DELEGATION_VISIBLE_CEILING, IDLE_SILENCE_MS, NATIVE_LIVE_ALIASLESS_TTL_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, ceilingDelegationList, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, createDelegationRowOpenHandler, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationRowGlyph, delegationRowIdentity, delegationRowMarker, delegationRowStatus, delegationSpinnerFrame, delegationStateTone, extractToolActivity, filterDelegationsToSession, fmtElapsed, formatDelegationAlias, formatDelegationElapsed, formatDelegationHeader, formatDelegationRowLead, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, resolvePantheonRoot, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
|
|
1881
2287
|
|
|
1882
2288
|
//# sourceMappingURL=tui.js.map
|