pantheon-opencode 1.0.1 → 1.0.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.
Files changed (72) hide show
  1. package/README.md +61 -109
  2. package/bin/pantheon-init.mjs +57 -136
  3. package/commands/pantheon-audit.md +1 -1
  4. package/commands/pantheon-deepwork.md +32 -81
  5. package/commands/pantheon-reflect.md +16 -0
  6. package/commands/pantheon-status.md +1 -1
  7. package/commands/pantheon-verify.md +17 -0
  8. package/opencode.json +25 -0
  9. package/package.json +20 -9
  10. package/scripts/ci-validate-yaml.py +19 -0
  11. package/scripts/doctor.mjs +10 -65
  12. package/scripts/install/opencode.mjs +69 -6
  13. package/scripts/install/shared.mjs +39 -13
  14. package/scripts/manifest.mjs +5 -30
  15. package/scripts/postinstall.mjs +47 -0
  16. package/scripts/release-bundle.mjs +2 -2
  17. package/scripts/uninstall.mjs +5 -5
  18. package/scripts/validate-routing.mjs +19 -16
  19. package/scripts/versioning.mjs +12 -1
  20. package/skills-lock.json +9 -2
  21. package/src/agents/aphrodite.md +15 -57
  22. package/src/agents/apollo.md +9 -48
  23. package/src/agents/athena.md +17 -54
  24. package/src/agents/demeter.md +10 -47
  25. package/src/agents/gaia.md +7 -40
  26. package/src/agents/hephaestus.md +9 -52
  27. package/src/agents/hermes.md +17 -52
  28. package/src/agents/iris.md +8 -45
  29. package/src/agents/mnemosyne.md +12 -47
  30. package/src/agents/nyx.md +8 -45
  31. package/src/agents/prometheus.md +11 -49
  32. package/src/agents/talos.md +6 -45
  33. package/src/agents/themis.md +10 -35
  34. package/src/agents/zeus.md +5 -7
  35. package/src/mcp/mcp_resources_server.py +2 -0
  36. package/src/plugins/tui/dist/tui.tsx +203 -93
  37. package/src/plugins/tui/src/index.tsx +203 -93
  38. package/src/routing.yml +33 -93
  39. package/src/skills/clonedeps/SKILL.md +45 -0
  40. package/src/skills/codemap/SKILL.md +47 -0
  41. package/src/skills/loop-engineering/SKILL.md +51 -0
  42. package/src/skills/reflect/SKILL.md +49 -0
  43. package/src/skills/simplify/SKILL.md +39 -0
  44. package/src/skills/verification-planning/SKILL.md +52 -0
  45. package/src/skills/worktrees/SKILL.md +43 -0
  46. package/commands/pantheon-bg.md +0 -10
  47. package/commands/pantheon-consolidate.md +0 -11
  48. package/commands/pantheon-doc.md +0 -10
  49. package/commands/pantheon-hash.md +0 -11
  50. package/commands/pantheon-todo.md +0 -11
  51. package/docs/AGENT-MCP.md +0 -194
  52. package/docs/ARCHITECTURE.md +0 -384
  53. package/docs/BRANCH-PROTECTION.md +0 -142
  54. package/docs/INDEX.md +0 -81
  55. package/docs/INSTALLATION.md +0 -217
  56. package/docs/MCP.md +0 -238
  57. package/docs/MEMORY.md +0 -471
  58. package/docs/MIGRATION-MEMORY-BANK.md +0 -139
  59. package/docs/PLATFORMS.md +0 -5
  60. package/docs/QUICKSTART.md +0 -49
  61. package/docs/README.md +0 -18
  62. package/docs/RELEASING.md +0 -256
  63. package/docs/SETUP.md +0 -5
  64. package/docs/UPGRADING.md +0 -41
  65. package/docs/mcp-recommendations.md +0 -439
  66. package/docs/mcp-tools.md +0 -156
  67. package/docs/mcp-user-guide.md +0 -204
  68. package/docs/persistence-mcp.md +0 -111
  69. package/scripts/init-pantheon-mcp.sh +0 -118
  70. package/scripts/install.mjs +0 -26
  71. package/src/instructions/documentation-standards.instructions.md +0 -53
  72. package/src/mcp/init-pantheon-mcp.sh +0 -118
@@ -1,45 +1,20 @@
1
1
  ---
2
2
  name: themis
