@prevalentware/opencode-goal-plugin 0.1.4 → 0.1.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.
- package/README.md +15 -9
- package/dist/server.js +54 -0
- package/package.json +1 -1
- package/src/tui.tsx +2 -104
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
|
|
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": 1000000
|
|
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`: `1000000`
|
|
75
79
|
|
|
76
80
|
## Goal Workflow
|
|
77
81
|
|
|
78
|
-
Use `/goal
|
|
82
|
+
Use `/goal <objective>` in a fresh OpenCode chat to create a long-running goal:
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
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>` creates the goal with `token_budget: 1000000`. To omit the budget, set `default_token_budget` to `null`. To use a different fixed budget without prompting the user, set `default_token_budget` to another positive integer in `opencode.json`.
|
|
85
91
|
|
|
86
|
-
When
|
|
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,52 @@ 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
|
+
var DEFAULT_TOKEN_BUDGET = 1e6;
|
|
302
|
+
function defaultTokenBudgetFromOptions(options) {
|
|
303
|
+
const budget = options?.default_token_budget;
|
|
304
|
+
if (budget === null)
|
|
305
|
+
return null;
|
|
306
|
+
if (budget === undefined)
|
|
307
|
+
return DEFAULT_TOKEN_BUDGET;
|
|
308
|
+
return Number.isInteger(budget) && budget > 0 ? budget : null;
|
|
309
|
+
}
|
|
310
|
+
function goalCommandTemplate(commandName, defaultTokenBudget) {
|
|
311
|
+
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.`;
|
|
312
|
+
return `OpenCode goal mode command "/${commandName}" was invoked.
|
|
313
|
+
|
|
314
|
+
Arguments:
|
|
315
|
+
<goal_command_arguments>
|
|
316
|
+
$ARGUMENTS
|
|
317
|
+
</goal_command_arguments>
|
|
318
|
+
|
|
319
|
+
Use the goal tools to handle this command:
|
|
320
|
+
|
|
321
|
+
- If the arguments are empty, call get_goal and briefly report the current goal state.
|
|
322
|
+
- If the arguments are "status", "show", or "current", call get_goal and briefly report the current goal state.
|
|
323
|
+
- If the arguments are "clear", call clear_goal and report whether a goal was cleared.
|
|
324
|
+
- 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.
|
|
325
|
+
- 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.
|
|
326
|
+
- Otherwise, create a new goal with create_goal. Use the full arguments as the objective. ${defaultBudgetInstruction}
|
|
327
|
+
- 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".
|
|
328
|
+
|
|
329
|
+
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.`;
|
|
330
|
+
}
|
|
331
|
+
function commandNameFromOptions(options) {
|
|
332
|
+
const name = options?.command_name?.trim() || DEFAULT_COMMAND_NAME;
|
|
333
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name))
|
|
334
|
+
return DEFAULT_COMMAND_NAME;
|
|
335
|
+
return name;
|
|
336
|
+
}
|
|
337
|
+
function registerDesktopCommand(config, commandName, defaultTokenBudget) {
|
|
338
|
+
config.command ??= {};
|
|
339
|
+
if (config.command[commandName])
|
|
340
|
+
return;
|
|
341
|
+
config.command[commandName] = {
|
|
342
|
+
description: "Set or view the long-running session goal",
|
|
343
|
+
template: goalCommandTemplate(commandName, defaultTokenBudget)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
300
346
|
function textFromPart(part) {
|
|
301
347
|
if (!part || typeof part !== "object")
|
|
302
348
|
return "";
|
|
@@ -357,7 +403,15 @@ var server = async ({ client }, options) => {
|
|
|
357
403
|
const autoContinue = options?.auto_continue ?? true;
|
|
358
404
|
const maxAutoTurns = options?.max_auto_turns ?? DEFAULT_MAX_AUTO_TURNS;
|
|
359
405
|
const minInterval = options?.min_continue_interval_seconds ?? DEFAULT_CONTINUE_INTERVAL_SECONDS;
|
|
406
|
+
const registerCommand = options?.register_command ?? true;
|
|
407
|
+
const commandName = commandNameFromOptions(options);
|
|
408
|
+
const defaultTokenBudget = defaultTokenBudgetFromOptions(options);
|
|
360
409
|
return {
|
|
410
|
+
async config(config) {
|
|
411
|
+
if (!registerCommand)
|
|
412
|
+
return;
|
|
413
|
+
registerDesktopCommand(config, commandName, defaultTokenBudget);
|
|
414
|
+
},
|
|
361
415
|
tool: {
|
|
362
416
|
get_goal: {
|
|
363
417
|
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
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
|
|
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: "
|
|
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
|