@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 CHANGED
@@ -210,7 +210,7 @@ OpenCode plugin modules are target-specific. This package exports separate modul
210
210
  {
211
211
  "exports": {
212
212
  "./server": "./dist/server.js",
213
- "./tui": "./src/tui.tsx"
213
+ "./tui": "./src/tui.ts"
214
214
  }
215
215
  }
216
216
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prevalentware/opencode-goal-plugin",
3
- "version": "0.1.25",
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.tsx"
36
+ "import": "./src/tui.ts"
37
37
  }
38
38
  },
39
39
  "files": [
40
40
  "dist",
41
- "src/tui.tsx",
41
+ "src/tui.ts",
42
42
  "LICENSE",
43
43
  "README.md"
44
44
  ],
@@ -1,6 +1,6 @@
1
- /** @jsxImportSource @opentui/solid */
2
1
  import type { TuiCommand, TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui"
3
- import { createMemo, createSignal, onCleanup, Show } from "solid-js"
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(props: { api: TuiPluginApi; sessionID: string }) {
336
- const theme = () => props.api.theme.current
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
- const timer = setInterval(() => setNowSeconds(currentEpochSeconds()), 1000)
339
- onCleanup(() => clearInterval(timer))
340
- const state = createMemo(() => {
341
- props.api.state.session.messages(props.sessionID)
342
- return goalStateFromSession(props.api, props.sessionID)
343
- })
344
- const goal = createMemo(() => state().goal)
345
- const elapsed = createMemo(() => {
346
- const value = goal()
347
- return value ? liveTimeUsedSeconds(value, nowSeconds()) : 0
348
- })
349
- const objective = createMemo(() => goal()?.objective ?? "")
350
-
351
- return (
352
- <Show when={goal()}>
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 <GoalSidebar api={api} sessionID={props.session_id} />
404
+ return GoalSidebar(api, props.session_id)
421
405
  },
422
406
  },
423
407
  })