3
- description: Quality & security gate — heuristic scan (Layer 1, zero LLM) + deep
4
- review (Layer 2, LLM leve) + verification planning (Layer 3). Ruff/Biome, anti-pattern
5
- slop detection, hash-anchored edits, OWASP Top 10, coverage >80%. Called by implementers;
6
- escalates blockers to zeus.
7
- mode: primary
3
+ description: "Quality & security gate — Ruff/Biome linting, dead/legacy code detection, OWASP Top 10, coverage >80%, correctness, deprecation audit. Called by implementers; escalates blockers to zeus."
4
+ mode: subagent
8
5
  reasoning_effort: high
9
6
  permission:
7
+ read: allow
8
+ grep: allow
9
+ bash: allow
10
10
  edit: ask
11
- bash:
12
- pytest *: allow
13
- ruff *: allow
14
- grep *: allow
15
- npx vitest *: allow
16
- pip-audit *: allow
17
- dep-audit *: allow
18
- "pantheon-resources_*": allow
19
- "pantheon-memory_*": allow
20
- "pantheon-persistence_*": allow
21
-
22
- tools:
23
- agent: true
24
- vscode/askQuestions: true
25
- search/codebase: true
26
- search/usages: true
27
- read/readFile: true
28
- read/problems: true
29
- execute/runInTerminal: true
30
- execute/testFailure: true
31
- edit/editFiles: true
32
- browser/openBrowserPage: true
33
- browser/navigatePage: true
34
- browser/readPage: true
35
- browser/clickElement: true
36
- browser/screenshotPage: true
37
11
  temperature: 0.1
38
- steps: 20
12
+ steps: 30
39
13
  skills:
40
- - code-review-checklist
41
- - security-hardening
42
- - tdd-with-agents
14
+ - code-review-checklist
15
+ - security-hardening
16
+ - tdd-with-agents
17
+ - context-compression
43
18
  mcp_tools:
44
19
  pantheon-resources: all
45
20
  pantheon-memory: [memory_search]
@@ -7,12 +7,9 @@ permission:
7
7
  bash: allow
8
8
  task:
9
9
  "*": allow
10
- pantheon-resources_*: allow
11
- pantheon-memory_*: allow
12
10
  pantheon-code-mode_*: ask
13
- pantheon-persistence_*: allow
14
11
  temperature: 0.2
15
- steps: 25
12
+ steps: 45
16
13
  mcp_tools:
17
14
  pantheon-resources: all
18
15
  pantheon-memory:
@@ -27,12 +24,13 @@ mcp_tools:
27
24
  - kv_search
28
25
  skills:
29
26
  - agent-coordination
27
+ - session-goal
30
28
  - artifact-management
31
- - auto-continue
32
29
  - context-compression
33
- - memory-bank
30
+ - auto-continue
34
31
  - orchestration-workflow
35
- - session-goal
32
+ - incremental-implementation
33
+
36
34
  ---
37
35
 
38
36
  ## Memory Protocol
@@ -111,6 +111,8 @@ async def list_skills() -> str:
111
111
  async def get_routing() -> str:
112
112
  """Return the full content of routing.yml."""
113
113
  routing_file = _PANTHEON_HOME / "routing.yml"
114
+ if not routing_file.exists() and _PANTHEON_PROJECT:
115
+ routing_file = _PANTHEON_PROJECT / "src" / "routing.yml"
114
116
  if not routing_file.exists():
115
117
  return "routing.yml not found."
116
118
  return routing_file.read_text(encoding="utf-8")
@@ -1,144 +1,254 @@
1
+ // biome-ignore-all lint/suspicious/noExplicitAny: necessary for TuiPluginApi dynamic state access
2
+ // biome-ignore-all lint/a11y/noStaticElementInteractions: TUI elements, not DOM
1
3
  /** @jsxImportSource @opentui/solid */
2
- import type { TuiPlugin, TuiPluginApi, TuiPluginModule, TuiSlotContext, TuiSlotPlugin } from '@opencode-ai/plugin/tui'
3
- import type { JSX } from '@opentui/solid'
4
- import { createMemo, createSignal, Show, For } from 'solid-js'
5
4
 
