@prevalentware/opencode-goal-plugin 0.1.25 → 0.1.26
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 +1 -1
- package/package.json +3 -3
- package/src/{tui.tsx → tui.ts} +43 -59
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prevalentware/opencode-goal-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "OpenCode goal plugin that adds Codex-style long-running goal mode, /goal commands, persistence, and TUI status for AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"import": "./dist/server.js"
|
|
34
34
|
},
|
|
35
35
|
"./tui": {
|
|
36
|
-
"import": "./src/tui.
|
|
36
|
+
"import": "./src/tui.ts"
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"files": [
|
|
40
40
|
"dist",
|
|
41
|
-
"src/tui.
|
|
41
|
+
"src/tui.ts",
|
|
42
42
|
"LICENSE",
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
package/src/{tui.tsx → tui.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/solid */
|
|
2
1
|
import type { TuiCommand, TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
3
|
-
import {
|
|
2
|
+
import { createElement, insert, setProp } from "@opentui/solid"
|
|
3
|
+
import { createSignal, onCleanup } from "solid-js"
|
|
4
4
|
|
|
5
5
|
type GoalCheckpoint = {
|
|
6
6
|
summary: string
|
|
@@ -63,6 +63,7 @@ type GoalSessionState = {
|
|
|
63
63
|
goal: GoalSnapshot | null
|
|
64
64
|
messageIndex: number
|
|
65
65
|
}
|
|
66
|
+
type ElementChild = string | number | boolean | null | undefined | object | (() => string)
|
|
66
67
|
|
|
67
68
|
type ModernTuiApi = TuiPluginApi & {
|
|
68
69
|
keymap?: {
|
|
@@ -82,6 +83,21 @@ type ModernTuiApi = TuiPluginApi & {
|
|
|
82
83
|
|
|
83
84
|
const goalCache = new Map<string, GoalSnapshot>()
|
|
84
85
|
|
|
86
|
+
function element(tag: string, props: Record<string, unknown>, children: ElementChild[] = []) {
|
|
87
|
+
const node = createElement(tag)
|
|
88
|
+
for (const [key, value] of Object.entries(props)) if (value !== undefined) setProp(node, key, value)
|
|
89
|
+
for (const child of children) if (child !== null && child !== undefined && child !== false) insert(node, child)
|
|
90
|
+
return node
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function text(props: Record<string, unknown>, children: ElementChild[]) {
|
|
94
|
+
return element("text", props, children)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function box(props: Record<string, unknown>, children: ElementChild[] = []) {
|
|
98
|
+
return element("box", props, children)
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
function goalSnapshotKey(sessionID: string) {
|
|
86
102
|
return `goal-mode.snapshot.${sessionID}`
|
|
87
103
|
}
|
|
@@ -332,63 +348,31 @@ function formatGoal(goal: GoalSnapshot | null) {
|
|
|
332
348
|
return lines.join("\n")
|
|
333
349
|
}
|
|
334
350
|
|
|
335
|
-
function GoalSidebar(
|
|
336
|
-
const theme =
|
|
351
|
+
function GoalSidebar(api: TuiPluginApi, sessionID: string) {
|
|
352
|
+
const theme = api.theme.current
|
|
353
|
+
const state = goalStateFromSession(api, sessionID)
|
|
354
|
+
const goal = state.goal
|
|
355
|
+
if (!goal) return null
|
|
356
|
+
if (goal.status === "complete" || goal.status === "unmet") {
|
|
357
|
+
const elapsed = liveTimeUsedSeconds(goal)
|
|
358
|
+
return text({ fg: goal.status === "complete" ? theme.primary : theme.textMuted }, [`${goal.status === "complete" ? "Goal achieved" : "Goal unmet"} (${formatDurationBadge(elapsed)})`])
|
|
359
|
+
}
|
|
337
360
|
const [nowSeconds, setNowSeconds] = createSignal(currentEpochSeconds())
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
{(value: () => GoalSnapshot) => (
|
|
354
|
-
<Show
|
|
355
|
-
when={value().status === "complete" || value().status === "unmet"}
|
|
356
|
-
fallback={
|
|
357
|
-
<box>
|
|
358
|
-
<text fg={theme().text}>
|
|
359
|
-
<b>Goal</b>
|
|
360
|
-
</text>
|
|
361
|
-
<text fg={theme().textMuted}>Status: {value().status}</text>
|
|
362
|
-
<text fg={theme().textMuted}>Time: {formatDuration(elapsed())}</text>
|
|
363
|
-
<text fg={theme().textMuted}>
|
|
364
|
-
Tokens: {value().tokensUsed}
|
|
365
|
-
<Show when={value().tokenBudget}>{(budget: () => number) => <>/{budget()}</>}</Show>
|
|
366
|
-
</text>
|
|
367
|
-
<text fg={theme().textMuted}>
|
|
368
|
-
Auto-continues: {value().autoTurns}
|
|
369
|
-
<Show when={value().maxAutoTurns}>{(budget: () => number) => <>/{budget()}</>}</Show>
|
|
370
|
-
</text>
|
|
371
|
-
<Show when={value().lastCheckpoint}>
|
|
372
|
-
{(checkpoint: () => GoalCheckpoint) => <text fg={theme().textMuted}>Checkpoint: {checkpoint().summary}</text>}
|
|
373
|
-
</Show>
|
|
374
|
-
<Show when={value().stopReason}>
|
|
375
|
-
{(reason: () => string) => <text fg={theme().textMuted}>Stop: {reason()}</text>}
|
|
376
|
-
</Show>
|
|
377
|
-
<Show when={value().lastStatus}>
|
|
378
|
-
{(status: () => string) => <text fg={theme().textMuted}>{status()}</text>}
|
|
379
|
-
</Show>
|
|
380
|
-
<text fg={theme().textMuted}>{objective()}</text>
|
|
381
|
-
</box>
|
|
382
|
-
}
|
|
383
|
-
>
|
|
384
|
-
<text fg={value().status === "complete" ? theme().primary : theme().textMuted}>
|
|
385
|
-
<b>{value().status === "complete" ? "Goal achieved" : "Goal unmet"}</b> (
|
|
386
|
-
{formatDurationBadge(elapsed())})
|
|
387
|
-
</text>
|
|
388
|
-
</Show>
|
|
389
|
-
)}
|
|
390
|
-
</Show>
|
|
391
|
-
)
|
|
361
|
+
if (goal.status === "active") {
|
|
362
|
+
const timer = setInterval(() => setNowSeconds(currentEpochSeconds()), 1000)
|
|
363
|
+
onCleanup(() => clearInterval(timer))
|
|
364
|
+
}
|
|
365
|
+
return box({}, [
|
|
366
|
+
text({ fg: theme.text }, ["Goal"]),
|
|
367
|
+
text({ fg: theme.textMuted }, [`Status: ${goal.status}`]),
|
|
368
|
+
text({ fg: theme.textMuted }, [() => `Time: ${formatDuration(liveTimeUsedSeconds(goal, nowSeconds()))}`]),
|
|
369
|
+
text({ fg: theme.textMuted }, [`Tokens: ${goal.tokensUsed}${goal.tokenBudget == null ? "" : `/${goal.tokenBudget}`}`]),
|
|
370
|
+
text({ fg: theme.textMuted }, [`Auto-continues: ${goal.autoTurns}${goal.maxAutoTurns == null ? "" : `/${goal.maxAutoTurns}`}`]),
|
|
371
|
+
...(goal.lastCheckpoint ? [text({ fg: theme.textMuted }, [`Checkpoint: ${goal.lastCheckpoint.summary}`])] : []),
|
|
372
|
+
...(goal.stopReason ? [text({ fg: theme.textMuted }, [`Stop: ${goal.stopReason}`])] : []),
|
|
373
|
+
...(goal.lastStatus ? [text({ fg: theme.textMuted }, [goal.lastStatus])] : []),
|
|
374
|
+
text({ fg: theme.textMuted }, [goal.objective]),
|
|
375
|
+
])
|
|
392
376
|
}
|
|
393
377
|
|
|
394
378
|
function registerGoalCommand(api: TuiPluginApi, command: TuiCommand) {
|
|
@@ -417,7 +401,7 @@ const tui: TuiPlugin = async (api) => {
|
|
|
417
401
|
order: 125,
|
|
418
402
|
slots: {
|
|
419
403
|
sidebar_content(_ctx, props) {
|
|
420
|
-
return
|
|
404
|
+
return GoalSidebar(api, props.session_id)
|
|
421
405
|
},
|
|
422
406
|
},
|
|
423
407
|
})
|