pantheon-opencode 1.3.5-beta.60.efa533f → 1.3.6
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/README.md +38 -6
- package/package.json +4 -3
- package/scripts/_pantheon_paths.py +0 -1
- package/scripts/build-agents-md.mjs +4 -2
- package/scripts/code_mode_server.py +307 -21
- package/scripts/doctor.mjs +58 -15
- package/scripts/hooks/format-multi-language.sh +6 -3
- package/scripts/hooks/log-session-start.sh +6 -0
- package/scripts/hooks/on-subagent-delegation-start.sh +22 -6
- package/scripts/hooks/validate-post-conditions.sh +1 -1
- package/scripts/hooks/validate-tool-safety.sh +5 -2
- package/scripts/install/agents-md.mjs +3 -1
- package/scripts/install/cli-ui.mjs +24 -4
- package/scripts/install/interactive.mjs +18 -7
- package/scripts/install/migrate-config.mjs +13 -19
- package/scripts/install/migrate.mjs +8 -5
- package/scripts/install/model-picker.mjs +4 -1
- package/scripts/install/opencode.mjs +8 -2
- package/scripts/install/shared.mjs +8 -4
- package/scripts/install/state.mjs +9 -6
- package/scripts/mcp_persistence_server.py +23 -22
- package/scripts/mcp_resources_server.py +60 -1
- package/scripts/memory_mcp_server.py +80 -4
- package/scripts/postinstall.mjs +27 -25
- package/scripts/prune.mjs +1 -1
- package/scripts/redaction-gate.mjs +3 -1
- package/scripts/uninstall.mjs +0 -2
- package/scripts/validation-policy.mjs +6 -1
- package/scripts/version-check.mjs +9 -4
- package/src/agents/aphrodite.md +1 -1
- package/src/agents/apollo.md +3 -0
- package/src/agents/demeter.md +1 -1
- package/src/agents/gaia.md +3 -0
- package/src/agents/hephaestus.md +1 -1
- package/src/agents/hermes.md +1 -1
- package/src/agents/themis.md +4 -0
- package/src/agents/zeus.md +5 -1
- package/src/mcp/code_mode_server.py +304 -18
- package/src/mcp/eval_store.py +233 -0
- package/src/mcp/mcp_persistence_server.py +23 -22
- package/src/mcp/mcp_resources_server.py +58 -0
- package/src/mcp/memory_mcp_server.py +80 -3
- package/src/pantheon/auto-wake.ts +2 -4
- package/src/pantheon/background-job-board.ts +17 -4
- package/src/pantheon/comment-checker.ts +48 -0
- package/src/pantheon/delegation-classifier.ts +307 -0
- package/src/pantheon/delegation-enforce.ts +67 -3
- package/src/pantheon/delegation-finalize.ts +44 -4
- package/src/pantheon/delegation.ts +413 -30
- package/src/pantheon/dispatch-guard.ts +2 -0
- package/src/pantheon/file-persistence.ts +7 -3
- package/src/pantheon/goal-loop.ts +13 -2
- package/src/pantheon/permission-eval.ts +284 -0
- package/src/pantheon/permission-globs.ts +86 -0
- package/src/pantheon/presets.d.mts +41 -0
- package/src/pantheon/presets.mjs +135 -0
- package/src/pantheon/retry-policy.ts +212 -0
- package/src/pantheon/session-guard.ts +48 -0
- package/src/pantheon/step-cap.ts +110 -0
- package/src/pantheon/todo-enforcer.ts +31 -5
- package/src/pantheon/todo-preserve.ts +7 -1
- package/src/pantheon/vision.ts +7 -5
- package/src/pantheon/zeus-delegate-with-retry.ts +401 -0
- package/src/plugin.ts +72 -13
- package/src/plugins/hook-runner.ts +1 -1
- package/src/plugins/pantheon-hooks.ts +1 -1
- package/src/plugins/tui/dist/tui.d.ts +1 -1
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +36 -14
- package/src/plugins/tui/dist/tui.tsx +71 -16
- package/src/plugins/tui/package.json +1 -1
- package/src/plugins/tui/src/index.tsx +71 -16
- package/src/plugins/tui/tsdown.config.ts +1 -1
- package/src/routing.yml +47 -0
- package/src/agents/README.md +0 -26
|
@@ -49,6 +49,20 @@ import { For, Show, createEffect, createMemo, createResource, createSignal, onCl
|
|
|
49
49
|
* (the old ContextBar was removed) and we deliberately do NOT hack the
|
|
50
50
|
* native footer. Track upstream: opencode statusline config.
|
|
51
51
|
*/
|
|
52
|
+
const __tuiPluginOnceKey = "__pantheonPluginsLoaded";
|
|
53
|
+
function pantheonPluginOnce(key) {
|
|
54
|
+
try {
|
|
55
|
+
const g = globalThis;
|
|
56
|
+
if (g[__tuiPluginOnceKey] === void 0) g[__tuiPluginOnceKey] = /* @__PURE__ */ new Set();
|
|
57
|
+
const set = g[__tuiPluginOnceKey];
|
|
58
|
+
if (!(set instanceof Set)) return false;
|
|
59
|
+
if (set.has(key)) return true;
|
|
60
|
+
set.add(key);
|
|
61
|
+
return false;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
52
66
|
/** How often the sidebar re-reads .pantheon/active-preset.json so `set-tier`
|
|
53
67
|
* changes made while opencode is open show up within ~30s. */
|
|
54
68
|
const PRESET_REFRESH_MS = 3e4;
|
|
@@ -441,10 +455,10 @@ function parseConfig(raw) {
|
|
|
441
455
|
if (!toml?.parse) return defaultConfig();
|
|
442
456
|
const root = asTable(toml.parse(raw));
|
|
443
457
|
const cfg = defaultConfig();
|
|
444
|
-
const ui = asTable(root
|
|
445
|
-
cfg.showBars = bool(ui
|
|
446
|
-
cfg.showStatus = bool(ui
|
|
447
|
-
const rawWidth = ui
|
|
458
|
+
const ui = asTable(root.ui);
|
|
459
|
+
cfg.showBars = bool(ui.show_bars, cfg.showBars);
|
|
460
|
+
cfg.showStatus = bool(ui.show_status, cfg.showStatus);
|
|
461
|
+
const rawWidth = ui.bar_width;
|
|
448
462
|
if (typeof rawWidth === "number" && Number.isFinite(rawWidth) && rawWidth >= 1) cfg.barWidth = Math.min(40, Math.floor(rawWidth));
|
|
449
463
|
for (const id of [
|
|
450
464
|
"anthropic",
|
|
@@ -453,14 +467,14 @@ function parseConfig(raw) {
|
|
|
453
467
|
]) {
|
|
454
468
|
const t = asTable(root[id]);
|
|
455
469
|
const p = cfg.providers[id];
|
|
456
|
-
p.enabled = bool(t
|
|
457
|
-
p.show["5h"] = bool(t
|
|
458
|
-
p.show["7d"] = bool(t
|
|
459
|
-
p.show["1m"] = bool(t
|
|
460
|
-
p.show.model = bool(t
|
|
461
|
-
const credentialsPath = str(t
|
|
470
|
+
p.enabled = bool(t.enabled, p.enabled);
|
|
471
|
+
p.show["5h"] = bool(t.show_5h, p.show["5h"]);
|
|
472
|
+
p.show["7d"] = bool(t.show_7d, p.show["7d"]);
|
|
473
|
+
p.show["1m"] = bool(t.show_1m, p.show["1m"]);
|
|
474
|
+
p.show.model = bool(t.show_model, p.show.model);
|
|
475
|
+
const credentialsPath = str(t.credentials_path);
|
|
462
476
|
if (credentialsPath !== void 0) p.credentialsPath = credentialsPath;
|
|
463
|
-
const codexAuthPath = str(t
|
|
477
|
+
const codexAuthPath = str(t.codex_auth_path);
|
|
464
478
|
if (codexAuthPath !== void 0) p.codexAuthPath = codexAuthPath;
|
|
465
479
|
}
|
|
466
480
|
return cfg;
|
|
@@ -751,7 +765,7 @@ function parseDelegationMarkdown(raw, fileAlias, sessionID = "") {
|
|
|
751
765
|
const startedAt = started !== void 0 ? Date.parse(started) : NaN;
|
|
752
766
|
if (agent === void 0 || state === void 0 || Number.isNaN(startedAt)) return null;
|
|
753
767
|
const normalized = state.toLowerCase();
|
|
754
|
-
if (normalized !== "running" && normalized !== "completed" && normalized !== "error" && normalized !== "cancelled") return null;
|
|
768
|
+
if (normalized !== "running" && normalized !== "completed" && normalized !== "error" && normalized !== "startup_failed" && normalized !== "startup_unknown" && normalized !== "cancelled") return null;
|
|
755
769
|
const finalizedAt = finalized !== void 0 ? Date.parse(finalized) : NaN;
|
|
756
770
|
return {
|
|
757
771
|
alias: title ?? (fileAlias !== void 0 ? stripMdSuffix(fileAlias) : "unknown"),
|
|
@@ -884,7 +898,7 @@ function delegationElapsed(entry, now) {
|
|
|
884
898
|
* state machine testable without booting OpenCode's renderer. */
|
|
885
899
|
function delegationActivity(entry) {
|
|
886
900
|
if (entry.state === "completed") return "completed";
|
|
887
|
-
if (entry.state === "error") return "error";
|
|
901
|
+
if (entry.state === "error" || entry.state === "startup_failed" || entry.state === "startup_unknown") return "error";
|
|
888
902
|
if (entry.state === "cancelled") return "cancelled";
|
|
889
903
|
if (entry.read) return "reading";
|
|
890
904
|
if (entry.alias.startsWith("live-") && entry.taskID === void 0) return "delegating";
|
|
@@ -933,6 +947,7 @@ function mergeChildDelegationSources(children, live) {
|
|
|
933
947
|
bySessionAlias.set(key(entry.sessionID, entry.alias), index);
|
|
934
948
|
});
|
|
935
949
|
for (const liveEntry of live) {
|
|
950
|
+
if (liveEntry.alias === null && liveEntry.taskID === null && Date.now() - liveEntry.startedAt > 3e4) continue;
|
|
936
951
|
const incoming = toDelegationEntry(liveEntry);
|
|
937
952
|
const index = (incoming.taskID !== void 0 ? byTask.get(incoming.taskID) : void 0) ?? (incoming.alias !== "" ? bySessionAlias.get(key(incoming.sessionID, incoming.alias)) : void 0);
|
|
938
953
|
if (index === void 0) {
|
|
@@ -944,7 +959,12 @@ function mergeChildDelegationSources(children, live) {
|
|
|
944
959
|
}
|
|
945
960
|
const existing = result[index];
|
|
946
961
|
if (existing === void 0) continue;
|
|
947
|
-
if (existing.
|
|
962
|
+
if (existing.state !== "running") {
|
|
963
|
+
if (liveEntry.alias !== null && existing.alias !== incoming.alias) existing.alias = incoming.alias;
|
|
964
|
+
if (incoming.agent !== "agent" && existing.agent !== incoming.agent) existing.agent = incoming.agent;
|
|
965
|
+
if (incoming.read && !existing.read) existing.read = true;
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
948
968
|
result[index] = {
|
|
949
969
|
...existing,
|
|
950
970
|
alias: liveEntry.alias !== null ? incoming.alias : existing.alias,
|
|
@@ -1530,6 +1550,7 @@ function View(props) {
|
|
|
1530
1550
|
...c,
|
|
1531
1551
|
status: resolveStatus(c.id)
|
|
1532
1552
|
})), md, now());
|
|
1553
|
+
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);
|
|
1533
1554
|
const liveEntries = [...props.liveStore.map.values()].filter((entry) => entry.sessionID === sessionID);
|
|
1534
1555
|
setChildDelegations(mergeChildDelegationSources(childEntries, liveEntries));
|
|
1535
1556
|
panelLog.info(`panel: children=${children.length} md=${md.length} events=${eventRefreshCount}`);
|
|
@@ -1768,6 +1789,7 @@ function View(props) {
|
|
|
1768
1789
|
})();
|
|
1769
1790
|
}
|
|
1770
1791
|
const tui = (api, _options, _meta) => {
|
|
1792
|
+
if (pantheonPluginOnce("pantheon:tui")) return;
|
|
1771
1793
|
const [version, setVersion] = createSignal(null);
|
|
1772
1794
|
detectVersion(api).then((detected) => setVersion(detected)).catch(() => setVersion(null));
|
|
1773
1795
|
setupUsageBar(api);
|
|
@@ -63,6 +63,24 @@ import {
|
|
|
63
63
|
Show,
|
|
64
64
|
} from 'solid-js'
|
|
65
65
|
|
|
66
|
+
// ─── Double-load guard (mirrors src/pantheon/plugin-once.ts) ───────────────
|
|
67
|
+
// Opencode merges global + project plugin paths, so this module can load twice
|
|
68
|
+
// in one process. The second factory invocation becomes a no-op.
|
|
69
|
+
const __tuiPluginOnceKey = '__pantheonPluginsLoaded'
|
|
70
|
+
function pantheonPluginOnce(key: string): boolean {
|
|
71
|
+
try {
|
|
72
|
+
const g = globalThis as unknown as Record<string, unknown>
|
|
73
|
+
if (g[__tuiPluginOnceKey] === undefined) g[__tuiPluginOnceKey] = new Set<string>()
|
|
74
|
+
const set = g[__tuiPluginOnceKey]
|
|
75
|
+
if (!(set instanceof Set)) return false
|
|
76
|
+
if ((set as Set<string>).has(key)) return true
|
|
77
|
+
;(set as Set<string>).add(key)
|
|
78
|
+
return false
|
|
79
|
+
} catch {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
/* ─── Constants ─────────────────────────────────────────── */
|
|
67
85
|
|
|
68
86
|
/** How often the sidebar re-reads .pantheon/active-preset.json so `set-tier`
|
|
@@ -659,25 +677,25 @@ function parseConfig(raw: string): UsageBarConfig {
|
|
|
659
677
|
const root = asTable(toml.parse(raw))
|
|
660
678
|
const cfg = defaultConfig()
|
|
661
679
|
|
|
662
|
-
const ui = asTable(root
|
|
663
|
-
cfg.showBars = bool(ui
|
|
664
|
-
cfg.showStatus = bool(ui
|
|
665
|
-
const rawWidth = ui
|
|
680
|
+
const ui = asTable(root.ui)
|
|
681
|
+
cfg.showBars = bool(ui.show_bars, cfg.showBars)
|
|
682
|
+
cfg.showStatus = bool(ui.show_status, cfg.showStatus)
|
|
683
|
+
const rawWidth = ui.bar_width
|
|
666
684
|
if (typeof rawWidth === 'number' && Number.isFinite(rawWidth) && rawWidth >= 1)
|
|
667
685
|
cfg.barWidth = Math.min(40, Math.floor(rawWidth))
|
|
668
686
|
|
|
669
687
|
for (const id of ['anthropic', 'openai', 'opencodego'] as ProviderId[]) {
|
|
670
688
|
const t = asTable(root[id])
|
|
671
689
|
const p = cfg.providers[id]
|
|
672
|
-
p.enabled = bool(t
|
|
673
|
-
p.show['5h'] = bool(t
|
|
674
|
-
p.show['7d'] = bool(t
|
|
675
|
-
p.show['1m'] = bool(t
|
|
676
|
-
p.show.model = bool(t
|
|
690
|
+
p.enabled = bool(t.enabled, p.enabled)
|
|
691
|
+
p.show['5h'] = bool(t.show_5h, p.show['5h'])
|
|
692
|
+
p.show['7d'] = bool(t.show_7d, p.show['7d'])
|
|
693
|
+
p.show['1m'] = bool(t.show_1m, p.show['1m'])
|
|
694
|
+
p.show.model = bool(t.show_model, p.show.model)
|
|
677
695
|
// Assign only when present — keeps `exactOptionalPropertyTypes` happy.
|
|
678
|
-
const credentialsPath = str(t
|
|
696
|
+
const credentialsPath = str(t.credentials_path)
|
|
679
697
|
if (credentialsPath !== undefined) p.credentialsPath = credentialsPath
|
|
680
|
-
const codexAuthPath = str(t
|
|
698
|
+
const codexAuthPath = str(t.codex_auth_path)
|
|
681
699
|
if (codexAuthPath !== undefined) p.codexAuthPath = codexAuthPath
|
|
682
700
|
}
|
|
683
701
|
return cfg
|
|
@@ -924,7 +942,7 @@ export type DelegationEntry = {
|
|
|
924
942
|
taskID?: string
|
|
925
943
|
/** Agent name, e.g. "apollo". */
|
|
926
944
|
agent: string
|
|
927
|
-
state: 'running' | 'completed' | 'error' | 'cancelled'
|
|
945
|
+
state: 'running' | 'completed' | 'error' | 'startup_failed' | 'startup_unknown' | 'cancelled'
|
|
928
946
|
/** Epoch ms of the `Started` header. */
|
|
929
947
|
startedAt: number
|
|
930
948
|
/** Epoch ms of the `Finalized` header — null while still running. */
|
|
@@ -1034,6 +1052,8 @@ export function parseDelegationMarkdown(
|
|
|
1034
1052
|
normalized !== 'running' &&
|
|
1035
1053
|
normalized !== 'completed' &&
|
|
1036
1054
|
normalized !== 'error' &&
|
|
1055
|
+
normalized !== 'startup_failed' &&
|
|
1056
|
+
normalized !== 'startup_unknown' &&
|
|
1037
1057
|
normalized !== 'cancelled'
|
|
1038
1058
|
) {
|
|
1039
1059
|
return null
|
|
@@ -1203,7 +1223,12 @@ export type DelegationActivity =
|
|
|
1203
1223
|
|
|
1204
1224
|
export function delegationActivity(entry: DelegationEntry): DelegationActivity {
|
|
1205
1225
|
if (entry.state === 'completed') return 'completed'
|
|
1206
|
-
if (
|
|
1226
|
+
if (
|
|
1227
|
+
entry.state === 'error' ||
|
|
1228
|
+
entry.state === 'startup_failed' ||
|
|
1229
|
+
entry.state === 'startup_unknown'
|
|
1230
|
+
)
|
|
1231
|
+
return 'error'
|
|
1207
1232
|
if (entry.state === 'cancelled') return 'cancelled'
|
|
1208
1233
|
if (entry.read) return 'reading'
|
|
1209
1234
|
if (entry.alias.startsWith('live-') && entry.taskID === undefined) return 'delegating'
|
|
@@ -1256,6 +1281,12 @@ export function mergeChildDelegationSources(
|
|
|
1256
1281
|
})
|
|
1257
1282
|
|
|
1258
1283
|
for (const liveEntry of live) {
|
|
1284
|
+
if (
|
|
1285
|
+
liveEntry.alias === null &&
|
|
1286
|
+
liveEntry.taskID === null &&
|
|
1287
|
+
Date.now() - liveEntry.startedAt > 30_000
|
|
1288
|
+
)
|
|
1289
|
+
continue
|
|
1259
1290
|
const incoming = toDelegationEntry(liveEntry)
|
|
1260
1291
|
const index =
|
|
1261
1292
|
(incoming.taskID !== undefined ? byTask.get(incoming.taskID) : undefined) ??
|
|
@@ -1273,8 +1304,26 @@ export function mergeChildDelegationSources(
|
|
|
1273
1304
|
|
|
1274
1305
|
const existing = result[index]
|
|
1275
1306
|
if (existing === undefined) continue
|
|
1276
|
-
|
|
1277
|
-
|
|
1307
|
+
// Any terminal state is authoritative — a child session that went idle
|
|
1308
|
+
// correctly derives 'completed' via childStatusToState before the MD
|
|
1309
|
+
// report is written. The live tool-part channel stays 'running' until
|
|
1310
|
+
// pantheon_delegation_read fires, so allowing it to overwrite a
|
|
1311
|
+
// terminal state would flip completed → running (the bug this fixes).
|
|
1312
|
+
//
|
|
1313
|
+
// When the existing entry IS terminal, we still absorb live metadata
|
|
1314
|
+
// (alias, agent, read) but preserve the terminal state.
|
|
1315
|
+
if (existing.state !== 'running') {
|
|
1316
|
+
if (liveEntry.alias !== null && existing.alias !== incoming.alias) {
|
|
1317
|
+
existing.alias = incoming.alias
|
|
1318
|
+
}
|
|
1319
|
+
if (incoming.agent !== 'agent' && existing.agent !== incoming.agent) {
|
|
1320
|
+
existing.agent = incoming.agent
|
|
1321
|
+
}
|
|
1322
|
+
if (incoming.read && !existing.read) {
|
|
1323
|
+
existing.read = true
|
|
1324
|
+
}
|
|
1325
|
+
continue
|
|
1326
|
+
}
|
|
1278
1327
|
result[index] = {
|
|
1279
1328
|
...existing,
|
|
1280
1329
|
alias: liveEntry.alias !== null ? incoming.alias : existing.alias,
|
|
@@ -2201,6 +2250,9 @@ function View(props: {
|
|
|
2201
2250
|
}
|
|
2202
2251
|
const withStatus = children.map((c) => ({ ...c, status: resolveStatus(c.id) }))
|
|
2203
2252
|
const childEntries = childrenToDelegationEntries(withStatus, md, now())
|
|
2253
|
+
for (const [k, v] of props.liveStore.map)
|
|
2254
|
+
if (v.alias === null && v.taskID === null && Date.now() - v.startedAt > 30_000)
|
|
2255
|
+
props.liveStore.map.delete(k)
|
|
2204
2256
|
// The event channel is optimistic: it renders a job even while the
|
|
2205
2257
|
// child session/report is still being created. Filter by parent so a
|
|
2206
2258
|
// different focused session never leaks rows into this sidebar.
|
|
@@ -2397,6 +2449,7 @@ function View(props: {
|
|
|
2397
2449
|
/* ─── Plugin Registration ────────────────────────────────── */
|
|
2398
2450
|
|
|
2399
2451
|
const tui: TuiPlugin = (api, _options, _meta) => {
|
|
2452
|
+
if (pantheonPluginOnce('pantheon:tui')) return
|
|
2400
2453
|
// Do not await project file/process probes here. OpenCode waits for the TUI
|
|
2401
2454
|
// factory before it finishes startup, and a Windows host opening a WSL
|
|
2402
2455
|
// project can leave those probes pending while the project bridge comes up.
|
|
@@ -2467,7 +2520,9 @@ const tui: TuiPlugin = (api, _options, _meta) => {
|
|
|
2467
2520
|
order: 900,
|
|
2468
2521
|
slots: {
|
|
2469
2522
|
sidebar_content(_ctx, props) {
|
|
2470
|
-
return
|
|
2523
|
+
return (
|
|
2524
|
+
<View api={api} sessionID={props.session_id} version={version()} liveStore={liveStore} />
|
|
2525
|
+
)
|
|
2471
2526
|
},
|
|
2472
2527
|
},
|
|
2473
2528
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.6",
|
|
5
5
|
"description": "Pantheon TUI plugin — sidebar (version, preset, sessions, real-time delegations) + AI subscription usage gauges incl. OpenCode Go/Zen dollar limits (vendored, MIT)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -63,6 +63,24 @@ import {
|
|
|
63
63
|
Show,
|
|
64
64
|
} from 'solid-js'
|
|
65
65
|
|
|
66
|
+
// ─── Double-load guard (mirrors src/pantheon/plugin-once.ts) ───────────────
|
|
67
|
+
// Opencode merges global + project plugin paths, so this module can load twice
|
|
68
|
+
// in one process. The second factory invocation becomes a no-op.
|
|
69
|
+
const __tuiPluginOnceKey = '__pantheonPluginsLoaded'
|
|
70
|
+
function pantheonPluginOnce(key: string): boolean {
|
|
71
|
+
try {
|
|
72
|
+
const g = globalThis as unknown as Record<string, unknown>
|
|
73
|
+
if (g[__tuiPluginOnceKey] === undefined) g[__tuiPluginOnceKey] = new Set<string>()
|
|
74
|
+
const set = g[__tuiPluginOnceKey]
|
|
75
|
+
if (!(set instanceof Set)) return false
|
|
76
|
+
if ((set as Set<string>).has(key)) return true
|
|
77
|
+
;(set as Set<string>).add(key)
|
|
78
|
+
return false
|
|
79
|
+
} catch {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
/* ─── Constants ─────────────────────────────────────────── */
|
|
67
85
|
|
|
68
86
|
/** How often the sidebar re-reads .pantheon/active-preset.json so `set-tier`
|
|
@@ -659,25 +677,25 @@ function parseConfig(raw: string): UsageBarConfig {
|
|
|
659
677
|
const root = asTable(toml.parse(raw))
|
|
660
678
|
const cfg = defaultConfig()
|
|
661
679
|
|
|
662
|
-
const ui = asTable(root
|
|
663
|
-
cfg.showBars = bool(ui
|
|
664
|
-
cfg.showStatus = bool(ui
|
|
665
|
-
const rawWidth = ui
|
|
680
|
+
const ui = asTable(root.ui)
|
|
681
|
+
cfg.showBars = bool(ui.show_bars, cfg.showBars)
|
|
682
|
+
cfg.showStatus = bool(ui.show_status, cfg.showStatus)
|
|
683
|
+
const rawWidth = ui.bar_width
|
|
666
684
|
if (typeof rawWidth === 'number' && Number.isFinite(rawWidth) && rawWidth >= 1)
|
|
667
685
|
cfg.barWidth = Math.min(40, Math.floor(rawWidth))
|
|
668
686
|
|
|
669
687
|
for (const id of ['anthropic', 'openai', 'opencodego'] as ProviderId[]) {
|
|
670
688
|
const t = asTable(root[id])
|
|
671
689
|
const p = cfg.providers[id]
|
|
672
|
-
p.enabled = bool(t
|
|
673
|
-
p.show['5h'] = bool(t
|
|
674
|
-
p.show['7d'] = bool(t
|
|
675
|
-
p.show['1m'] = bool(t
|
|
676
|
-
p.show.model = bool(t
|
|
690
|
+
p.enabled = bool(t.enabled, p.enabled)
|
|
691
|
+
p.show['5h'] = bool(t.show_5h, p.show['5h'])
|
|
692
|
+
p.show['7d'] = bool(t.show_7d, p.show['7d'])
|
|
693
|
+
p.show['1m'] = bool(t.show_1m, p.show['1m'])
|
|
694
|
+
p.show.model = bool(t.show_model, p.show.model)
|
|
677
695
|
// Assign only when present — keeps `exactOptionalPropertyTypes` happy.
|
|
678
|
-
const credentialsPath = str(t
|
|
696
|
+
const credentialsPath = str(t.credentials_path)
|
|
679
697
|
if (credentialsPath !== undefined) p.credentialsPath = credentialsPath
|
|
680
|
-
const codexAuthPath = str(t
|
|
698
|
+
const codexAuthPath = str(t.codex_auth_path)
|
|
681
699
|
if (codexAuthPath !== undefined) p.codexAuthPath = codexAuthPath
|
|
682
700
|
}
|
|
683
701
|
return cfg
|
|
@@ -924,7 +942,7 @@ export type DelegationEntry = {
|
|
|
924
942
|
taskID?: string
|
|
925
943
|
/** Agent name, e.g. "apollo". */
|
|
926
944
|
agent: string
|
|
927
|
-
state: 'running' | 'completed' | 'error' | 'cancelled'
|
|
945
|
+
state: 'running' | 'completed' | 'error' | 'startup_failed' | 'startup_unknown' | 'cancelled'
|
|
928
946
|
/** Epoch ms of the `Started` header. */
|
|
929
947
|
startedAt: number
|
|
930
948
|
/** Epoch ms of the `Finalized` header — null while still running. */
|
|
@@ -1034,6 +1052,8 @@ export function parseDelegationMarkdown(
|
|
|
1034
1052
|
normalized !== 'running' &&
|
|
1035
1053
|
normalized !== 'completed' &&
|
|
1036
1054
|
normalized !== 'error' &&
|
|
1055
|
+
normalized !== 'startup_failed' &&
|
|
1056
|
+
normalized !== 'startup_unknown' &&
|
|
1037
1057
|
normalized !== 'cancelled'
|
|
1038
1058
|
) {
|
|
1039
1059
|
return null
|
|
@@ -1203,7 +1223,12 @@ export type DelegationActivity =
|
|
|
1203
1223
|
|
|
1204
1224
|
export function delegationActivity(entry: DelegationEntry): DelegationActivity {
|
|
1205
1225
|
if (entry.state === 'completed') return 'completed'
|
|
1206
|
-
if (
|
|
1226
|
+
if (
|
|
1227
|
+
entry.state === 'error' ||
|
|
1228
|
+
entry.state === 'startup_failed' ||
|
|
1229
|
+
entry.state === 'startup_unknown'
|
|
1230
|
+
)
|
|
1231
|
+
return 'error'
|
|
1207
1232
|
if (entry.state === 'cancelled') return 'cancelled'
|
|
1208
1233
|
if (entry.read) return 'reading'
|
|
1209
1234
|
if (entry.alias.startsWith('live-') && entry.taskID === undefined) return 'delegating'
|
|
@@ -1256,6 +1281,12 @@ export function mergeChildDelegationSources(
|
|
|
1256
1281
|
})
|
|
1257
1282
|
|
|
1258
1283
|
for (const liveEntry of live) {
|
|
1284
|
+
if (
|
|
1285
|
+
liveEntry.alias === null &&
|
|
1286
|
+
liveEntry.taskID === null &&
|
|
1287
|
+
Date.now() - liveEntry.startedAt > 30_000
|
|
1288
|
+
)
|
|
1289
|
+
continue
|
|
1259
1290
|
const incoming = toDelegationEntry(liveEntry)
|
|
1260
1291
|
const index =
|
|
1261
1292
|
(incoming.taskID !== undefined ? byTask.get(incoming.taskID) : undefined) ??
|
|
@@ -1273,8 +1304,26 @@ export function mergeChildDelegationSources(
|
|
|
1273
1304
|
|
|
1274
1305
|
const existing = result[index]
|
|
1275
1306
|
if (existing === undefined) continue
|
|
1276
|
-
|
|
1277
|
-
|
|
1307
|
+
// Any terminal state is authoritative — a child session that went idle
|
|
1308
|
+
// correctly derives 'completed' via childStatusToState before the MD
|
|
1309
|
+
// report is written. The live tool-part channel stays 'running' until
|
|
1310
|
+
// pantheon_delegation_read fires, so allowing it to overwrite a
|
|
1311
|
+
// terminal state would flip completed → running (the bug this fixes).
|
|
1312
|
+
//
|
|
1313
|
+
// When the existing entry IS terminal, we still absorb live metadata
|
|
1314
|
+
// (alias, agent, read) but preserve the terminal state.
|
|
1315
|
+
if (existing.state !== 'running') {
|
|
1316
|
+
if (liveEntry.alias !== null && existing.alias !== incoming.alias) {
|
|
1317
|
+
existing.alias = incoming.alias
|
|
1318
|
+
}
|
|
1319
|
+
if (incoming.agent !== 'agent' && existing.agent !== incoming.agent) {
|
|
1320
|
+
existing.agent = incoming.agent
|
|
1321
|
+
}
|
|
1322
|
+
if (incoming.read && !existing.read) {
|
|
1323
|
+
existing.read = true
|
|
1324
|
+
}
|
|
1325
|
+
continue
|
|
1326
|
+
}
|
|
1278
1327
|
result[index] = {
|
|
1279
1328
|
...existing,
|
|
1280
1329
|
alias: liveEntry.alias !== null ? incoming.alias : existing.alias,
|
|
@@ -2201,6 +2250,9 @@ function View(props: {
|
|
|
2201
2250
|
}
|
|
2202
2251
|
const withStatus = children.map((c) => ({ ...c, status: resolveStatus(c.id) }))
|
|
2203
2252
|
const childEntries = childrenToDelegationEntries(withStatus, md, now())
|
|
2253
|
+
for (const [k, v] of props.liveStore.map)
|
|
2254
|
+
if (v.alias === null && v.taskID === null && Date.now() - v.startedAt > 30_000)
|
|
2255
|
+
props.liveStore.map.delete(k)
|
|
2204
2256
|
// The event channel is optimistic: it renders a job even while the
|
|
2205
2257
|
// child session/report is still being created. Filter by parent so a
|
|
2206
2258
|
// different focused session never leaks rows into this sidebar.
|
|
@@ -2397,6 +2449,7 @@ function View(props: {
|
|
|
2397
2449
|
/* ─── Plugin Registration ────────────────────────────────── */
|
|
2398
2450
|
|
|
2399
2451
|
const tui: TuiPlugin = (api, _options, _meta) => {
|
|
2452
|
+
if (pantheonPluginOnce('pantheon:tui')) return
|
|
2400
2453
|
// Do not await project file/process probes here. OpenCode waits for the TUI
|
|
2401
2454
|
// factory before it finishes startup, and a Windows host opening a WSL
|
|
2402
2455
|
// project can leave those probes pending while the project bridge comes up.
|
|
@@ -2467,7 +2520,9 @@ const tui: TuiPlugin = (api, _options, _meta) => {
|
|
|
2467
2520
|
order: 900,
|
|
2468
2521
|
slots: {
|
|
2469
2522
|
sidebar_content(_ctx, props) {
|
|
2470
|
-
return
|
|
2523
|
+
return (
|
|
2524
|
+
<View api={api} sessionID={props.session_id} version={version()} liveStore={liveStore} />
|
|
2525
|
+
)
|
|
2471
2526
|
},
|
|
2472
2527
|
},
|
|
2473
2528
|
})
|
package/src/routing.yml
CHANGED
|
@@ -151,6 +151,7 @@ presets:
|
|
|
151
151
|
agents:
|
|
152
152
|
zeus:
|
|
153
153
|
role: Central orchestrator
|
|
154
|
+
max_steps: 40 # R4: step budget — at max_steps the agent is forced to summarize-and-stop. Tracker is permanent-per-process (no reset wiring).
|
|
154
155
|
reasoning_effort: medium
|
|
155
156
|
user_invocable: true
|
|
156
157
|
can_delegate: true
|
|
@@ -189,6 +190,7 @@ agents:
|
|
|
189
190
|
- incremental-implementation
|
|
190
191
|
athena:
|
|
191
192
|
role: Strategic planner
|
|
193
|
+
max_steps: 15 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
192
194
|
reasoning_effort: high
|
|
193
195
|
user_invocable: true
|
|
194
196
|
can_delegate: true
|
|
@@ -208,6 +210,7 @@ agents:
|
|
|
208
210
|
- memory-bank
|
|
209
211
|
apollo:
|
|
210
212
|
role: Investigation scout
|
|
213
|
+
max_steps: 25 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
211
214
|
reasoning_effort: low
|
|
212
215
|
user_invocable: false
|
|
213
216
|
can_delegate: false
|
|
@@ -225,6 +228,7 @@ agents:
|
|
|
225
228
|
- auto-continue
|
|
226
229
|
hermes:
|
|
227
230
|
role: Backend specialist
|
|
231
|
+
max_steps: 60 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
228
232
|
reasoning_effort: medium
|
|
229
233
|
user_invocable: true
|
|
230
234
|
can_delegate: true
|
|
@@ -247,6 +251,7 @@ agents:
|
|
|
247
251
|
- incremental-implementation
|
|
248
252
|
aphrodite:
|
|
249
253
|
role: Frontend specialist
|
|
254
|
+
max_steps: 60 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
250
255
|
reasoning_effort: medium
|
|
251
256
|
user_invocable: true
|
|
252
257
|
can_delegate: false
|
|
@@ -267,6 +272,7 @@ agents:
|
|
|
267
272
|
- file-prompts
|
|
268
273
|
demeter:
|
|
269
274
|
role: Database specialist
|
|
275
|
+
max_steps: 50 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
270
276
|
reasoning_effort: medium
|
|
271
277
|
user_invocable: true
|
|
272
278
|
can_delegate: false
|
|
@@ -286,6 +292,7 @@ agents:
|
|
|
286
292
|
- incremental-implementation
|
|
287
293
|
themis:
|
|
288
294
|
role: Quality gate
|
|
295
|
+
max_steps: 25 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
289
296
|
reasoning_effort: high
|
|
290
297
|
user_invocable: true
|
|
291
298
|
can_delegate: false
|
|
@@ -307,6 +314,7 @@ agents:
|
|
|
307
314
|
- context-compression
|
|
308
315
|
prometheus:
|
|
309
316
|
role: Infrastructure specialist
|
|
317
|
+
max_steps: 30 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
310
318
|
reasoning_effort: medium
|
|
311
319
|
user_invocable: true
|
|
312
320
|
can_delegate: false
|
|
@@ -325,6 +333,7 @@ agents:
|
|
|
325
333
|
- incremental-implementation
|
|
326
334
|
hephaestus:
|
|
327
335
|
role: AI pipelines
|
|
336
|
+
max_steps: 50 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
328
337
|
reasoning_effort: medium
|
|
329
338
|
user_invocable: true
|
|
330
339
|
can_delegate: false
|
|
@@ -343,6 +352,7 @@ agents:
|
|
|
343
352
|
- auto-continue
|
|
344
353
|
nyx:
|
|
345
354
|
role: Observability
|
|
355
|
+
max_steps: 25 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
346
356
|
reasoning_effort: low
|
|
347
357
|
user_invocable: true
|
|
348
358
|
can_delegate: false
|
|
@@ -361,6 +371,7 @@ agents:
|
|
|
361
371
|
- auto-continue
|
|
362
372
|
gaia:
|
|
363
373
|
role: Remote sensing domain specialist
|
|
374
|
+
max_steps: 25 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
364
375
|
reasoning_effort: low
|
|
365
376
|
user_invocable: false
|
|
366
377
|
can_delegate: false
|
|
@@ -375,6 +386,7 @@ agents:
|
|
|
375
386
|
- auto-continue
|
|
376
387
|
iris:
|
|
377
388
|
role: GitHub operations
|
|
389
|
+
max_steps: 20 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
378
390
|
reasoning_effort: low
|
|
379
391
|
user_invocable: true
|
|
380
392
|
can_delegate: false
|
|
@@ -392,6 +404,7 @@ agents:
|
|
|
392
404
|
- artifact-management
|
|
393
405
|
mnemosyne:
|
|
394
406
|
role: Memory bank
|
|
407
|
+
max_steps: 20 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
395
408
|
reasoning_effort: low
|
|
396
409
|
user_invocable: true
|
|
397
410
|
can_delegate: false
|
|
@@ -415,6 +428,7 @@ agents:
|
|
|
415
428
|
- session-goal
|
|
416
429
|
talos:
|
|
417
430
|
role: Hotfix agent
|
|
431
|
+
max_steps: 15 # R4: step budget — at max_steps the agent is forced to summarize-and-stop
|
|
418
432
|
reasoning_effort: low
|
|
419
433
|
user_invocable: true
|
|
420
434
|
can_delegate: false
|
|
@@ -457,6 +471,11 @@ intent_gate:
|
|
|
457
471
|
- search
|
|
458
472
|
- lookup
|
|
459
473
|
- documentation
|
|
474
|
+
- procure
|
|
475
|
+
- busque
|
|
476
|
+
- encontre
|
|
477
|
+
- localize
|
|
478
|
+
- pesquise
|
|
460
479
|
target: apollo
|
|
461
480
|
investigate:
|
|
462
481
|
patterns:
|
|
@@ -568,6 +587,34 @@ background_delegation:
|
|
|
568
587
|
read_only_agents: [apollo, gaia]
|
|
569
588
|
session_max: 3
|
|
570
589
|
retry_count: 1
|
|
590
|
+
# ── R1: Per-error-type retry + provider cooldown (LiteLLM pattern) ──────
|
|
591
|
+
# retry_policy: max retries per error class before escalating. Exponential
|
|
592
|
+
# backoff (base 1s, doubling, capped 30s) applies between retries. auth
|
|
593
|
+
# errors are NEVER retried (0) — a credential problem will not fix itself.
|
|
594
|
+
# Enforcement: zeus-delegate-with-retry (manual waves) + RetryPolicyEngine.
|
|
595
|
+
retry_policy:
|
|
596
|
+
auth: 0
|
|
597
|
+
rate_limit: 3
|
|
598
|
+
timeout: 2
|
|
599
|
+
other: 1
|
|
600
|
+
# cooldown: after `allowed_fails` consecutive failures for a provider, skip
|
|
601
|
+
# that provider for `cooldown_time_seconds`. A success resets the counter.
|
|
602
|
+
# In-memory tracker (no Redis) — process-wide, shared across retry helpers.
|
|
603
|
+
cooldown:
|
|
604
|
+
allowed_fails: 3
|
|
605
|
+
cooldown_time_seconds: 60
|
|
606
|
+
# ── O5: permission.task — glob rules for which subagents an agent may
|
|
607
|
+
# invoke (last matching rule wins; deny removes the agent from the delegate
|
|
608
|
+
# tool description entirely — prevents circular delegation at the
|
|
609
|
+
# permission layer). Default "*": allow keeps the existing runtime matrix
|
|
610
|
+
# (zeus → anyone; athena/hermes → apollo only). Example deny-first config:
|
|
611
|
+
# permission:
|
|
612
|
+
# task:
|
|
613
|
+
# "*": deny
|
|
614
|
+
# "orchestrator-*": allow
|
|
615
|
+
permission:
|
|
616
|
+
task:
|
|
617
|
+
"*": allow
|
|
571
618
|
todo_enforcer:
|
|
572
619
|
enabled: true
|
|
573
620
|
cooldown_base_ms: 5000 # base backoff between injections
|
package/src/agents/README.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Agent Reference — Pantheon
|
|
2
|
-
|
|
3
|
-
This directory contains the definitions for all 14 Pantheon agents. Each agent is defined in a `.agent.md` file with YAML frontmatter specifying tools, model tier, capabilities, and delegation rules.
|
|
4
|
-
|
|
5
|
-
## Agents
|
|
6
|
-
|
|
7
|
-
| Agent | File | Role |
|
|
8
|
-
|-------|------|------|
|
|
9
|
-
| @zeus | [zeus.agent.md](zeus.agent.md) | Central orchestrator |
|
|
10
|
-
| @athena | [athena.agent.md](athena.agent.md) | Strategic planner |
|
|
11
|
-
| @apollo | [apollo.agent.md](apollo.agent.md) | Investigation scout |
|
|
12
|
-
| @hermes | [hermes.agent.md](hermes.agent.md) | Backend specialist |
|
|
13
|
-
| @aphrodite | [aphrodite.agent.md](aphrodite.agent.md) | Frontend specialist |
|
|
14
|
-
| @demeter | [demeter.agent.md](demeter.agent.md) | Database specialist |
|
|
15
|
-
| @themis | [themis.agent.md](themis.agent.md) | Quality & security gate |
|
|
16
|
-
| @prometheus | [prometheus.agent.md](prometheus.agent.md) | Infrastructure specialist |
|
|
17
|
-
| @hephaestus | [hephaestus.agent.md](hephaestus.agent.md) | AI pipelines |
|
|
18
|
-
| @nyx | [nyx.agent.md](nyx.agent.md) | Observability |
|
|
19
|
-
| @gaia | [gaia.agent.md](gaia.agent.md) | Remote sensing |
|
|
20
|
-
| @iris | [iris.agent.md](iris.agent.md) | GitHub operations |
|
|
21
|
-
| @mnemosyne | [mnemosyne.agent.md](mnemosyne.agent.md) | Memory bank |
|
|
22
|
-
| @talos | [talos.agent.md](talos.agent.md) | Hotfix agent |
|
|
23
|
-
|
|
24
|
-
## Full Reference
|
|
25
|
-
|
|
26
|
-
See [AGENTS.md](../../AGENTS.md) for the complete agent reference with roles, capabilities, and invocation patterns.
|