6
- const AGENTS = [
7
- 'zeus', 'athena', 'apollo', 'hermes', 'aphrodite',
8
- 'demeter', 'themis', 'prometheus', 'hephaestus',
9
- 'nyx', 'gaia', 'iris', 'mnemosyne', 'talos',
5
+ import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from '@opencode-ai/plugin/tui'
6
+ import { createMemo, createSignal, createResource, For, Show } from 'solid-js'
7
+
8
+ async function readPantheonVersion(api: TuiPluginApi): Promise<string> {
9
+ try {
10
+ const worktree = ((api.state as any).path?.worktree ?? '') as string
11
+ const filePath = worktree ? `${worktree}/package.json` : 'package.json'
12
+ const result = await api.client.file.read({ query: { path: filePath } })
13
+ const content = typeof result.content === 'string' ? result.content : String(result.content ?? '')
14
+ const match = content.match(/"version":\s*"([^"]+)"/)
15
+ if (match?.[1]) return match[1]
16
+ } catch { /* fall through */ }
17
+
18
+ try {
19
+ const proc = (api as any).client?.process
20
+ if (typeof proc?.exec === 'function') {
21
+ const result = await proc.exec({ command: 'git', args: ['describe', '--tags', '--always'] })
22
+ const stdout = (result.stdout ?? result.output ?? '') as string
23
+ const tag = stdout.trim().replace(/^v/, '')
24
+ if (tag) return tag
25
+ }
26
+ } catch { /* fall through */ }
27
+
28
+ return '1.0.1'
29
+ }
30
+
31
+ const COMMANDS = [
32
+ { name: '/pantheon', desc: 'Council synthesis' },
33
+ { name: '/pantheon-status', desc: 'System status' },
34
+ { name: '/pantheon-audit', desc: 'Full audit' },
35
+ { name: '/pantheon-bg', desc: 'List background tasks' },
36
+ { name: '/pantheon-cancel', desc: 'Cancel task' },
37
+ { name: '/pantheon-deepwork', desc: 'Deep work mode' },
38
+ { name: '/pantheon-focus', desc: 'Focus on scope' },
39
+ { name: '/pantheon-remember', desc: 'Memory store/recall' },
40
+ { name: '/pantheon-search', desc: 'Memory search' },
41
+ { name: '/pantheon-consolidate', desc: 'Merge memories' },
42
+ { name: '/pantheon-forget', desc: 'Compress memories' },
10
43
  ] as const
11
44
 
