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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -18,6 +18,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
18
18
 
19
19
  ## ✅ Closed Issues
20
20
 
21
+ ## [v1.5.0-beta.11] - 2026-09-14
22
+
23
+ <!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
24
+ move this section to a versioned entry and reset the template below. -->
25
+
26
+ ## 🆕 What's New
27
+
28
+ - **Glyphs da FSM real no painel Delegations:** cada estado do BackgroundJobBoard
29
+ tem agora um glifo Unicode geométrico próprio — `⠋` running (spinner 1s),
30
+ `⟳` retry, `⚠` stale/startup, `✓` completed, `✕` error, `−` cancelled. Forma e
31
+ cor são canais independentes: as linhas seguem legíveis sem cor e sem Nerd Font.
32
+ - **Prefixo `nat:` / `pan:` documentado:** `docs/INSTALLATION.md` passa a
33
+ descrever os prefixos curtos das linhas nativas (`nat:<agent>`) e do board
34
+ (`pan:<alias>`) e o par de glyphs ◇/◆.
35
+ - **`scripts/dev-tui.sh` (dev local sem publish):** instala, builda e registra o
36
+ caminho absoluto do repo em `.opencode/tui.json` (gitignored) com hint de
37
+ restart — sem tocar `opencode.json` e sem publicar.
38
+ - **104 testes TUI delegations:** `tests/pantheon/tui-delegations.test.ts` com
39
+ 104/104 passando, cobrindo glifos/marker/tone dos estados reais da FSM.
40
+
41
+ ## 🐞 Fixed
42
+
43
+ - **Doctor F.2 (`hasUsableScripts`):** a resolução do diretório code-mode agora
44
+ exige um `.py`/`.sh` regular (paridade com `_has_usable_scripts` do MCP), então
45
+ um overlay de projeto vazio não mascara mais uma instalação que traz scripts.
46
+ - **`publish:next` órfão removido:** nenhum workflow usava o script (o release
47
+ publica via `--tag beta`); removido do `package.json` para eliminar o dist-tag
48
+ `next` obsoleto da linha de publicação.
21
49
  ## [v1.5.0-beta.10] - 2026-09-14
22
50
 
23
51
  &lt;!-- Add new changes here. Running `node scripts/versioning.mjs apply` will
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pantheon-opencode",
3
- "version": "1.5.0-beta.10",
3
+ "version": "1.5.0-beta.11",
4
4
  "description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -31,7 +31,6 @@
31
31
  "release:dry-run": "node scripts/versioning.mjs status && node scripts/release-notes.mjs && node scripts/package-evidence.mjs",
32
32
  "package:evidence": "node scripts/package-evidence.mjs",
33
33
  "package:validate": "node scripts/validate-package.mjs",
34
- "publish:next": "npm publish --tag next --access public",
35
34
  "lint": "biome ci .",
36
35
  "typecheck": "tsc --noEmit",
37
36
  "audit": "npm audit --audit-level=high",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon",
3
3
  "publisher": "ils15",
4
- "version": "1.5.0-beta.10",
4
+ "version": "1.5.0-beta.11",
5
5
  "displayName": "Pantheon",
6
6
  "description": "Conductor-Delegate orchestration platform with 14 specialized agents, 21 skills, and OpenCode support.",
7
7
  "agents": "./src/agents",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
4
4
 
5
5
  [project]
6
6
  name = "pantheon"
