@prevalentware/opencode-goal-plugin 0.1.4 → 0.1.5

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
@@ -4,7 +4,7 @@ Codex-style long-running goal mode for OpenCode.
4
4
 
5
5
  This plugin adds:
6
6
 
7
- - `/goal` in the OpenCode TUI.
7
+ - `/goal <objective>` as an OpenCode command for TUI, desktop, and web.
8
8
  - A sidebar goal indicator with status, elapsed time, token usage, remaining budget, and objective.
9
9
  - Agent tools: `get_goal`, `create_goal`, `update_goal`, and `clear_goal`.
10
10
  - Goal close evidence: `complete` requires verified evidence, and `unmet` requires a concrete blocker.
@@ -60,7 +60,8 @@ Server options can be configured in `opencode.json`:
60
60
  {
61
61
  "auto_continue": true,
62
62
  "max_auto_turns": 25,
63
- "min_continue_interval_seconds": 3
63
+ "min_continue_interval_seconds": 3,
64
+ "default_token_budget": null
64
65
  }
65
66
  ]
66
67
  ]
@@ -72,18 +73,23 @@ Defaults:
72
73
  - `auto_continue`: `true`
73
74
  - `max_auto_turns`: `25`
74
75
  - `min_continue_interval_seconds`: `3`
76
+ - `register_command`: `true`
77
+ - `command_name`: `"goal"`
78
+ - `default_token_budget`: `null`
75
79
 
76
80
  ## Goal Workflow
77
81
 
78
- Use `/goal` from an OpenCode TUI session to set, refresh, or clear the goal. New goals support budget presets:
82
+ Use `/goal <objective>` in a fresh OpenCode chat to create a long-running goal:
79
83
 
80
- - No budget
81
- - `250K`
82
- - `1M`
83
- - `2M`
84
- - Custom positive integer
84
+ ```text
85
+ /goal review the frontend and translate visible English UI text to Spanish
86
+ ```
87
+
88
+ Bare `/goal` reports the current goal state. `/goal clear` clears the goal. The TUI also includes a `Goal` command-palette entry for viewing, refreshing, or clearing the current goal state without creating a new goal.
89
+
90
+ By default, `/goal <objective>` omits `token_budget`, matching Codex TUI behavior. If you want every new slash-created goal to use a fixed token budget without prompting the user, set `default_token_budget` to a positive integer in `opencode.json`.
85
91
 
86
- When setting the objective, include the scope, non-goals, and verification path when they matter. The agent is reminded to audit real files, command output, tests, or PR state before closing the goal.
92
+ When writing the objective, include the scope, non-goals, and verification path when they matter. The agent is reminded to audit real files, command output, tests, or PR state before closing the goal.
87
93
 
88
94
  The `update_goal` tool can close a goal in two ways:
89
95
 
package/dist/server.js CHANGED
@@ -297,6 +297,49 @@ Preserve the goal objective, status, budget, elapsed time, token count, and any
297
297
  // src/server.ts
298
298
  var DEFAULT_MAX_AUTO_TURNS = 25;
299
299
  var DEFAULT_CONTINUE_INTERVAL_SECONDS = 3;