12
- const CMDS = [
13
- '/pantheon', '/pantheon-status', '/pantheon-audit',
14
- '/pantheon-bg', '/pantheon-deepwork', '/pantheon-focus',
15
- '/pantheon-optimize', '/pantheon-doc',
16
- '/pantheon-remember', '/pantheon-search', '/pantheon-forget',
17
- '/pantheon-consolidate', '/pantheon-todo', '/pantheon-hash',
45
+ const AGENTS = [
46
+ { name: 'zeus', tier: 'default' as const, role: 'Orchestrator' },
47
+ { name: 'athena', tier: 'premium' as const, role: 'Strategic planner' },
48
+ { name: 'apollo', tier: 'fast' as const, role: 'Codebase discovery' },
49
+ { name: 'hermes', tier: 'default' as const, role: 'Backend' },
50
+ { name: 'aphrodite', tier: 'default' as const, role: 'Frontend' },
51
+ { name: 'demeter', tier: 'default' as const, role: 'Database' },
52
+ { name: 'themis', tier: 'premium' as const, role: 'Quality & security' },
53
+ { name: 'prometheus', tier: 'default' as const, role: 'Infrastructure' },
54
+ { name: 'hephaestus', tier: 'default' as const, role: 'AI pipelines' },
55
+ { name: 'nyx', tier: 'fast' as const, role: 'Observability' },
56
+ { name: 'gaia', tier: 'fast' as const, role: 'Remote sensing' },
57
+ { name: 'iris', tier: 'fast' as const, role: 'GitHub operations' },
58
+ { name: 'mnemosyne', tier: 'fast' as const, role: 'Memory bank' },
59
+ { name: 'talos', tier: 'fast' as const, role: 'Hotfixes' },
18
60
  ] as const
19
61
 
20
- function PantheonPanel(props: { api: TuiPluginApi; version: string }): JSX.Element {
21
- const [showSub, setShowSub] = createSignal(false)
22
- const [showCmd, setShowCmd] = createSignal(false)
23
- const [showAg, setShowAg] = createSignal(false)
24
- const [showCfg, setShowCfg] = createSignal(false)
25
- const [showMem, setShowMem] = createSignal(false)
62
+ function safeNum(v: unknown): number {
63
+ return typeof v === 'number' && Number.isFinite(v) ? v : 0
64
+ }
65
+
66
+ function View(props: { api: TuiPluginApi; sessionID: string; version: string }) {
67
+ const [showCommands, setShowCommands] = createSignal(false)
68
+ const [showAgents, setShowAgents] = createSignal(false)
69
+ const [showConfig, setShowConfig] = createSignal(false)
70
+ const [showMemory, setShowMemory] = createSignal(false)
71
+ const [showSubagents, setShowSubagents] = createSignal(false)
72
+
73
+ const theme = () => props.api.theme.current
74
+
75
+ const branch = createMemo(() =>
76
+ props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null,
77
+ )
78
+
79
+ const configInfo = createMemo(() => {
80
+ const cfg = (props.api.state as any).config
81
+ if (!cfg) return null
82
+ return {
83
+ plugins: Array.isArray(cfg.plugin) ? cfg.plugin : [],
84
+ mcpCount: cfg.mcp ? Object.keys(cfg.mcp).length : 0,
85
+ autoCompaction: cfg.compaction?.auto === true,
86
+ }
87
+ })
26
88
 
27
- const branch = createMemo(() => {
28
- const b = props.api.state.vcs?.branch
29
- return b ? `\u2387 ${b}` : null
89
+ const memoryInfo = createMemo(() => {
90
+ const mem = (props.api.state as any).memory
91
+ if (mem) {
92
+ const entries = safeNum(mem?.entries) || safeNum(mem?.count)
93
+ return entries > 0 ? { entries } : null
94
+ }
95
+ return null
30
96
  })
31
97
 
32
- const mem = (props.api.state as any).memory
33
- const cfg = props.api.state.config
98
+ const [subagents] = createResource(
99
+ () => showSubagents(),
100
+ async () => {
101
+ try {
102
+ const proc = (props.api as any).client?.process
103
+ if (typeof proc?.exec !== 'function') return null
104
+ const result = await proc.exec({
105
+ command: 'opencode',
106
+ args: ['session', 'list', '--format', 'json'],
107
+ timeoutMs: 5000,
108
+ })
109
+ const stdout = (result.stdout ?? result.output ?? '') as string
110
+ const sessions = JSON.parse(stdout)
111
+ if (!Array.isArray(sessions)) return null
112
+ return sessions
113
+ .filter((s: any) => s.id !== props.sessionID)
114
+ .sort((a: any, b: any) => (b.updated ?? 0) - (a.updated ?? 0))
115
+ .slice(0, 10)
116
+ } catch { return null }
117
+ },
118
+ )
34
119
 
35
120
  return (
36
121
  <box flexDirection="column" width="100%">
37
- <text fg={props.api.theme.current.accent} attributes={{ bold: true }}>
38
- Pantheon v{props.version}
122
+ {/* Header */}
123
+ <text fg={theme().accent} attributes={{ bold: true }}>
124
+ {`Pantheon v${props.version}`}
39
125
  </text>
40
126
 
41
127
  <Show when={branch()}>
42
- {(b) => <box marginTop={1}><text fg={props.api.theme.current.textMuted}>{b()}</text></box>}
128
+ {(b) => (<box marginTop={1}><text fg={theme().textMuted}>{b()}</text></box>)}
43
129
  </Show>
44
130
 
45
- {/* Sessions — total historico (API nao distingue ativas) */}
46
- <box marginTop={0} onMouseDown={() => setShowSub((x) => !x)}>
47
- <text fg={props.api.theme.current.text} attributes={{ bold: true }}>
48
- {showSub() ? '\u25bc ' : '\u25b6 '}Sessions
49
- </text>
50
- <text fg={props.api.theme.current.textMuted}>
51
- ({(props.api.state as any).session?.count?.() ?? '?'} total)
131
+ {/* Subagents */}
132
+ <box marginTop={0} onMouseDown={() => setShowSubagents((x) => !x)}>
133
+ <text fg={theme().text} attributes={{ bold: true }}>
134
+ {`${showSubagents() ? '\u25bc' : '\u25b6'} Subagents`}
52
135
  </text>
136
+ <text fg={theme().textMuted}>{` (${String(props.api.state.session.count())})`}</text>
53
137
  </box>
54
138
 
139
+ <Show when={showSubagents()}>
140
+ <Show when={subagents()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>{`sessions: ${String(props.api.state.session.count())}`}</text></box>}>
141
+ {(sessions) => (
142
+ <Show when={sessions().length > 0} fallback={<box marginLeft={1}><text fg={theme().textMuted}>(idle)</text></box>}>
143
+ <box marginLeft={1} flexDirection="column">
144
+ <For each={sessions().slice(0, 5)}>
145
+ {(s: any) => (
146
+ <text fg={theme().textMuted}>{`${s.id.slice(0, 8)}... ${s.title?.slice(0, 30) ?? ''}`}</text>
147
+ )}
148
+ </For>
149
+ </box>
150
+ </Show>
151
+ )}
152
+ </Show>
153
+ </Show>
154
+
55
155
  {/* Commands */}
56
- <box marginTop={0} onMouseDown={() => setShowCmd((x) => !x)}>
57
- <text fg={props.api.theme.current.text} attributes={{ bold: true }}>
58
- {showCmd() ? '\u25bc ' : '\u25b6 '}Commands
156
+ <box marginTop={0} onMouseDown={() => setShowCommands((x) => !x)}>
157
+ <text fg={theme().text} attributes={{ bold: true }}>
158
+ {`${showCommands() ? '\u25bc' : '\u25b6'} Commands`}
59
159
  </text>
60
- <text fg={props.api.theme.current.textMuted}> ({CMDS.length})</text>
160
+ <text fg={theme().textMuted}>{` (${String(COMMANDS.length)})`}</text>
61
161
  </box>
62
- <Show when={showCmd()}>
63
- <box marginLeft={1} flexDirection="column">
64
- <For each={CMDS}>
65
- {(cmd) => <text fg={props.api.theme.current.textMuted}>{'\u00b7'} {cmd}</text>}
66
- </For>
67
- </box>
162
+
163
+ <Show when={showCommands()}>
164
+ <For each={COMMANDS}>
165
+ {(cmd) => (
166
+ <box marginLeft={1} onMouseDown={(e) => {
167
+ e.stopPropagation()
168
+ try {
169
+ const cmdApi = (props.api as any).command
170
+ const cmdName = cmd.name.replace('/', '')
171
+ if (cmdApi?.trigger?.(cmdName)) return
172
+ } catch {}
173
+ props.api.ui?.toast?.({ title: 'Command', message: `Type ${cmd.name} in chat` })
174
+ }}>
175
+ <text fg={cmd.name === '/pantheon' ? theme().accent : theme().textMuted}>{cmd.name}</text>
176
+ <text fg={theme().textMuted}>{` — ${cmd.desc}`}</text>
177
+ </box>
178
+ )}
179
+ </For>
68
180
  </Show>
69
181
 
70
182
  {/* Agents */}
71
- <box marginTop={0} onMouseDown={() => setShowAg((x) => !x)}>
72
- <text fg={props.api.theme.current.text} attributes={{ bold: true }}>
73
- {showAg() ? '\u25bc ' : '\u25b6 '}Agents
183
+ <box marginTop={0} onMouseDown={() => setShowAgents((x) => !x)}>
184
+ <text fg={theme().text} attributes={{ bold: true }}>
185
+ {`${showAgents() ? '\u25bc' : '\u25b6'} Agents`}
74
186
  </text>
75
- <text fg={props.api.theme.current.textMuted}> ({AGENTS.length})</text>
187
+ <text fg={theme().textMuted}>{` (${String(AGENTS.length)})`}</text>
76
188
  </box>
77
- <Show when={showAg()}>
78
- <box marginLeft={1} flexDirection="column">
79
- <For each={AGENTS}>
80
- {(a) => <text fg={props.api.theme.current.textMuted}>{'\u00b7'} {a}</text>}
81
- </For>
82
- </box>
189
+
190
+ <Show when={showAgents()}>
191
+ <For each={AGENTS}>
192
+ {(agent) => (
193
+ <box marginLeft={1}>
194
+ <text fg={agent.tier === 'premium' ? theme().accent : theme().textMuted}>
195
+ {`${agent.tier === 'premium' ? '\u2726 ' : '\u00b7 '}${agent.name}`}
196
+ </text>
197
+ <text fg={theme().textMuted}>{` — ${agent.role}`}</text>
198
+ </box>
199
+ )}
200
+ </For>
83
201
  </Show>
84
202
 
85
203
  {/* Config */}
86
- <box marginTop={0} onMouseDown={() => setShowCfg((x) => !x)}>
87
- <text fg={props.api.theme.current.text} attributes={{ bold: true }}>
88
- {showCfg() ? '\u25bc ' : '\u25b6 '}Config
204
+ <box marginTop={0} onMouseDown={() => setShowConfig((x) => !x)}>
205
+ <text fg={theme().text} attributes={{ bold: true }}>
206
+ {`${showConfig() ? '\u25bc' : '\u25b6'} Config`}
89
207
  </text>
90
208
  </box>
91
- <Show when={showCfg()}>
92
- <box marginLeft={1} flexDirection="column">
93
- <text fg={props.api.theme.current.textMuted}>
94
- MCPs: {cfg?.mcp ? Object.keys(cfg.mcp).length : 0}
95
- </text>
96
- <text fg={props.api.theme.current.textMuted}>
97
- Compaction: {cfg?.compaction?.auto ? 'ON' : 'OFF'}
98
- </text>
99
- </box>
209
+
210
+ <Show when={showConfig()}>
211
+ <Show when={configInfo()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>(no config data)</text></box>}>
212
+ {(cfg) => (
213
+ <box marginLeft={1} flexDirection="column">
214
+ <text fg={theme().textMuted}>{`MCP servers: ${String(cfg().mcpCount)}`}</text>
215
+ <text fg={theme().textMuted}>{`Auto-compaction: ${cfg().autoCompaction ? 'ON' : 'OFF'}`}</text>
216
+ </box>
217
+ )}
218
+ </Show>
100
219
  </Show>
101
220
 
102
221
  {/* Memory */}
103
- <box marginTop={0} onMouseDown={() => setShowMem((x) => !x)}>
104
- <text fg={props.api.theme.current.text} attributes={{ bold: true }}>
105
- {showMem() ? '\u25bc ' : '\u25b6 '}Memory
222
+ <box marginTop={0} onMouseDown={() => setShowMemory((x) => !x)}>
223
+ <text fg={theme().text} attributes={{ bold: true }}>
224
+ {`${showMemory() ? '\u25bc' : '\u25b6'} Memory`}
106
225
  </text>
107
226
  </box>
108
- <Show when={showMem()}>
109
- <box marginLeft={1}>
110
- <text fg={props.api.theme.current.textMuted}>
111
- {mem?.entries > 0 ? `Entries: ${mem.entries}` : '(no data)'}
112
- </text>
113
- </box>
227
+
228
+ <Show when={showMemory()}>
229
+ <Show when={memoryInfo()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>(no data)</text></box>}>
230
+ {(mem) => (<box marginLeft={1}><text fg={theme().textMuted}>{`Entries: ${String(mem().entries)}`}</text></box>)}
231
+ </Show>
114
232
  </Show>
115
233
  </box>
116
234
  )
117
235
  }
118
236
 
119
- function createSlot(api: TuiPluginApi, version: string): TuiSlotPlugin {
120
- return {
237
+ const tui: TuiPlugin = async (api, _options, _meta) => {
238
+ const version = await readPantheonVersion(api)
239
+ api.slots.register({
121
240
  order: 900,
122
241
  slots: {
123
- sidebar_content(_ctx: TuiSlotContext, _input: { session_id: string }): JSX.Element {
124
- return <PantheonPanel api={api} version={version} />
242
+ sidebar_content(_ctx, props) {
243
+ return <View api={api} sessionID={props.session_id} version={version} />
125
244
  },
126
245
  },
127
- }
246
+ })
128
247
  }
129
248
 
130
- const tui: TuiPlugin = async (api: TuiPluginApi) => {
131
- // Read version ONCE at startup (sincrono, evitando Promise)
132
- let version = '5.0.0'
133
- try {
134
- const wt = api.state.path?.worktree ?? ''
135
- const fp = wt ? `${wt}/package.json` : 'package.json'
136
- const r = await api.client.file.read({ query: { path: fp } })
137
- const m = String(r?.content ?? '').match(/"version":\s*"([^"]+)"/)
138
- if (m?.[1]) version = m[1]
139
- } catch { /* fallback */ }
140
-
141
- api.slots.register(createSlot(api, version))
249
+ const plugin: TuiPluginModule & { id: string } = {
250
+ id: 'pantheon.tui',
251
+ tui,
142
252
  }
143
253
 
144
- export default { id: 'pantheon.tui', tui } as TuiPluginModule & { id: string }
254
+ export default plugin