opencode-goal-plugin 0.6.4 → 0.6.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/CHANGELOG.md +4 -0
- package/README.md +12 -11
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/goal-plugin.js +21 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.5 — 2026-07-12
|
|
4
|
+
|
|
5
|
+
- Make `/goal sequence` the canonical ordered multi-goal command, retain the previous command and mode spelling as input-only compatibility aliases, correct the public auditor snapshot mode type to `"normal" | "ordered"`, and align README archive and compatibility claims with verified behavior.
|
|
6
|
+
|
|
3
7
|
## 0.6.4 — 2026-07-12
|
|
4
8
|
|
|
5
9
|
- Re-check session status and recent messages after an auto-continue cooldown, pause immediately for human intervention, Plan-agent switches, permission rejection, aborts, and provider errors, and abort an accepted continuation when the user takes control.
|
package/README.md
CHANGED
|
@@ -36,15 +36,16 @@ surface and versioning expectations.
|
|
|
36
36
|
|
|
37
37
|
### OpenCode version compatibility
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Tested against real OpenCode 1.17.15 processes with live provider credentials and no mocked plugin hooks. State, ledger entries, and workspace files were checked independently of terminal or model prose:
|
|
40
40
|
|
|
41
41
|
| OpenCode Version | Provider Tested | `/goal status` | Auto-continue | Evidence-gated completion | Hook Output Display |
|
|
42
42
|
|---|---|---|---|---|---|
|
|
43
|
+
| 1.17.15 | opencode (`deepseek-v4-flash-free`) | ✅ Canonical tool | ✅ Checkpoint + idle continuation | ✅ Structured `goal_complete` claim | ⚠️ Command text routed to model; mutation guard verified |
|
|
43
44
|
| 1.17.15 | opencode-go (`qwen3.7-plus`) | ✅ | ✅ | ✅ Self-corrected after one rejection (bare `[goal:complete]` with no evidence), then completed cleanly | ⚠️ Not displayed |
|
|
44
45
|
| 1.17.15 | opencode-go (`glm-5.2`) | ✅ | ✅ | ✅ Clean `[goal:evidence]` + `[goal:complete]` on the first attempt | ⚠️ Not displayed |
|
|
45
46
|
| 1.17.15 | deepseek (`deepseek-chat`) | ✅ | ✅ | ✅ Clean `[goal:evidence]` + `[goal:complete]` on the first attempt; also verified end-to-end via the [demo](demo/) — autonomously fixed a real bug and reported evidence-backed completion | ⚠️ Not displayed |
|
|
46
47
|
|
|
47
|
-
`/goal status` and auto-continue are graded on **state correctness** (verified directly against
|
|
48
|
+
`/goal status` and auto-continue are graded on **state correctness** (verified directly against persisted state: correct limits, turn/stop accounting, completion state, and file effects), not on terminal rendering. The `deepseek-v4-flash-free` canary suite additionally covers pause/resume across processes, blocker/restart, hard-process recovery, real host compaction, and stale-history clear enforcement. See [`docs/providers.md`](docs/providers.md) for the complete lifecycle matrix and session evidence.
|
|
48
49
|
|
|
49
50
|
**Note:** Hook output display depends on OpenCode version — on 1.17.15, `command.execute.before`'s `output.parts` text is not rendered in the TUI for any provider tested; the raw command argument is instead routed to the model as a normal chat turn (see [Limitations](#limitations)). State mutations always work regardless of display: goal creation, flag parsing, auto-continue, limit enforcement, and evidence-gated completion detection were all verified correct via the persisted state file in every combination above. Re-test against your own OpenCode build before relying on unattended runs, and see [`docs/providers.md`](docs/providers.md) for the full per-model marker-compliance notes.
|
|
50
51
|
|
|
@@ -89,7 +90,7 @@ Add success criteria, constraints / non-goals, and a mode:
|
|
|
89
90
|
/goal ship the release --success "tests pass and changelog updated" --constraints "do not touch the public API" --mode ordered
|
|
90
91
|
```
|
|
91
92
|
|
|
92
|
-
`--success` (alias `--success-criteria`) and `--constraints` (alias `--non-goals`) take quoted text and are injected alongside the objective so the assistant keeps them in view. `--mode` is `normal` (default) or `ordered
|
|
93
|
+
`--success` (alias `--success-criteria`) and `--constraints` (alias `--non-goals`) take quoted text and are injected alongside the objective so the assistant keeps them in view. `--mode` is `normal` (default) or `ordered`; `ordered` tells the assistant to preserve step order inside one objective. To queue distinct objectives that auto-promote one at a time, use `/goal sequence`. Multi-word values must be quoted.
|
|
93
94
|
|
|
94
95
|
Flags accept either `--flag value` or `--flag=value`. If a flag is unknown, missing a value, given a non-positive integer, or (for `--mode`) an unrecognized mode, the plugin rejects the command with a helpful error instead of silently folding the bad flag into the goal text.
|
|
95
96
|
|
|
@@ -125,7 +126,7 @@ Pause without clearing the active goal:
|
|
|
125
126
|
/goal pause
|
|
126
127
|
```
|
|
127
128
|
|
|
128
|
-
Clear the
|
|
129
|
+
Clear all live goals in the current session and discard their saved status:
|
|
129
130
|
|
|
130
131
|
```
|
|
131
132
|
/goal clear
|
|
@@ -155,14 +156,14 @@ A session can hold more than one goal. `/goal <condition>` replaces the focused
|
|
|
155
156
|
/goal focus 1
|
|
156
157
|
```
|
|
157
158
|
|
|
158
|
-
`/goal list` shows
|
|
159
|
+
`/goal list` shows numbered live goals (focused and backgrounded) plus achieved goals retained in the per-session archive. `/goal clear` intentionally removes live goals and saved status from these views; its terminal ledger entries remain available for crash-safe recovery decisions. `/goal focus <number>` switches the active goal, backgrounding the previous one. Focus is tracked per session and survives a restart.
|
|
159
160
|
|
|
160
|
-
#### Ordered
|
|
161
|
+
#### Ordered sequences
|
|
161
162
|
|
|
162
|
-
`/goal
|
|
163
|
+
`/goal sequence` sets up a strict execution queue: separate objectives with `;` or newlines, and the plugin runs them one at a time, focusing the next as soon as the current one completes.
|
|
163
164
|
|
|
164
165
|
```
|
|
165
|
-
/goal
|
|
166
|
+
/goal sequence build the parser; write the tests; ship the release
|
|
166
167
|
```
|
|
167
168
|
|
|
168
169
|
The first goal is focused and the rest are queued. `/goal list` marks the session as ordered. Auto-promotion stops when the sequence is exhausted; `/goal clear` ends the sequence.
|
|
@@ -187,7 +188,7 @@ With success criteria, constraints, and a token budget shorthand:
|
|
|
187
188
|
An ordered sequence, run as a strict pipeline:
|
|
188
189
|
|
|
189
190
|
```
|
|
190
|
-
/goal
|
|
191
|
+
/goal sequence build the parser; write the tests; ship the release
|
|
191
192
|
```
|
|
192
193
|
|
|
193
194
|
## How it works
|
|
@@ -272,7 +273,7 @@ Override any limit for a single goal:
|
|
|
272
273
|
| `--no-progress-turns <n>` | Consecutive stalled low-output turns before pausing |
|
|
273
274
|
| `--success <text>` | Success criteria that define when the goal is satisfied (quote multi-word text) |
|
|
274
275
|
| `--constraints <text>` | Constraints / non-goals to respect (alias `--non-goals`) |
|
|
275
|
-
| `--mode <normal\|ordered>` |
|
|
276
|
+
| `--mode <normal\|ordered>` | Prompt mode for one goal; `ordered` preserves step order inside its objective |
|
|
276
277
|
| `--no-tool-turns <n>` | Consecutive tool-free continuation turns before pausing |
|
|
277
278
|
|
|
278
279
|
Examples:
|
|
@@ -343,7 +344,7 @@ Registered tools:
|
|
|
343
344
|
|
|
344
345
|
`goal_set` and `set_goal` are explicitly constrained to user-requested goals. `goal_complete` accepts a structured claim: a required non-empty `summary`, plus optional criterion/evidence pairs, checks (`passed`, `failed`, or `not-run`), changed files, and known limitations. Failed checks and empty criterion evidence are rejected before archival; accepted claims are serialized deterministically for the configured completion auditor. The legacy `update_goal` tool retains its string `evidence` field for compatibility.
|
|
345
346
|
|
|
346
|
-
These operate on the same per-session multi-goal state as the command path: a tool-set goal persists, shows up in `/goal list`, and is driven by the idle auto-continue; completing a goal in an ordered
|
|
347
|
+
These operate on the same per-session multi-goal state as the command path: a tool-set goal persists, shows up in `/goal list`, and is driven by the idle auto-continue; completing a goal in an ordered sequence auto-promotes the next.
|
|
347
348
|
|
|
348
349
|
> Integration note: the tool execute-context shape (`ctx.sessionID`) and the `tool.schema` surface follow the OpenCode plugin docs. The tool **logic** is unit-tested independently, but the live registration should be confirmed against a real OpenCode run (see the smoke-test checklist).
|
|
349
350
|
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
package/src/goal-plugin.js
CHANGED
|
@@ -74,7 +74,7 @@ const DEFAULT_OPTIONS = {
|
|
|
74
74
|
// handler drives and that the system-prompt transform injects. `sessionGoals`
|
|
75
75
|
// is the full registry of live goals per session (focused + backgrounded);
|
|
76
76
|
// the focused goal is the same object reference held in both. `sessionArchive`
|
|
77
|
-
// keeps a capped list of
|
|
77
|
+
// keeps a capped list of achieved goals so completed work stays readable.
|
|
78
78
|
function createRuntimeState() {
|
|
79
79
|
return {
|
|
80
80
|
goalStates: new Map(),
|
|
@@ -129,7 +129,7 @@ function runtimeCollection(name) {
|
|
|
129
129
|
const goalStates = runtimeCollection("goalStates")
|
|
130
130
|
const sessionGoals = runtimeCollection("sessionGoals")
|
|
131
131
|
const sessionArchive = runtimeCollection("sessionArchive")
|
|
132
|
-
// Sessions running an ordered
|
|
132
|
+
// Sessions running an ordered sequence: when the focused goal
|
|
133
133
|
// completes, the next live goal (in creation order) is auto-promoted to focus
|
|
134
134
|
// so the sequence advances on its own.
|
|
135
135
|
const sessionOrdered = runtimeCollection("sessionOrdered")
|
|
@@ -147,6 +147,9 @@ const seenOutputTokens = runtimeCollection("seenOutputTokens")
|
|
|
147
147
|
const activeContinues = runtimeCollection("activeContinues")
|
|
148
148
|
const CLEAR_COMMANDS = new Set(["clear", "stop", "off", "reset", "none", "cancel"])
|
|
149
149
|
const PAUSE_COMMANDS = new Set(["pause"])
|
|
150
|
+
// `sequence` is canonical. The former public spelling remains accepted at
|
|
151
|
+
// the parser boundary so existing scripts do not break.
|
|
152
|
+
const SEQUENCE_COMMANDS = ["sequence", "sisyphus"]
|
|
150
153
|
const READ_ONLY_COMMAND_TOOLS = new Set(["goal_status", "get_goal", "get_goal_history", "read", "glob", "grep"])
|
|
151
154
|
const GOAL_FLAG_SPECS = {
|
|
152
155
|
"--max-turns": {
|
|
@@ -211,8 +214,8 @@ function messageHasToolCall(message) {
|
|
|
211
214
|
|
|
212
215
|
const GOAL_MODES = new Set(["normal", "ordered"])
|
|
213
216
|
|
|
214
|
-
// Goal mode: normal vs ordered
|
|
215
|
-
//
|
|
217
|
+
// Goal mode: normal vs ordered. The former public spelling remains accepted
|
|
218
|
+
// as an input alias, while stored state and output always use `ordered`.
|
|
216
219
|
// Returns the canonical mode or null when unrecognized.
|
|
217
220
|
function normalizeMode(value) {
|
|
218
221
|
const normalized = String(value || "").trim().toLowerCase()
|
|
@@ -727,7 +730,7 @@ function archiveSessionResult(sessionID, result) {
|
|
|
727
730
|
sessionArchive.set(sessionID, list.slice(-MAX_ARCHIVED_PER_SESSION))
|
|
728
731
|
}
|
|
729
732
|
|
|
730
|
-
// Advance an ordered
|
|
733
|
+
// Advance an ordered sequence: focus the next live goal in creation
|
|
731
734
|
// order, clearing any backgrounded state so the idle handler drives it. Returns
|
|
732
735
|
// the promoted goal, or null when the sequence is exhausted (which also clears
|
|
733
736
|
// the session's ordered flag).
|
|
@@ -743,7 +746,7 @@ function promoteNextOrderedGoal(sessionID) {
|
|
|
743
746
|
resumeGoalClock(next)
|
|
744
747
|
next.skipNextTerminalCheck = true
|
|
745
748
|
next.lastStatus = "Promoted as the next ordered goal."
|
|
746
|
-
pushHistory(next, "focused", "Auto-promoted as the next goal in the ordered
|
|
749
|
+
pushHistory(next, "focused", "Auto-promoted as the next goal in the ordered sequence.")
|
|
747
750
|
focusGoal(sessionID, next)
|
|
748
751
|
return next
|
|
749
752
|
}
|
|
@@ -2463,7 +2466,7 @@ function buildAgentToolHandlers({ defaultGoalOptions, persist, persistTerminalSt
|
|
|
2463
2466
|
const ordered = sessionOrdered.has(sessionID)
|
|
2464
2467
|
rememberGoalResult(sessionID, goal, "achieved", "", evidence)
|
|
2465
2468
|
cleanupGoal(sessionID)
|
|
2466
|
-
// Advance an ordered
|
|
2469
|
+
// Advance an ordered sequence just like the marker path does.
|
|
2467
2470
|
if (ordered) promoteNextOrderedGoal(sessionID)
|
|
2468
2471
|
const durable = await persistFinal("completion", ledgerDurable)
|
|
2469
2472
|
if (durable === false) {
|
|
@@ -2707,7 +2710,7 @@ function formatGoalList(sessionID, commandName = "goal") {
|
|
|
2707
2710
|
|
|
2708
2711
|
const lines = []
|
|
2709
2712
|
if (goals.length) {
|
|
2710
|
-
lines.push(`Goals (${goals.length})${sessionOrdered.has(sessionID) ? " — ordered
|
|
2713
|
+
lines.push(`Goals (${goals.length})${sessionOrdered.has(sessionID) ? " — ordered sequence" : ""}:`)
|
|
2711
2714
|
goals.forEach((goal, index) => {
|
|
2712
2715
|
const marker = goal.goalId === focusedId ? "focused" : goal.stopped ? "background" : "idle"
|
|
2713
2716
|
const state = goal.stopped && goal.goalId !== focusedId ? ` — ${goal.stopReason || "stopped"}` : ""
|
|
@@ -3347,8 +3350,11 @@ async function createGoalPlugin({ client, directory } = {}, pluginOptions = {})
|
|
|
3347
3350
|
return
|
|
3348
3351
|
}
|
|
3349
3352
|
|
|
3350
|
-
|
|
3351
|
-
|
|
3353
|
+
const sequenceCommand = SEQUENCE_COMMANDS.find(
|
|
3354
|
+
(command) => args.toLowerCase() === command || args.toLowerCase().startsWith(`${command} `),
|
|
3355
|
+
)
|
|
3356
|
+
if (sequenceCommand) {
|
|
3357
|
+
const rest = args.slice(sequenceCommand.length).trim()
|
|
3352
3358
|
const objectives = rest
|
|
3353
3359
|
.split(/\n|;/)
|
|
3354
3360
|
.map((part) => stripWrappingQuotes(part.trim()))
|
|
@@ -3356,7 +3362,7 @@ async function createGoalPlugin({ client, directory } = {}, pluginOptions = {})
|
|
|
3356
3362
|
if (!objectives.length) {
|
|
3357
3363
|
output.parts = [
|
|
3358
3364
|
makeTextPart(
|
|
3359
|
-
`No objectives provided. Use \`/${commandName}
|
|
3365
|
+
`No objectives provided. Use \`/${commandName} sequence <objective 1>; <objective 2>; …\` (separate with \`;\` or newlines).`,
|
|
3360
3366
|
),
|
|
3361
3367
|
]
|
|
3362
3368
|
return
|
|
@@ -3400,7 +3406,7 @@ async function createGoalPlugin({ client, directory } = {}, pluginOptions = {})
|
|
|
3400
3406
|
pushHistory(
|
|
3401
3407
|
created,
|
|
3402
3408
|
"set",
|
|
3403
|
-
`Ordered goal ${index + 1}/${objectives.length} created
|
|
3409
|
+
`Ordered goal ${index + 1}/${objectives.length} created.`,
|
|
3404
3410
|
)
|
|
3405
3411
|
registerSessionGoal(created)
|
|
3406
3412
|
})
|
|
@@ -3410,7 +3416,7 @@ async function createGoalPlugin({ client, directory } = {}, pluginOptions = {})
|
|
|
3410
3416
|
output.parts = [
|
|
3411
3417
|
makeTextPart(
|
|
3412
3418
|
[
|
|
3413
|
-
`Started an ordered sequence of ${objectives.length} goal(s)
|
|
3419
|
+
`Started an ordered sequence of ${objectives.length} goal(s):`,
|
|
3414
3420
|
...objectives.map((objective, index) => `${index + 1}. ${objective}`),
|
|
3415
3421
|
"",
|
|
3416
3422
|
`Focused goal 1: ${firstGoal.condition}`,
|
|
@@ -3560,7 +3566,7 @@ async function createGoalPlugin({ client, directory } = {}, pluginOptions = {})
|
|
|
3560
3566
|
// Replace the focused goal (cleanupGoal discards it); backgrounded goals
|
|
3561
3567
|
// for this session are preserved. Use `/goal add` to keep the current
|
|
3562
3568
|
// goal and add another. Clear any ordered-sequence flag so the new
|
|
3563
|
-
// standalone goal does not trigger
|
|
3569
|
+
// standalone goal does not trigger auto-promotion of the old sequence
|
|
3564
3570
|
// goals that may still be in the registry (matches the agent setGoal path).
|
|
3565
3571
|
sessionOrdered.delete(sessionID)
|
|
3566
3572
|
cleanupGoal(sessionID)
|
|
@@ -3852,7 +3858,7 @@ async function createGoalPlugin({ client, directory } = {}, pluginOptions = {})
|
|
|
3852
3858
|
const ordered = sessionOrdered.has(sessionID)
|
|
3853
3859
|
rememberGoalResult(sessionID, activeGoalAfterMessages, "achieved", "", evidence)
|
|
3854
3860
|
cleanupGoal(sessionID)
|
|
3855
|
-
// Ordered
|
|
3861
|
+
// Ordered sequence: auto-promote the next goal so the
|
|
3856
3862
|
// session keeps working through the sequence without manual /goal focus.
|
|
3857
3863
|
if (ordered) {
|
|
3858
3864
|
promoteNextOrderedGoal(sessionID)
|