7
- version = "1.5.0-beta.10"
7
+ version = "1.5.0-beta.11"
8
8
  description = "Multi-agent orchestration framework"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = []
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ # dev-tui.sh — load the Pantheon TUI from the repo (src/plugins/tui) WITHOUT publishing.
3
+ #
4
+ # Flow: build the bundle → register the repo's absolute path in
5
+ # .opencode/tui.json (read by the OpenCode TUI loader) → restart hint.
6
+ # It deliberately does NOT touch opencode.json: that file is packaged and
7
+ # merged into user configs by the installer, so a repo-relative path would
8
+ # leak into third-party installs and break the CI package gates.
9
+ #
10
+ # Usage: scripts/dev-tui.sh [--test]
11
+ # --test also run the full repo test suite (npm run test:all) after build.
12
+ set -euo pipefail
13
+
14
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
15
+ TUI_DIR="$ROOT/src/plugins/tui"
16
+ TUI_JSON="$ROOT/.opencode/tui.json"
17
+
18
+ command -v npm >/dev/null 2>&1 || { echo "error: npm not found in PATH" >&2; exit 1; }
19
+ [ -d "$TUI_DIR" ] || { echo "error: TUI source not found: $TUI_DIR" >&2; exit 1; }
20
+
21
+ echo "==> TUI dev source: $TUI_DIR"
22
+
23
+ # 1. Install pinned deps + build dist/tui.js (dist/tui.tsx is copied by build).
24
+ echo "==> Installing TUI dev dependencies (npm ci)"
25
+ npm ci --prefix "$TUI_DIR" --ignore-scripts --no-audit --no-fund
26
+
27
+ echo "==> Building TUI plugin"
28
+ npm run build --prefix "$TUI_DIR"
29
+
30
+ # 2. Optional: full suite to match the CI gate before opening a PR.
31
+ if [[ "${1:-}" == "--test" ]]; then
32
+ echo "==> Running full test suite (npm run test:all)"
33
+ npm run test:all
34
+ fi
35
+
36
+ # 3. Register the repo path in the project tui.json (absolute, gitignored).
37
+ # Reuses the installer's registerPlugin so the array is merged, not clobbered.
38
+ echo "==> Registering repo TUI path in $TUI_JSON"
39
+ ROOT="$ROOT" TUI_JSON="$TUI_JSON" TUI_DIR="$TUI_DIR" node --input-type=module <<'NODE'
40
+ const { registerPlugin } = await import(`${process.env.ROOT}/scripts/install/plugin.mjs`)
41
+ const result = registerPlugin(process.env.TUI_JSON, process.env.TUI_DIR)
42
+ console.log(`${result}: ${process.env.TUI_DIR}`)
43
+ NODE
44
+
45
+ # 4. Restart hint + dev-mode caveat.
46
+ echo "==> Done. Restart OpenCode to load the rebuilt TUI plugin."
47
+ echo " Dev mode caveat: do NOT run 'init' in this repo while iterating — it"
48
+ echo " removes repo path refs as stale. If you do, re-run scripts/dev-tui.sh."
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { spawn as spawnAsync, spawnSync } from 'node:child_process'
25
25
  import { createHash } from 'node:crypto'
26
- import { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from 'node:fs'
26
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, statSync } from 'node:fs'
27
27
  import { homedir } from 'node:os'
28
28
  import { basename, dirname, join, resolve } from 'node:path'
29
29
  import { fileURLToPath } from 'node:url'