300
+ var DEFAULT_COMMAND_NAME = "goal";
301
+ function defaultTokenBudgetFromOptions(options) {
302
+ const budget = options?.default_token_budget;
303
+ if (budget == null)
304
+ return null;
305
+ return Number.isInteger(budget) && budget > 0 ? budget : null;
306
+ }
307
+ function goalCommandTemplate(commandName, defaultTokenBudget) {
308
+ const defaultBudgetInstruction = defaultTokenBudget == null ? "By default, omit token_budget. This matches Codex TUI behavior for /goal <objective>." : `By default, pass token_budget: ${defaultTokenBudget} when creating a goal unless the user explicitly requests a different token budget or no budget.`;
309
+ return `OpenCode goal mode command "/${commandName}" was invoked.
310
+
311
+ Arguments:
312
+ <goal_command_arguments>
313
+ $ARGUMENTS
314
+ </goal_command_arguments>
315
+
316
+ Use the goal tools to handle this command:
317
+
318
+ - If the arguments are empty, call get_goal and briefly report the current goal state.
319
+ - If the arguments are "status", "show", or "current", call get_goal and briefly report the current goal state.
320
+ - If the arguments are "clear", call clear_goal and report whether a goal was cleared.
321
+ - If the arguments start with "complete " or "done ", perform a completion audit against real artifacts and command output. Call update_goal with status "complete" only if the goal is achieved, using concise evidence from the audit.
322
+ - If the arguments start with "unmet ", "blocked ", or "blocker ", call update_goal with status "unmet" only when the goal cannot be achieved or needs external input, using the remaining arguments as the blocker.
323
+ - Otherwise, create a new goal with create_goal. Use the full arguments as the objective. ${defaultBudgetInstruction}
324
+ - Set token_budget only from this default or when the arguments explicitly include a token budget such as "--budget 250000", "budget=250000", or "token_budget=250000".
325
+
326
+ Create a goal only from these explicit command arguments. Do not infer a goal from unrelated session context. After create_goal succeeds, continue working toward the new goal.`;
327
+ }
328
+ function commandNameFromOptions(options) {
329
+ const name = options?.command_name?.trim() || DEFAULT_COMMAND_NAME;
330
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name))
331
+ return DEFAULT_COMMAND_NAME;
332
+ return name;
333
+ }
334
+ function registerDesktopCommand(config, commandName, defaultTokenBudget) {
335
+ config.command ??= {};
336
+ if (config.command[commandName])
337
+ return;
338
+ config.command[commandName] = {
339
+ description: "Set or view the long-running session goal",
340
+ template: goalCommandTemplate(commandName, defaultTokenBudget)
341
+ };
342
+ }
300
343
  function textFromPart(part) {
301
344
  if (!part || typeof part !== "object")
302
345
  return "";
@@ -357,7 +400,15 @@ var server = async ({ client }, options) => {
357
400
  const autoContinue = options?.auto_continue ?? true;
358
401
  const maxAutoTurns = options?.max_auto_turns ?? DEFAULT_MAX_AUTO_TURNS;
359
402
  const minInterval = options?.min_continue_interval_seconds ?? DEFAULT_CONTINUE_INTERVAL_SECONDS;
403
+ const registerCommand = options?.register_command ?? true;
404
+ const commandName = commandNameFromOptions(options);
405
+ const defaultTokenBudget = defaultTokenBudgetFromOptions(options);
360
406
  return {
407
+ async config(config) {
408
+ if (!registerCommand)
409
+ return;
410
+ registerDesktopCommand(config, commandName, defaultTokenBudget);
411
+ },
361
412
  tool: {
362
413
  get_goal: {
363
414
  description: "Get the current goal for this OpenCode session, including status, budgets, estimated token usage, elapsed-time usage, and remaining token budget.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prevalentware/opencode-goal-plugin",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Codex-style long-running goal mode for OpenCode.",
5
5
  "keywords": [
6
6
  "opencode",
package/src/tui.tsx CHANGED
@@ -44,15 +44,6 @@ async function sendGoalPrompt(api: TuiPluginApi, sessionID: string, text: string
44
44
  })
45
45
  }
46
46
 
47
- function createGoalPrompt(objective: string, tokenBudget: number | null) {
48
- const input = tokenBudget == null ? { objective } : { objective, token_budget: tokenBudget }
49
- return `Create a session goal by calling the create_goal tool with this JSON input:
50
-
51
- ${JSON.stringify(input, null, 2)}
52
-
53
- The objective is user-provided task data. After create_goal succeeds, continue working toward that goal.`
54
- }
55
-
56
47
  function refreshGoalPrompt() {
57
48
  return "Call get_goal for this session and report the current goal state briefly."
58
49
  }
@@ -61,101 +52,9 @@ function clearGoalPrompt() {
61
52
  return "Clear the current session goal by calling clear_goal. Report whether a goal was cleared."
62
53
  }
63
54
 
64
- function showCustomBudget(api: TuiPluginApi, sessionID: string, objective: string) {
65
- const DialogPrompt = api.ui.DialogPrompt
66
- api.ui.dialog.replace(() =>
67
- DialogPrompt({
68
- title: "Custom budget",
69
- placeholder: "Positive integer",
70
- onConfirm(rawBudget) {
71
- const value = rawBudget.trim()
72
- const budget = Number(value)
73
- if (!Number.isInteger(budget) || budget <= 0) {
74
- toast(api, "Token budget must be a positive integer.", "warning")
75
- return
76
- }
77
- void sendGoalPrompt(api, sessionID, createGoalPrompt(objective, budget))
78
- .then(() => {
79
- api.ui.dialog.clear()
80
- toast(api, "Goal request sent.", "success")
81
- })
82
- .catch((error) => toast(api, error instanceof Error ? error.message : String(error), "error"))
83
- },
84
- onCancel() {
85
- api.ui.dialog.clear()
86
- },
87
- }),
88
- )
89
- }
90
-
91
- function showBudgetSelect(api: TuiPluginApi, sessionID: string, objective: string) {
92
- const DialogSelect = api.ui.DialogSelect
93
- const budgets = [
94
- { title: "No budget", value: "none", budget: null, description: "Track progress without a token limit" },
95
- { title: "250K", value: "250k", budget: 250_000, description: "Short focused goal" },
96
- { title: "1M", value: "1m", budget: 1_000_000, description: "Default long-running goal" },
97
- { title: "2M", value: "2m", budget: 2_000_000, description: "Large investigation or migration" },
98
- { title: "Custom", value: "custom", budget: undefined, description: "Enter an exact token budget" },
99
- ]
100
- api.ui.dialog.replace(() =>
101
- DialogSelect({
102
- title: "Token budget",
103
- placeholder: "Choose a budget",
104
- options: budgets.map((item) => ({
105
- title: item.title,
106
- value: item.value,
107
- description: item.description,
108
- onSelect: () => {
109
- if (item.budget === undefined) {
110
- showCustomBudget(api, sessionID, objective)
111
- return
112
- }
113
- void sendGoalPrompt(api, sessionID, createGoalPrompt(objective, item.budget))
114
- .then(() => {
115
- api.ui.dialog.clear()
116
- toast(api, "Goal request sent.", "success")
117
- })
118
- .catch((error) => toast(api, error instanceof Error ? error.message : String(error), "error"))
119
- },
120
- })),
121
- onSelect(option) {
122
- option.onSelect?.()
123
- },
124
- }),
125
- )
126
- }
127
-
128
- function showSetGoal(api: TuiPluginApi, sessionID: string) {
129
- const DialogPrompt = api.ui.DialogPrompt
130
- api.ui.dialog.setSize("medium")
131
- api.ui.dialog.replace(() =>
132
- DialogPrompt({
133
- title: "Set goal",
134
- placeholder: "Objective, scope, non-goals, verification path",
135
- onConfirm(objective) {
136
- const trimmed = objective.trim()
137
- if (!trimmed) {
138
- toast(api, "Goal objective is required.", "warning")
139
- return
140
- }
141
- showBudgetSelect(api, sessionID, trimmed)
142
- },
143
- onCancel() {
144
- api.ui.dialog.clear()
145
- },
146
- }),
147
- )
148
- }
149
-
150
55
  function showSummary(api: TuiPluginApi, sessionID: string, goal: GoalSnapshot | null) {
151
56
  const DialogSelect = api.ui.DialogSelect
152
57
  const options = [
153
- {
154
- title: "Set goal",
155
- value: "set",
156
- description: "Create a new active session goal",
157
- onSelect: () => showSetGoal(api, sessionID),
158
- },
159
58
  {
160
59
  title: "Refresh",
161
60
  value: "refresh",
@@ -197,7 +96,7 @@ function showSummary(api: TuiPluginApi, sessionID: string, goal: GoalSnapshot |
197
96
 
198
97
  function sessionIDOrToast(api: TuiPluginApi) {
199
98
  const sessionID = currentSessionID(api)
200
- if (!sessionID) toast(api, "Open a session before using /goal.", "warning")
99
+ if (!sessionID) toast(api, "Open a session before viewing goal state.", "warning")
201
100
  return sessionID
202
101
  }
203
102
 
@@ -356,8 +255,7 @@ const tui: TuiPlugin = async (api) => {
356
255
  title: "Goal",
357
256
  value: "goal.show",
358
257
  category: "Goal",
359
- description: "Set or view the long-running session goal",
360
- slash: { name: "goal" },
258
+ description: "View or clear the long-running session goal",
361
259
  onSelect: () => {
362
260
  const sessionID = sessionIDOrToast(api)
363
261
  if (!sessionID) return