pantheon-opencode 1.1.1-beta.8 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/opencode.json +49 -2
- package/package.json +1 -1
- package/pantheon.schema.json +1 -1
- package/scripts/install-mcp.mjs +54 -18
- package/scripts/mcp_persistence_server.py +3 -3
- package/src/agents/mnemosyne.md +9 -1
- package/src/agents/nyx.md +7 -1
- package/src/agents/themis.md +8 -2
- package/src/agents/zeus.md +10 -0
- package/src/mcp/install-mcp.mjs +54 -18
- package/src/mcp/mcp_persistence_server.py +3 -3
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +208 -94
- package/src/plugins/tui/dist/tui.tsx +162 -31
- package/src/plugins/tui/src/index.tsx +162 -31
- package/src/routing.yml +11 -30
- package/commands/pantheon-audit.md +0 -25
- package/commands/pantheon-deepwork.md +0 -79
- package/commands/pantheon-focus.md +0 -9
- package/commands/pantheon-forget.md +0 -58
- package/commands/pantheon-optimize.md +0 -79
- package/commands/pantheon-remember.md +0 -44
- package/commands/pantheon-search.md +0 -48
- package/commands/pantheon-status.md +0 -71
- package/commands/pantheon.md +0 -49
- package/scripts/check-version-consistency.sh +0 -23
- package/scripts/generate-routing-docs.mjs +0 -104
- package/scripts/manifest.mjs +0 -597
- package/scripts/memory_cache.py +0 -78
- package/scripts/paths.py +0 -64
- package/scripts/prune_context.py +0 -72
- package/scripts/release-bundle.mjs +0 -109
- package/scripts/todo_enforcer.py +0 -242
- package/scripts/validate_agent_frontmatter.py +0 -226
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/** @jsxImportSource @opentui/solid */
|
|
4
4
|
|
|
5
5
|
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from '@opencode-ai/plugin/tui'
|
|
6
|
-
import { createMemo, createSignal, For, Show } from 'solid-js'
|
|
6
|
+
import { createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
|
|
7
7
|
|
|
8
8
|
/* ─── Constants ─────────────────────────────────────────── */
|
|
9
9
|
|
|
@@ -75,10 +75,54 @@ function buildBar(pct: number): string {
|
|
|
75
75
|
return '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/* ─── Version Detection ────────────────────────────────────
|
|
79
|
+
* 100% dinâmico — zero fallback hardcoded.
|
|
80
|
+
* Se falhar, retorna null e a View omite a versão.
|
|
81
|
+
*
|
|
82
|
+
* Try order:
|
|
83
|
+
* 1. api.client.file.read package.json
|
|
84
|
+
* 2. git describe --tags
|
|
85
|
+
* 3. opencode --version
|
|
86
|
+
* 4. null */
|
|
87
|
+
|
|
88
|
+
async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
89
|
+
// Try 1: package.json do projeto (via worktree path)
|
|
90
|
+
try {
|
|
91
|
+
const wt = ((api.state as any).path?.worktree ?? '') as string
|
|
92
|
+
const fp = wt ? `${wt}/package.json` : 'package.json'
|
|
93
|
+
const result = await api.client.file.read({ query: { path: fp } })
|
|
94
|
+
const match = String(result?.content ?? '').match(/"version":\s*"([^"]+)"/)
|
|
95
|
+
if (match?.[1]) return match[1]
|
|
96
|
+
} catch { /* fall through */ }
|
|
97
|
+
|
|
98
|
+
// Try 2: git describe --tags
|
|
99
|
+
try {
|
|
100
|
+
const proc = (api as any).client?.process
|
|
101
|
+
if (typeof proc?.exec === 'function') {
|
|
102
|
+
const r = await proc.exec({ command: 'git', args: ['describe', '--tags', '--always'], timeoutMs: 3000 })
|
|
103
|
+
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
104
|
+
const tag = stdout.trim().replace(/^v/, '').replace(/-\d+-g[0-9a-f]+$/, '')
|
|
105
|
+
if (tag && tag !== '5.0.0') return tag
|
|
106
|
+
}
|
|
107
|
+
} catch { /* fall through */ }
|
|
108
|
+
|
|
109
|
+
// Try 3: opencode --version
|
|
110
|
+
try {
|
|
111
|
+
const proc = (api as any).client?.process
|
|
112
|
+
if (typeof proc?.exec === 'function') {
|
|
113
|
+
const r = await proc.exec({ command: 'opencode', args: ['--version'], timeoutMs: 3000 })
|
|
114
|
+
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
115
|
+
const ver = stdout.trim().replace(/^v/, '')
|
|
116
|
+
if (ver) return ver
|
|
117
|
+
}
|
|
118
|
+
} catch { /* fall through */ }
|
|
119
|
+
|
|
120
|
+
return null // sem fallback — versão não aparece
|
|
121
|
+
}
|
|
122
|
+
|
|
78
123
|
/* ─── Context Bar ──────────────────────────────────────────
|
|
79
124
|
* Inspired by streetturtle/opencode-better-sidebar (context-progress)
|
|
80
|
-
* Shows: progress bar, token usage / limit, session cost
|
|
81
|
-
* Always visible at the top of the sidebar */
|
|
125
|
+
* Shows: progress bar, token usage / limit, session cost */
|
|
82
126
|
|
|
83
127
|
function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
84
128
|
const messages = createMemo((): any[] => {
|
|
@@ -101,7 +145,7 @@ function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
|
101
145
|
? ((props.api.state as any).provider ?? []).find((p: any) => p.id === pid)
|
|
102
146
|
: null
|
|
103
147
|
const ctxLimit = safeNum(provider?.models?.[mid ?? '']?.limit?.context)
|
|
104
|
-
const limit = ctxLimit > 0 ? ctxLimit :
|
|
148
|
+
const limit = ctxLimit > 0 ? ctxLimit : 200000
|
|
105
149
|
return { tokens, limit, percent: ctxLimit > 0 ? Math.round((tokens / ctxLimit) * 100) : 0 }
|
|
106
150
|
})
|
|
107
151
|
|
|
@@ -138,13 +182,41 @@ function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
|
138
182
|
)
|
|
139
183
|
}
|
|
140
184
|
|
|
185
|
+
/* ─── Session Row (single session with live status indicator) ─ */
|
|
186
|
+
|
|
187
|
+
function SessionRow(props: { api: TuiPluginApi; session: any }) {
|
|
188
|
+
const theme = () => props.api.theme.current
|
|
189
|
+
|
|
190
|
+
const status = createMemo(() => {
|
|
191
|
+
try {
|
|
192
|
+
const s = (props.api.state as any).session?.status?.(props.session.id)
|
|
193
|
+
if (s?.type) return s.type as string
|
|
194
|
+
} catch { /* ignore */ }
|
|
195
|
+
return null
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const statusIcon = createMemo(() => {
|
|
199
|
+
switch (status()) {
|
|
200
|
+
case 'busy': return '\u25cf ' // ● — running
|
|
201
|
+
case 'retry': return '\u26a0 ' // ⚠ — needs attention
|
|
202
|
+
default: return '' // idle/unknown — no icon
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<text fg={theme().textMuted}>
|
|
208
|
+
{`${statusIcon()}${props.session?.id?.slice(0, 8) ?? '????'}... ${props.session?.title?.slice(0, 26) ?? '(untitled)'}`}
|
|
209
|
+
</text>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
141
213
|
/* ─── Main Sidebar View ──────────────────────────────────── */
|
|
142
214
|
|
|
143
|
-
function View(props: { api: TuiPluginApi; sessionID: string; version: string }) {
|
|
215
|
+
function View(props: { api: TuiPluginApi; sessionID: string; version: string | null }) {
|
|
144
216
|
const [showCommands, setShowCommands] = createSignal(false)
|
|
145
217
|
const [showAgents, setShowAgents] = createSignal(false)
|
|
146
218
|
const [showConfig, setShowConfig] = createSignal(false)
|
|
147
|
-
const [
|
|
219
|
+
const [showSessions, setShowSessions] = createSignal(false)
|
|
148
220
|
|
|
149
221
|
const theme = () => props.api.theme.current
|
|
150
222
|
|
|
@@ -152,15 +224,73 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
152
224
|
props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null,
|
|
153
225
|
)
|
|
154
226
|
|
|
155
|
-
|
|
156
|
-
|
|
227
|
+
// ── Sessions: fetch via api.client.session.list (not proc.exec) ──
|
|
228
|
+
const [sessionList, { refetch: refetchSessions }] = createResource(async () => {
|
|
229
|
+
try {
|
|
230
|
+
const result = await (props.api.client as any)?.session?.list?.({ limit: 100 })
|
|
231
|
+
return result ?? { data: [] }
|
|
232
|
+
} catch {
|
|
233
|
+
return { data: [] }
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Total top-level sessions (excludes subagent sessions)
|
|
238
|
+
const totalSessions = createMemo(() => {
|
|
239
|
+
const result = sessionList()
|
|
240
|
+
if (!result) return 0
|
|
241
|
+
const data: any[] = (result as any).data ?? result
|
|
242
|
+
if (!Array.isArray(data)) return 0
|
|
243
|
+
return data.filter((s: any) => !s.parentID).length
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// Recent top-level sessions, sorted by time.updated descending
|
|
247
|
+
const recentSessions = createMemo(() => {
|
|
248
|
+
const result = sessionList()
|
|
249
|
+
if (!result) return []
|
|
250
|
+
const data: any[] = (result as any).data ?? result
|
|
251
|
+
if (!Array.isArray(data)) return []
|
|
252
|
+
return data
|
|
253
|
+
.filter((s: any) => !s.parentID && s.id !== props.sessionID)
|
|
254
|
+
.sort((a: any, b: any) => {
|
|
255
|
+
const ta = a.time?.updated ?? a.updated ?? 0
|
|
256
|
+
const tb = b.time?.updated ?? b.updated ?? 0
|
|
257
|
+
return tb - ta
|
|
258
|
+
})
|
|
259
|
+
.slice(0, 8)
|
|
157
260
|
})
|
|
158
261
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
262
|
+
// ── Memory: starts at null (= "N/A"), tracks via events ──
|
|
263
|
+
const [memoryCount, setMemoryCount] = createSignal<number | null>(null)
|
|
264
|
+
|
|
265
|
+
// ── Event subscriptions for live session/memory updates ──
|
|
266
|
+
onMount(() => {
|
|
267
|
+
const cleanup: (() => void)[] = []
|
|
268
|
+
|
|
269
|
+
// Session events — triggers resource refetch
|
|
270
|
+
try {
|
|
271
|
+
cleanup.push(props.api.event.on('session.status', refetchSessions))
|
|
272
|
+
cleanup.push(props.api.event.on('session.created', refetchSessions))
|
|
273
|
+
cleanup.push(props.api.event.on('session.updated', refetchSessions))
|
|
274
|
+
cleanup.push(props.api.event.on('session.deleted', refetchSessions))
|
|
275
|
+
} catch { /* events API not available in this runtime */ }
|
|
276
|
+
|
|
277
|
+
// Memory events — tracks running counter (no api.state.memory)
|
|
278
|
+
try {
|
|
279
|
+
const ev = (props.api.event as any)
|
|
280
|
+
if (typeof ev?.on === 'function') {
|
|
281
|
+
cleanup.push(
|
|
282
|
+
ev.on('memory.created', () => setMemoryCount((c) => (c !== null ? c + 1 : 1))),
|
|
283
|
+
)
|
|
284
|
+
cleanup.push(
|
|
285
|
+
ev.on('memory.deleted', () => setMemoryCount((c) => (c !== null ? Math.max(0, c - 1) : 0))),
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
} catch { /* memory events not available */ }
|
|
289
|
+
|
|
290
|
+
onCleanup(() => cleanup.forEach((fn) => fn()))
|
|
162
291
|
})
|
|
163
292
|
|
|
293
|
+
// ── Config summary ──
|
|
164
294
|
const configSummary = createMemo(() => {
|
|
165
295
|
const cfg = (props.api.state as any).config
|
|
166
296
|
if (!cfg) return null
|
|
@@ -177,7 +307,7 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
177
307
|
<box flexDirection="column" width="100%">
|
|
178
308
|
{/* ── Header ── */}
|
|
179
309
|
<text fg={theme().accent} attributes={{ bold: true }}>
|
|
180
|
-
{`Pantheon v${props.version}`}
|
|
310
|
+
{`Pantheon${props.version ? ` v${props.version}` : ''}`}
|
|
181
311
|
</text>
|
|
182
312
|
<Show when={branch()}>
|
|
183
313
|
{(b) => <text fg={theme().textMuted}>{b()}</text>}
|
|
@@ -190,17 +320,24 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
190
320
|
|
|
191
321
|
<HR />
|
|
192
322
|
|
|
193
|
-
{/* ──
|
|
194
|
-
<box onMouseDown={() =>
|
|
323
|
+
{/* ── Sessions ── */}
|
|
324
|
+
<box onMouseDown={() => setShowSessions((x) => !x)}>
|
|
195
325
|
<text fg={theme().text} attributes={{ bold: true }}>
|
|
196
|
-
{`${
|
|
326
|
+
{`${showSessions() ? '\u25bc' : '\u25b6'} Sessions`}
|
|
197
327
|
</text>
|
|
198
|
-
<text fg={theme().textMuted}>{` (${String(
|
|
328
|
+
<text fg={theme().textMuted}>{` (${String(totalSessions())})`}</text>
|
|
199
329
|
</box>
|
|
200
|
-
<Show when={
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
330
|
+
<Show when={showSessions()}>
|
|
331
|
+
<Show
|
|
332
|
+
when={recentSessions().length > 0}
|
|
333
|
+
fallback={<box marginLeft={1}><text fg={theme().textMuted}>No recent sessions</text></box>}
|
|
334
|
+
>
|
|
335
|
+
<box marginLeft={1} flexDirection="column">
|
|
336
|
+
<For each={recentSessions()}>
|
|
337
|
+
{(ses: any) => <SessionRow api={props.api} session={ses} />}
|
|
338
|
+
</For>
|
|
339
|
+
</box>
|
|
340
|
+
</Show>
|
|
204
341
|
</Show>
|
|
205
342
|
|
|
206
343
|
{/* ── Commands ── */}
|
|
@@ -265,7 +402,7 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
265
402
|
<Show when={configSummary()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>No config data</text></box>}>
|
|
266
403
|
{(cfg) => (
|
|
267
404
|
<box marginLeft={1} flexDirection="column">
|
|
268
|
-
<text fg={theme().textMuted}>{`MCP
|
|
405
|
+
<text fg={theme().textMuted}>{`MCP: ${String(cfg().mcpCount)} Plugins: ${String(cfg().pluginCount)}`}</text>
|
|
269
406
|
<text fg={theme().textMuted}>{`Auto-compaction: ${cfg().autoCompaction ? 'ON' : 'OFF'}`}</text>
|
|
270
407
|
</box>
|
|
271
408
|
)}
|
|
@@ -275,7 +412,9 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
275
412
|
{/* ── Memory (always visible summary) ── */}
|
|
276
413
|
<box marginTop={1}>
|
|
277
414
|
<text fg={theme().textMuted}>
|
|
278
|
-
{
|
|
415
|
+
{memoryCount() !== null
|
|
416
|
+
? `Memory: ${fmtInt(memoryCount()!)} entries`
|
|
417
|
+
: 'Memory: N/A'}
|
|
279
418
|
</text>
|
|
280
419
|
</box>
|
|
281
420
|
</box>
|
|
@@ -285,15 +424,7 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
285
424
|
/* ─── Plugin Registration ────────────────────────────────── */
|
|
286
425
|
|
|
287
426
|
const tui: TuiPlugin = async (api, _options, _meta) => {
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
const wt = ((api.state as any).path?.worktree ?? '') as string
|
|
291
|
-
const fp = wt ? `${wt}/package.json` : 'package.json'
|
|
292
|
-
const result = await api.client.file.read({ query: { path: fp } })
|
|
293
|
-
const content = String(result?.content ?? '')
|
|
294
|
-
const match = content.match(/"version":\s*"([^"]+)"/)
|
|
295
|
-
if (match?.[1]) version = match[1]
|
|
296
|
-
} catch { /* use default */ }
|
|
427
|
+
const version = await detectVersion(api)
|
|
297
428
|
|
|
298
429
|
api.slots.register({
|
|
299
430
|
order: 900,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/** @jsxImportSource @opentui/solid */
|
|
4
4
|
|
|
5
5
|
import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from '@opencode-ai/plugin/tui'
|
|
6
|
-
import { createMemo, createSignal, For, Show } from 'solid-js'
|
|
6
|
+
import { createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
|
|
7
7
|
|
|
8
8
|
/* ─── Constants ─────────────────────────────────────────── */
|
|
9
9
|
|
|
@@ -75,10 +75,54 @@ function buildBar(pct: number): string {
|
|
|
75
75
|
return '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/* ─── Version Detection ────────────────────────────────────
|
|
79
|
+
* 100% dinâmico — zero fallback hardcoded.
|
|
80
|
+
* Se falhar, retorna null e a View omite a versão.
|
|
81
|
+
*
|
|
82
|
+
* Try order:
|
|
83
|
+
* 1. api.client.file.read package.json
|
|
84
|
+
* 2. git describe --tags
|
|
85
|
+
* 3. opencode --version
|
|
86
|
+
* 4. null */
|
|
87
|
+
|
|
88
|
+
async function detectVersion(api: TuiPluginApi): Promise<string | null> {
|
|
89
|
+
// Try 1: package.json do projeto (via worktree path)
|
|
90
|
+
try {
|
|
91
|
+
const wt = ((api.state as any).path?.worktree ?? '') as string
|
|
92
|
+
const fp = wt ? `${wt}/package.json` : 'package.json'
|
|
93
|
+
const result = await api.client.file.read({ query: { path: fp } })
|
|
94
|
+
const match = String(result?.content ?? '').match(/"version":\s*"([^"]+)"/)
|
|
95
|
+
if (match?.[1]) return match[1]
|
|
96
|
+
} catch { /* fall through */ }
|
|
97
|
+
|
|
98
|
+
// Try 2: git describe --tags
|
|
99
|
+
try {
|
|
100
|
+
const proc = (api as any).client?.process
|
|
101
|
+
if (typeof proc?.exec === 'function') {
|
|
102
|
+
const r = await proc.exec({ command: 'git', args: ['describe', '--tags', '--always'], timeoutMs: 3000 })
|
|
103
|
+
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
104
|
+
const tag = stdout.trim().replace(/^v/, '').replace(/-\d+-g[0-9a-f]+$/, '')
|
|
105
|
+
if (tag && tag !== '5.0.0') return tag
|
|
106
|
+
}
|
|
107
|
+
} catch { /* fall through */ }
|
|
108
|
+
|
|
109
|
+
// Try 3: opencode --version
|
|
110
|
+
try {
|
|
111
|
+
const proc = (api as any).client?.process
|
|
112
|
+
if (typeof proc?.exec === 'function') {
|
|
113
|
+
const r = await proc.exec({ command: 'opencode', args: ['--version'], timeoutMs: 3000 })
|
|
114
|
+
const stdout = (r.stdout ?? r.output ?? '') as string
|
|
115
|
+
const ver = stdout.trim().replace(/^v/, '')
|
|
116
|
+
if (ver) return ver
|
|
117
|
+
}
|
|
118
|
+
} catch { /* fall through */ }
|
|
119
|
+
|
|
120
|
+
return null // sem fallback — versão não aparece
|
|
121
|
+
}
|
|
122
|
+
|
|
78
123
|
/* ─── Context Bar ──────────────────────────────────────────
|
|
79
124
|
* Inspired by streetturtle/opencode-better-sidebar (context-progress)
|
|
80
|
-
* Shows: progress bar, token usage / limit, session cost
|
|
81
|
-
* Always visible at the top of the sidebar */
|
|
125
|
+
* Shows: progress bar, token usage / limit, session cost */
|
|
82
126
|
|
|
83
127
|
function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
84
128
|
const messages = createMemo((): any[] => {
|
|
@@ -101,7 +145,7 @@ function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
|
101
145
|
? ((props.api.state as any).provider ?? []).find((p: any) => p.id === pid)
|
|
102
146
|
: null
|
|
103
147
|
const ctxLimit = safeNum(provider?.models?.[mid ?? '']?.limit?.context)
|
|
104
|
-
const limit = ctxLimit > 0 ? ctxLimit :
|
|
148
|
+
const limit = ctxLimit > 0 ? ctxLimit : 200000
|
|
105
149
|
return { tokens, limit, percent: ctxLimit > 0 ? Math.round((tokens / ctxLimit) * 100) : 0 }
|
|
106
150
|
})
|
|
107
151
|
|
|
@@ -138,13 +182,41 @@ function ContextBar(props: { api: TuiPluginApi; sessionID: string }) {
|
|
|
138
182
|
)
|
|
139
183
|
}
|
|
140
184
|
|
|
185
|
+
/* ─── Session Row (single session with live status indicator) ─ */
|
|
186
|
+
|
|
187
|
+
function SessionRow(props: { api: TuiPluginApi; session: any }) {
|
|
188
|
+
const theme = () => props.api.theme.current
|
|
189
|
+
|
|
190
|
+
const status = createMemo(() => {
|
|
191
|
+
try {
|
|
192
|
+
const s = (props.api.state as any).session?.status?.(props.session.id)
|
|
193
|
+
if (s?.type) return s.type as string
|
|
194
|
+
} catch { /* ignore */ }
|
|
195
|
+
return null
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const statusIcon = createMemo(() => {
|
|
199
|
+
switch (status()) {
|
|
200
|
+
case 'busy': return '\u25cf ' // ● — running
|
|
201
|
+
case 'retry': return '\u26a0 ' // ⚠ — needs attention
|
|
202
|
+
default: return '' // idle/unknown — no icon
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<text fg={theme().textMuted}>
|
|
208
|
+
{`${statusIcon()}${props.session?.id?.slice(0, 8) ?? '????'}... ${props.session?.title?.slice(0, 26) ?? '(untitled)'}`}
|
|
209
|
+
</text>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
141
213
|
/* ─── Main Sidebar View ──────────────────────────────────── */
|
|
142
214
|
|
|
143
|
-
function View(props: { api: TuiPluginApi; sessionID: string; version: string }) {
|
|
215
|
+
function View(props: { api: TuiPluginApi; sessionID: string; version: string | null }) {
|
|
144
216
|
const [showCommands, setShowCommands] = createSignal(false)
|
|
145
217
|
const [showAgents, setShowAgents] = createSignal(false)
|
|
146
218
|
const [showConfig, setShowConfig] = createSignal(false)
|
|
147
|
-
const [
|
|
219
|
+
const [showSessions, setShowSessions] = createSignal(false)
|
|
148
220
|
|
|
149
221
|
const theme = () => props.api.theme.current
|
|
150
222
|
|
|
@@ -152,15 +224,73 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
152
224
|
props.api.state.vcs?.branch ? `\u2387 ${props.api.state.vcs.branch}` : null,
|
|
153
225
|
)
|
|
154
226
|
|
|
155
|
-
|
|
156
|
-
|
|
227
|
+
// ── Sessions: fetch via api.client.session.list (not proc.exec) ──
|
|
228
|
+
const [sessionList, { refetch: refetchSessions }] = createResource(async () => {
|
|
229
|
+
try {
|
|
230
|
+
const result = await (props.api.client as any)?.session?.list?.({ limit: 100 })
|
|
231
|
+
return result ?? { data: [] }
|
|
232
|
+
} catch {
|
|
233
|
+
return { data: [] }
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Total top-level sessions (excludes subagent sessions)
|
|
238
|
+
const totalSessions = createMemo(() => {
|
|
239
|
+
const result = sessionList()
|
|
240
|
+
if (!result) return 0
|
|
241
|
+
const data: any[] = (result as any).data ?? result
|
|
242
|
+
if (!Array.isArray(data)) return 0
|
|
243
|
+
return data.filter((s: any) => !s.parentID).length
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// Recent top-level sessions, sorted by time.updated descending
|
|
247
|
+
const recentSessions = createMemo(() => {
|
|
248
|
+
const result = sessionList()
|
|
249
|
+
if (!result) return []
|
|
250
|
+
const data: any[] = (result as any).data ?? result
|
|
251
|
+
if (!Array.isArray(data)) return []
|
|
252
|
+
return data
|
|
253
|
+
.filter((s: any) => !s.parentID && s.id !== props.sessionID)
|
|
254
|
+
.sort((a: any, b: any) => {
|
|
255
|
+
const ta = a.time?.updated ?? a.updated ?? 0
|
|
256
|
+
const tb = b.time?.updated ?? b.updated ?? 0
|
|
257
|
+
return tb - ta
|
|
258
|
+
})
|
|
259
|
+
.slice(0, 8)
|
|
157
260
|
})
|
|
158
261
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
262
|
+
// ── Memory: starts at null (= "N/A"), tracks via events ──
|
|
263
|
+
const [memoryCount, setMemoryCount] = createSignal<number | null>(null)
|
|
264
|
+
|
|
265
|
+
// ── Event subscriptions for live session/memory updates ──
|
|
266
|
+
onMount(() => {
|
|
267
|
+
const cleanup: (() => void)[] = []
|
|
268
|
+
|
|
269
|
+
// Session events — triggers resource refetch
|
|
270
|
+
try {
|
|
271
|
+
cleanup.push(props.api.event.on('session.status', refetchSessions))
|
|
272
|
+
cleanup.push(props.api.event.on('session.created', refetchSessions))
|
|
273
|
+
cleanup.push(props.api.event.on('session.updated', refetchSessions))
|
|
274
|
+
cleanup.push(props.api.event.on('session.deleted', refetchSessions))
|
|
275
|
+
} catch { /* events API not available in this runtime */ }
|
|
276
|
+
|
|
277
|
+
// Memory events — tracks running counter (no api.state.memory)
|
|
278
|
+
try {
|
|
279
|
+
const ev = (props.api.event as any)
|
|
280
|
+
if (typeof ev?.on === 'function') {
|
|
281
|
+
cleanup.push(
|
|
282
|
+
ev.on('memory.created', () => setMemoryCount((c) => (c !== null ? c + 1 : 1))),
|
|
283
|
+
)
|
|
284
|
+
cleanup.push(
|
|
285
|
+
ev.on('memory.deleted', () => setMemoryCount((c) => (c !== null ? Math.max(0, c - 1) : 0))),
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
} catch { /* memory events not available */ }
|
|
289
|
+
|
|
290
|
+
onCleanup(() => cleanup.forEach((fn) => fn()))
|
|
162
291
|
})
|
|
163
292
|
|
|
293
|
+
// ── Config summary ──
|
|
164
294
|
const configSummary = createMemo(() => {
|
|
165
295
|
const cfg = (props.api.state as any).config
|
|
166
296
|
if (!cfg) return null
|
|
@@ -177,7 +307,7 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
177
307
|
<box flexDirection="column" width="100%">
|
|
178
308
|
{/* ── Header ── */}
|
|
179
309
|
<text fg={theme().accent} attributes={{ bold: true }}>
|
|
180
|
-
{`Pantheon v${props.version}`}
|
|
310
|
+
{`Pantheon${props.version ? ` v${props.version}` : ''}`}
|
|
181
311
|
</text>
|
|
182
312
|
<Show when={branch()}>
|
|
183
313
|
{(b) => <text fg={theme().textMuted}>{b()}</text>}
|
|
@@ -190,17 +320,24 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
190
320
|
|
|
191
321
|
<HR />
|
|
192
322
|
|
|
193
|
-
{/* ──
|
|
194
|
-
<box onMouseDown={() =>
|
|
323
|
+
{/* ── Sessions ── */}
|
|
324
|
+
<box onMouseDown={() => setShowSessions((x) => !x)}>
|
|
195
325
|
<text fg={theme().text} attributes={{ bold: true }}>
|
|
196
|
-
{`${
|
|
326
|
+
{`${showSessions() ? '\u25bc' : '\u25b6'} Sessions`}
|
|
197
327
|
</text>
|
|
198
|
-
<text fg={theme().textMuted}>{` (${String(
|
|
328
|
+
<text fg={theme().textMuted}>{` (${String(totalSessions())})`}</text>
|
|
199
329
|
</box>
|
|
200
|
-
<Show when={
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
330
|
+
<Show when={showSessions()}>
|
|
331
|
+
<Show
|
|
332
|
+
when={recentSessions().length > 0}
|
|
333
|
+
fallback={<box marginLeft={1}><text fg={theme().textMuted}>No recent sessions</text></box>}
|
|
334
|
+
>
|
|
335
|
+
<box marginLeft={1} flexDirection="column">
|
|
336
|
+
<For each={recentSessions()}>
|
|
337
|
+
{(ses: any) => <SessionRow api={props.api} session={ses} />}
|
|
338
|
+
</For>
|
|
339
|
+
</box>
|
|
340
|
+
</Show>
|
|
204
341
|
</Show>
|
|
205
342
|
|
|
206
343
|
{/* ── Commands ── */}
|
|
@@ -265,7 +402,7 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
265
402
|
<Show when={configSummary()} fallback={<box marginLeft={1}><text fg={theme().textMuted}>No config data</text></box>}>
|
|
266
403
|
{(cfg) => (
|
|
267
404
|
<box marginLeft={1} flexDirection="column">
|
|
268
|
-
<text fg={theme().textMuted}>{`MCP
|
|
405
|
+
<text fg={theme().textMuted}>{`MCP: ${String(cfg().mcpCount)} Plugins: ${String(cfg().pluginCount)}`}</text>
|
|
269
406
|
<text fg={theme().textMuted}>{`Auto-compaction: ${cfg().autoCompaction ? 'ON' : 'OFF'}`}</text>
|
|
270
407
|
</box>
|
|
271
408
|
)}
|
|
@@ -275,7 +412,9 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
275
412
|
{/* ── Memory (always visible summary) ── */}
|
|
276
413
|
<box marginTop={1}>
|
|
277
414
|
<text fg={theme().textMuted}>
|
|
278
|
-
{
|
|
415
|
+
{memoryCount() !== null
|
|
416
|
+
? `Memory: ${fmtInt(memoryCount()!)} entries`
|
|
417
|
+
: 'Memory: N/A'}
|
|
279
418
|
</text>
|
|
280
419
|
</box>
|
|
281
420
|
</box>
|
|
@@ -285,15 +424,7 @@ function View(props: { api: TuiPluginApi; sessionID: string; version: string })
|
|
|
285
424
|
/* ─── Plugin Registration ────────────────────────────────── */
|
|
286
425
|
|
|
287
426
|
const tui: TuiPlugin = async (api, _options, _meta) => {
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
const wt = ((api.state as any).path?.worktree ?? '') as string
|
|
291
|
-
const fp = wt ? `${wt}/package.json` : 'package.json'
|
|
292
|
-
const result = await api.client.file.read({ query: { path: fp } })
|
|
293
|
-
const content = String(result?.content ?? '')
|
|
294
|
-
const match = content.match(/"version":\s*"([^"]+)"/)
|
|
295
|
-
if (match?.[1]) version = match[1]
|
|
296
|
-
} catch { /* use default */ }
|
|
427
|
+
const version = await detectVersion(api)
|
|
297
428
|
|
|
298
429
|
api.slots.register({
|
|
299
430
|
order: 900,
|