@@ -959,6 +959,35 @@ function checkVenvLayer(args) {
959
959
  // Check F.2: Code-Mode Scripts Directory
960
960
  // ---------------------------------------------------------------------------
961
961
 
962
+ /**
963
+ * Return whether *dir* contains an executable code-mode target.
964
+ *
965
+ * Mirrors `_has_usable_scripts` in `scripts/code_mode_server.py`: a directory
966
+ * is only a valid resolver candidate when it is a real (non-symlink) directory
967
+ * containing a regular `.py`/`.sh` direct child. This prevents an empty project
968
+ * overlay from masking a lower-priority installation that actually ships
969
+ * scripts. Keep both implementations in sync (see tests/test_doctor_layers.mjs).
970
+ */
971
+ function hasUsableScripts(dir) {
972
+ try {
973
+ if (!existsSync(dir)) return false
974
+ const stat = lstatSync(dir)
975
+ if (stat.isSymbolicLink() || !stat.isDirectory()) return false
976
+ return readdirSync(dir).some((name) => {
977
+ if (name.startsWith('.')) return false
978
+ const lower = name.toLowerCase()
979
+ if (!lower.endsWith('.py') && !lower.endsWith('.sh')) return false
980
+ try {
981
+ return statSync(join(dir, name)).isFile()
982
+ } catch {
983
+ return false
984
+ }
985
+ })
986
+ } catch {
987
+ return false
988
+ }
989
+ }
990
+
962
991
  /**
963
992
  * Verify the code-mode scripts directory exists for the resolved runtime
964
993
  * root (project → <target>/.opencode, otherwise the global config dir).
@@ -977,7 +1006,7 @@ export function checkCodeModeDir(args) {
977
1006
  // install (.opencode present) gets its runtime dir created when missing.
978
1007
  // Only targets with no project layout fall back to the global config dir.
979
1008
  const codeModeDir =
980
- projectCandidates.find((candidate) => existsSync(candidate)) ??
1009
+ projectCandidates.find(hasUsableScripts) ??
981
1010
  (existsSync(join(target, '.opencode'))
982
1011
  ? projectCandidates[0]
983
1012
  : join(resolveOpenCodeConfigDir(env), '.pantheon', 'code-mode'))
@@ -1003,7 +1032,7 @@ export function resolveCodeModeDir(args) {
1003
1032
  join(target, '.opencode', '.pantheon', 'code-mode'),
1004
1033
  join(target, '.pantheon', 'code-mode'),
1005
1034
  ]
1006
- const projectDir = projectCandidates.find((candidate) => existsSync(candidate))
1035
+ const projectDir = projectCandidates.find(hasUsableScripts)
1007
1036
  if (projectDir) return projectDir
1008
1037
  return join(resolveOpenCodeConfigDir(env), '.pantheon', 'code-mode')
1009
1038
  }
@@ -116,6 +116,27 @@ declare function delegationActivityLabel(entry: DelegationEntry): string;
116
116
  /** Return a deterministic spinner frame. The View ticks this every 1000ms
117
117
  * (not 140ms — the fast tick flickered without adding information). */
118
118
  declare function delegationSpinnerFrame(now: number): string;
119
+ /** Every state the row knows how to draw: the real BackgroundJobBoard FSM
120
+ * (src/pantheon/background-job-board.ts) plus the TUI display-only states
121
+ * (`retry`, `stale-running`). Fase 1 deliberately omits speculative
122
+ * blocked/paused/scheduled/skipped. There is no `pending` display state: a
123
+ * pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
124
+ type DelegationDisplayState = DelegationEntry['state'];
125
+ /** Static glyph per display state. `running` shows its base spinner frame;
126
+ * callers that animate must prefer {@link delegationStateMarker}. Unicode
127
+ * geometric shapes only (no Nerd Font) — shape is an independent channel
128
+ * from color, so rows stay legible without color. */
129
+ declare const DELEGATION_STATE_GLYPHS: Readonly<Record<DelegationDisplayState, string>>;
130
+ declare function delegationStateGlyph(state: DelegationDisplayState): string;
131
+ /** Row marker (`<glyph> `) — the state channel. `running` animates through
132
+ * {@link delegationSpinnerFrame} for the given tick; every other state is
133
+ * static. Pure. */
134
+ declare function delegationStateMarker(state: DelegationDisplayState, now?: number): string;
135
+ /** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
136
+ * Kept separate + pure so the color channel is testable without booting the
137
+ * renderer. */
138
+ type DelegationStateTone = 'warning' | 'error' | 'success' | 'muted';
139
+ declare function delegationStateTone(state: DelegationDisplayState): DelegationStateTone;
119
140
  type ToolActivity = {
120
141
  tool: string;
121
142
  summary: string;
@@ -413,5 +434,5 @@ declare const plugin: TuiPluginModule & {
413
434
  setup: () => Promise<void>;
414
435
  };
415
436
  //#endregion
416
- export { ChildDelegationLike, DELEGATION_DESCRIPTION_MAX, DELEGATION_ELAPSED_WIDTH, DelegationActivity, DelegationEntry, DelegationToolPart, IDLE_SILENCE_MS, LiveDelegationEntry, LiveDelegationStore, ParsedDelegationToolPart, STALE_RUNNING_THRESHOLD_MS, ToolActivity, TuiSessionSources, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationIcon, delegationSpinnerFrame, delegationTag, extractToolActivity, fmtElapsed, formatDelegationElapsed, formatDelegationHeader, formatDelegationIdentity, formatDelegationRow, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
437
+ export { ChildDelegationLike, DELEGATION_DESCRIPTION_MAX, DELEGATION_ELAPSED_WIDTH, DELEGATION_STATE_GLYPHS, DelegationActivity, DelegationDisplayState, DelegationEntry, DelegationStateTone, DelegationToolPart, IDLE_SILENCE_MS, LiveDelegationEntry, LiveDelegationStore, ParsedDelegationToolPart, STALE_RUNNING_THRESHOLD_MS, ToolActivity, TuiSessionSources, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationIcon, delegationSpinnerFrame, delegationStateGlyph, delegationStateMarker, delegationStateTone, delegationTag, extractToolActivity, fmtElapsed, formatDelegationElapsed, formatDelegationHeader, formatDelegationIdentity, formatDelegationRow, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
417
438
  //# sourceMappingURL=tui.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;KAu7BY;;EAEV;;EAEA;;;;EAIA;;EAEA;EACA;;EAUA;;EAEA;EACA;EACA;;EAEA;;;;EAQA;;;;;;;;;;;iBAsBc,wBACd,aACA,oBACA,qBACC;;;;;;iBAkHmB,sBAAsB,cAAc,QAAQ;;;;;;;;iBAwC5C,yBAAyB,eAAe,QAAQ;;;;;;;;iBAWtD,sBACd;EAAS;EAAoB;eAC7B;;;;;;iBAYc,YAAY;;;iBAMZ,WAAW;;;;iBAOX,yBAAyB,GAAG,iBAAiB,GAAG;;;;;iBAahD,oBACd,cAAc,mBACd,oBACA,cACA;EACG,QAAQ;EAAmB,QAAQ;EAAmB,UAAU;;;;;iBAqBrD,sBACd,cAAc,mBACd,sBACA,cACA,4BACC;;cAMU;;;;cAKA;;;;;;;;;iBAUG,mBACd,OAAO,iBACP,aACA,uBACC;;;iBAWa,WAAW;;;;iBAeX,kBAAkB,OAAO,iBAAiB;;;KAQ9C;iBAQI,mBAAmB,OAAO,kBAAkB;iBAc5C,wBAAwB,OAAO;;;iBAsB/B,uBAAuB;KAY3B;EACV;EACA;EACA;;;;;iBA0Bc,oBACd;EACE;EACA;EACA;EACA;IAAU;IAAiB,QAAQ;;GAErC;EACG;EAAmB,UAAU;;;iBAYlB,kBACd,KAAK,YAAY,eACjB,mBACA,UAAU;;iBAWI,sBACd,KAAK,YAAY,eACjB,+BACA,cACA,iBACC;;;;;;;iBAca,4BACd,mBAAmB,mBACnB,eAAe,wBACd;;KAqFS;EACV;EACA;EACA;EACA;EACA;EACA;IACE;IACA,QAAQ;IACR;IACA;IACA;MAAS;MAAgB;;;;;KAKjB;;EAEV;;EAEA;;EAEA;EACA;;EAEA;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;;;KAIU;EACV;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;iBAec,wBACd,MAAM,oBACN,eACC;;;iBAgGa,yBACd,KAAK,YAAY,sBACjB,MAAM,oBACN;;;iBAiGc,sBACd,KAAK,YAAY,sBACjB;;;;;;;;;iBAoBc,2BACd;EAAqB;EAAa;iBAClC,YAAY,uDACX;;;;;iBA6Ba,sBACd,KAAK,YAAY,sBACjB,gBAAgB,sBAChB;;;iBAWc,kBAAkB,MAAM,sBAAsB;;;;;;iBAqB9C,uBACd,eAAe,uBACf,aAAa,oBACZ;;;;;;;;;iBA8Da,iBAAiB,cAAc;;;;KAOnC;;EAEV;EACA;;IAEE;MAAU;;;IAEV;MAAU;QAAY;QAAe,SAAS;;;;;;;;;;iBASlC,wBAAwB,SAAS;;;;;;;;iBAmBjC,gBAAgB;EAAgB;IAAQ;;;;;;;iBASxC,kBAAkB;EAAkC;IAAQ;;;;KAKhE;;EAEV;;EAEA;;;EAGA;;;EAGA;EACA;IACE;IACA;;;;;;;iBAQY,mBAAmB;;;;;;;;;;;;;iBAkBnB,cAAc,OAAO;;;;;iBAQrB,eAAe,OAAO;;;;iBAOtB,yBAAyB,OAAO;;;cAQnC;;;;iBAoBG,8BACd,cACA;;;;cAUW;iBAEG,wBACd,OAAO,iBACP,aACA;;;iBASc,oBAAoB,OAAO,iBAAiB;;;;iBAY5C,uBAAuB,kBAAkB;;;;iBAazC,uBAAuB,kBAAkB;EACvD;EACA;EACA;;;;;iBAUc,mBACd,kBACA,kBACA,gBACA,YACA;;;;;;;;;;;;;;;;;;iBAsBc,4BACd,mBAAmB,mCACnB,aAAa,mBACb,eACC;;;;;;iBA4Ca,4BACd;EAAS,YAAY,cAAc,SAAS;eAC5C;;;;;KA4MU;EACV,KAAK,YAAY;;EAEjB;;EAEA;;cAsgBI,QAAQ;EAAoB;EAAY,aAAa"}
1
+ {"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":"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"}
@@ -164,7 +164,7 @@ async function detectVersion(api) {
164
164
  if (ver) return ver;
165
165
  }
166
166
  } catch {}
167
- return "1.5.0-beta.10";
167
+ return "1.5.0-beta.11";
168
168
  }
169
169
  /**
170
170
  * usage-bar — AI subscription usage gauge for the opencode TUI.
@@ -977,6 +977,49 @@ function delegationSpinnerFrame(now) {
977
977
  const index = Math.floor(Math.max(0, now) / 1e3) % DELEGATION_SPINNER_FRAMES.length;
978
978
  return DELEGATION_SPINNER_FRAMES[index] ?? DELEGATION_SPINNER_FRAMES[0];
979
979
  }
980
+ /** Every state the row knows how to draw: the real BackgroundJobBoard FSM
981
+ * (src/pantheon/background-job-board.ts) plus the TUI display-only states
982
+ * (`retry`, `stale-running`). Fase 1 deliberately omits speculative
983
+ * blocked/paused/scheduled/skipped. There is no `pending` display state: a
984
+ * pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
985
+ /** Static glyph per display state. `running` shows its base spinner frame;
986
+ * callers that animate must prefer {@link delegationStateMarker}. Unicode
987
+ * geometric shapes only (no Nerd Font) — shape is an independent channel
988
+ * from color, so rows stay legible without color. */
989
+ const DELEGATION_STATE_GLYPHS = {
990
+ running: "⠋",
991
+ retry: "⟳",
992
+ "stale-running": "⚠",
993
+ completed: "✓",
994
+ error: "✕",
995
+ cancelled: "−",
996
+ startup_failed: "⚠",
997
+ startup_unknown: "⚠"
998
+ };
999
+ function delegationStateGlyph(state) {
1000
+ return DELEGATION_STATE_GLYPHS[state];
1001
+ }
1002
+ /** Row marker (`<glyph> `) — the state channel. `running` animates through
1003
+ * {@link delegationSpinnerFrame} for the given tick; every other state is
1004
+ * static. Pure. */
1005
+ function delegationStateMarker(state, now = Date.now()) {
1006
+ return `${state === "running" ? delegationSpinnerFrame(now) : delegationStateGlyph(state)} `;
1007
+ }
1008
+ /** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
1009
+ * Kept separate + pure so the color channel is testable without booting the
1010
+ * renderer. */
1011
+ function delegationStateTone(state) {
1012
+ switch (state) {
1013
+ case "running":
1014
+ case "retry":
1015
+ case "startup_unknown": return "warning";
1016
+ case "stale-running":
1017
+ case "error":
1018
+ case "startup_failed": return "error";
1019
+ case "completed": return "success";
1020
+ case "cancelled": return "muted";
1021
+ }
1022
+ }
980
1023
  /** Bounded tracker: child session id → latest running tool call. */
981
1024
  const latestToolActivity = /* @__PURE__ */ new Map();
982
1025
  const MAX_TOOL_ACTIVITY_ENTRIES = 200;
@@ -1603,25 +1646,14 @@ function DelegationRow(props) {
1603
1646
  const theme = () => props.api.theme.current;
1604
1647
  const color = createMemo(() => {
1605
1648
  const t = theme();
1606
- switch (props.job.state) {
1607
- case "running": return t.warning;
1608
- case "retry": return t.warning;
1609
- case "stale-running": return t.error;
1610
- case "completed": return t.success;
1649
+ switch (delegationStateTone(props.job.state)) {
1650
+ case "warning": return t.warning;
1611
1651
  case "error": return t.error;
1652
+ case "success": return t.success;
1612
1653
  default: return t.textMuted;
1613
1654
  }
1614
1655
  });
1615
- const marker = createMemo(() => {
1616
- if (props.job.state === "running") return `${delegationSpinnerFrame(props.animationNow)} `;
1617
- if (props.job.state === "retry") return "⟳ ";
1618
- if (props.job.state === "stale-running") return "⚠ ";
1619
- switch (props.job.state) {
1620
- case "completed": return "✓ ";
1621
- case "error": return "✕ ";
1622
- default: return "○ ";
1623
- }
1624
- });
1656
+ const marker = createMemo(() => delegationStateMarker(props.job.state, props.animationNow));
1625
1657
  const tagColor = createMemo(() => props.job.source === "children-only" ? theme().info : color());
1626
1658
  const description = createMemo(() => truncateDelegationDescription(props.job.description));
1627
1659
  const elapsed = createMemo(() => formatDelegationElapsed(props.job, props.now));
@@ -2141,6 +2173,6 @@ const plugin = {
2141
2173
  setup: async () => {}
2142
2174
  };
2143
2175
  //#endregion
2144
- export { DELEGATION_DESCRIPTION_MAX, DELEGATION_ELAPSED_WIDTH, IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationIcon, delegationSpinnerFrame, delegationTag, extractToolActivity, fmtElapsed, formatDelegationElapsed, formatDelegationHeader, formatDelegationIdentity, formatDelegationRow, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
2176
+ export { DELEGATION_DESCRIPTION_MAX, DELEGATION_ELAPSED_WIDTH, DELEGATION_STATE_GLYPHS, IDLE_SILENCE_MS, STALE_RUNNING_THRESHOLD_MS, buildChildrenPath, childStatusToState, childrenToDelegationEntries, collectDelegationToolParts, compareDelegationEntries, countDelegationSources, plugin as default, delegationActivity, delegationActivityLabel, delegationElapsed, delegationIcon, delegationSpinnerFrame, delegationStateGlyph, delegationStateMarker, delegationStateTone, delegationTag, extractToolActivity, fmtElapsed, formatDelegationElapsed, formatDelegationHeader, formatDelegationIdentity, formatDelegationRow, formatPanelLogLine, isValidSessionId, latestToolActivityFor, markStaleIfRunning, mergeChildDelegationSources, mergeDelegationSources, navigateToDelegationSession, panelLogDir, parseDelegationMarkdown, parseDelegationToolPart, readAllDelegationEntries, readDelegationEntries, reduceDelegationToolPart, removeDelegationEntry, resolveCurrentSessionID, resolveDelegationsDir, safeSessionPath, seedLiveDelegationMap, splitDelegationList, toDelegationEntry, trackToolActivity, truncateDelegationDescription, tuiLogPath, visibleDelegationList };
2145
2177
 
2146
2178
  //# sourceMappingURL=tui.js.map
@@ -1354,6 +1354,62 @@ export function delegationSpinnerFrame(now: number): string {
1354
1354
  return DELEGATION_SPINNER_FRAMES[index] ?? DELEGATION_SPINNER_FRAMES[0]
1355
1355
  }
1356
1356
 
1357
+ /** Every state the row knows how to draw: the real BackgroundJobBoard FSM
1358
+ * (src/pantheon/background-job-board.ts) plus the TUI display-only states
1359
+ * (`retry`, `stale-running`). Fase 1 deliberately omits speculative
1360
+ * blocked/paused/scheduled/skipped. There is no `pending` display state: a
1361
+ * pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
1362
+ export type DelegationDisplayState = DelegationEntry['state']
1363
+
1364
+ /** Static glyph per display state. `running` shows its base spinner frame;
1365
+ * callers that animate must prefer {@link delegationStateMarker}. Unicode
1366
+ * geometric shapes only (no Nerd Font) — shape is an independent channel
1367
+ * from color, so rows stay legible without color. */
1368
+ export const DELEGATION_STATE_GLYPHS: Readonly<Record<DelegationDisplayState, string>> = {
1369
+ running: '\u280b', // ⠋ base frame of the animated spinner
1370
+ retry: '\u27f3', // ⟳ retrying
1371
+ 'stale-running': '\u26a0', // ⚠ stale (display-only alert)
1372
+ completed: '\u2713', // ✓
1373
+ error: '\u2715', // ✕
1374
+ cancelled: '\u2212', // − minus: stopped, distinct from error's ✕
1375
+ startup_failed: '\u26a0', // ⚠ boot failed
1376
+ startup_unknown: '\u26a0', // ⚠ boot outcome unknown
1377
+ }
1378
+
1379
+ export function delegationStateGlyph(state: DelegationDisplayState): string {
1380
+ return DELEGATION_STATE_GLYPHS[state]
1381
+ }
1382
+
1383
+ /** Row marker (`<glyph> `) — the state channel. `running` animates through
1384
+ * {@link delegationSpinnerFrame} for the given tick; every other state is
1385
+ * static. Pure. */
1386
+ export function delegationStateMarker(state: DelegationDisplayState, now = Date.now()): string {
1387
+ const glyph = state === 'running' ? delegationSpinnerFrame(now) : delegationStateGlyph(state)
1388
+ return `${glyph} `
1389
+ }
1390
+
1391
+ /** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
1392
+ * Kept separate + pure so the color channel is testable without booting the
1393
+ * renderer. */
1394
+ export type DelegationStateTone = 'warning' | 'error' | 'success' | 'muted'
1395
+
1396
+ export function delegationStateTone(state: DelegationDisplayState): DelegationStateTone {
1397
+ switch (state) {
1398
+ case 'running':
1399
+ case 'retry':
1400
+ case 'startup_unknown':
1401
+ return 'warning'
1402
+ case 'stale-running':
1403
+ case 'error':
1404
+ case 'startup_failed':
1405
+ return 'error'
1406
+ case 'completed':
1407
+ return 'success'
1408
+ case 'cancelled':
1409
+ return 'muted'
1410
+ }
1411
+ }
1412
+
1357
1413
  /* ─── Live tool activity per child session (beta.6) ────────────────────────
1358
1414
  * The delegation panel's activity line ("↳ bash npm test") — what the agent
1359
1415
  * is doing RIGHT NOW. Sourced from message.part.updated tool parts: every
@@ -2351,35 +2407,19 @@ function DelegationRow(props: {
2351
2407
 
2352
2408
  const color = createMemo(() => {
2353
2409
  const t = theme()
2354
- switch (props.job.state) {
2355
- case 'running':
2356
- return t.warning
2357
- case 'retry':
2410
+ switch (delegationStateTone(props.job.state)) {
2411
+ case 'warning':
2358
2412
  return t.warning
2359
- case 'stale-running':
2360
- return t.error // warning color for stale delegations
2361
- case 'completed':
2362
- return t.success
2363
2413
  case 'error':
2364
2414
  return t.error
2415
+ case 'success':
2416
+ return t.success
2365
2417
  default:
2366
- return t.textMuted // cancelled
2418
+ return t.textMuted // muted (cancelled)
2367
2419
  }
2368
2420
  })
2369
2421
 
2370
- const marker = createMemo(() => {
2371
- if (props.job.state === 'running') return `${delegationSpinnerFrame(props.animationNow)} `
2372
- if (props.job.state === 'retry') return '\u27f3 ' // ⟳ retrying
2373
- if (props.job.state === 'stale-running') return '\u26a0 ' // warning triangle
2374
- switch (props.job.state) {
2375
- case 'completed':
2376
- return '\u2713 '
2377
- case 'error':
2378
- return '\u2715 '
2379
- default:
2380
- return '\u25cb '
2381
- }
2382
- })
2422
+ const marker = createMemo(() => delegationStateMarker(props.job.state, props.animationNow))
2383
2423
 
2384
2424
  // Two visual channels split native task() from pantheon_delegate: the tag
2385
2425
  // prefix (`nat:`/`pan:`, via delegationTag) and the glyph color — info
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
- "version": "1.5.0-beta.10",
3
+ "version": "1.5.0-beta.11",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pantheon-tui",
9
- "version": "1.5.0-beta.10",
9
+ "version": "1.5.0-beta.11",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@opencode-ai/plugin": ">=1.14.0",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon-tui",
3
3
  "type": "module",
4
- "version": "1.5.0-beta.10",
4
+ "version": "1.5.0-beta.11",
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": {
@@ -1354,6 +1354,62 @@ export function delegationSpinnerFrame(now: number): string {
1354
1354
  return DELEGATION_SPINNER_FRAMES[index] ?? DELEGATION_SPINNER_FRAMES[0]
1355
1355
  }
1356
1356
 
1357
+ /** Every state the row knows how to draw: the real BackgroundJobBoard FSM
1358
+ * (src/pantheon/background-job-board.ts) plus the TUI display-only states
1359
+ * (`retry`, `stale-running`). Fase 1 deliberately omits speculative
1360
+ * blocked/paused/scheduled/skipped. There is no `pending` display state: a
1361
+ * pre-dispatch tool part maps to `running` in {@link reduceDelegationToolPart}. */
1362
+ export type DelegationDisplayState = DelegationEntry['state']
1363
+
1364
+ /** Static glyph per display state. `running` shows its base spinner frame;
1365
+ * callers that animate must prefer {@link delegationStateMarker}. Unicode
1366
+ * geometric shapes only (no Nerd Font) — shape is an independent channel
1367
+ * from color, so rows stay legible without color. */
1368
+ export const DELEGATION_STATE_GLYPHS: Readonly<Record<DelegationDisplayState, string>> = {
1369
+ running: '\u280b', // ⠋ base frame of the animated spinner
1370
+ retry: '\u27f3', // ⟳ retrying
1371
+ 'stale-running': '\u26a0', // ⚠ stale (display-only alert)
1372
+ completed: '\u2713', // ✓
1373
+ error: '\u2715', // ✕
1374
+ cancelled: '\u2212', // − minus: stopped, distinct from error's ✕
1375
+ startup_failed: '\u26a0', // ⚠ boot failed
1376
+ startup_unknown: '\u26a0', // ⚠ boot outcome unknown
1377
+ }
1378
+
1379
+ export function delegationStateGlyph(state: DelegationDisplayState): string {
1380
+ return DELEGATION_STATE_GLYPHS[state]
1381
+ }
1382
+
1383
+ /** Row marker (`<glyph> `) — the state channel. `running` animates through
1384
+ * {@link delegationSpinnerFrame} for the given tick; every other state is
1385
+ * static. Pure. */
1386
+ export function delegationStateMarker(state: DelegationDisplayState, now = Date.now()): string {
1387
+ const glyph = state === 'running' ? delegationSpinnerFrame(now) : delegationStateGlyph(state)
1388
+ return `${glyph} `
1389
+ }
1390
+
1391
+ /** Semantic tone mapped to the TUI theme at the row ({@link DelegationRow}).
1392
+ * Kept separate + pure so the color channel is testable without booting the
1393
+ * renderer. */
1394
+ export type DelegationStateTone = 'warning' | 'error' | 'success' | 'muted'
1395
+
1396
+ export function delegationStateTone(state: DelegationDisplayState): DelegationStateTone {
1397
+ switch (state) {
1398
+ case 'running':
1399
+ case 'retry':
1400
+ case 'startup_unknown':
1401
+ return 'warning'
1402
+ case 'stale-running':
1403
+ case 'error':
1404
+ case 'startup_failed':
1405
+ return 'error'
1406
+ case 'completed':
1407
+ return 'success'
1408
+ case 'cancelled':
1409
+ return 'muted'
1410
+ }
1411
+ }
1412
+
1357
1413
  /* ─── Live tool activity per child session (beta.6) ────────────────────────
1358
1414
  * The delegation panel's activity line ("↳ bash npm test") — what the agent
1359
1415
  * is doing RIGHT NOW. Sourced from message.part.updated tool parts: every
@@ -2351,35 +2407,19 @@ function DelegationRow(props: {
2351
2407
 
2352
2408
  const color = createMemo(() => {
2353
2409
  const t = theme()
2354
- switch (props.job.state) {
2355
- case 'running':
2356
- return t.warning
2357
- case 'retry':
2410
+ switch (delegationStateTone(props.job.state)) {
2411
+ case 'warning':
2358
2412
  return t.warning
2359
- case 'stale-running':
2360
- return t.error // warning color for stale delegations
2361
- case 'completed':
2362
- return t.success
2363
2413
  case 'error':
2364
2414
  return t.error
2415
+ case 'success':
2416
+ return t.success
2365
2417
  default:
2366
- return t.textMuted // cancelled
2418
+ return t.textMuted // muted (cancelled)
2367
2419
  }
2368
2420
  })
2369
2421
 
2370
- const marker = createMemo(() => {
2371
- if (props.job.state === 'running') return `${delegationSpinnerFrame(props.animationNow)} `
2372
- if (props.job.state === 'retry') return '\u27f3 ' // ⟳ retrying
2373
- if (props.job.state === 'stale-running') return '\u26a0 ' // warning triangle
2374
- switch (props.job.state) {
2375
- case 'completed':
2376
- return '\u2713 '
2377
- case 'error':
2378
- return '\u2715 '
2379
- default:
2380
- return '\u25cb '
2381
- }
2382
- })
2422
+ const marker = createMemo(() => delegationStateMarker(props.job.state, props.animationNow))
2383
2423
 
2384
2424
  // Two visual channels split native task() from pantheon_delegate: the tag
2385
2425
  // prefix (`nat:`/`pan:`, via delegationTag) and the glyph color — info