opencode-goal-plugin 0.5.0 → 0.6.1
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 +29 -4
- package/CONTRIBUTING.md +8 -7
- package/README.md +67 -39
- package/SECURITY.md +29 -1
- package/demo/README.md +86 -0
- package/demo/opencode.json +11 -0
- package/demo/package.json +10 -0
- package/demo/src/math.js +3 -0
- package/demo/test/math.test.js +11 -0
- package/docs/providers.md +84 -0
- package/index.d.ts +53 -5
- package/package.json +19 -6
- package/scripts/behavior-benchmark.mjs +272 -0
- package/scripts/packed-host-contract.mjs +160 -0
- package/scripts/verify.mjs +4 -2
- package/src/completion-claim.js +127 -0
- package/src/goal-plugin.js +1166 -304
- package/src/goal-tool-result.js +18 -0
- package/src/native-agent-config.js +73 -0
- package/src/opencode-session-api.js +110 -0
- package/src/persistence-lease.js +94 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.6.1 — 2026-07-10
|
|
6
|
+
|
|
7
|
+
- Default-deny verifier tools except read/glob/grep, and fail closed unless the owned verifier agent registers successfully.
|
|
8
|
+
- Require exact final-line audit verdicts and adjacent bounded completion evidence to prevent quoted-marker false approvals.
|
|
9
|
+
- Prevent mutating SDK shape retries, validate child-session ancestry, and make auditor timeout independent of a hanging abort.
|
|
10
|
+
- Move context-budget reset to the successful `session.compacted` event and prevent ordered goals from reusing a prior goal's completion message.
|
|
11
|
+
- Drain accepted persistence writes before disposal, suppress late disposed-instance writes, and re-check goal identity after asynchronous audits/announcements.
|
|
12
|
+
- Harden persistence recovery with bounded hostile-state parsing, cross-session-safe ledger identities, multi-goal snapshots, archive restoration, corrupt-state quarantine, stale malformed-lock recovery, symlink-safe ledger appends, and exclusive one-project legacy migration with preserved backups.
|
|
13
|
+
- Fail closed when neither terminal state nor ledger can be persisted; keep the goal paused instead of claiming archival.
|
|
14
|
+
- Preserve paused time for queued/backgrounded goals, expose an opt-out for the tool-free heuristic, honor host `tokens.total`, and improve multi-step usage/cost availability accounting.
|
|
15
|
+
- Add conditional TypeScript export mappings, an OpenCode engine range, honest auditor/visibility documentation, and cache-safe near-limit continuation warnings.
|
|
16
|
+
|
|
17
|
+
## 0.6.0 — 2026-07-10
|
|
18
|
+
|
|
19
|
+
> Adds canonical structured goal tools, native goal and read-only verifier agents, fail-closed completion verification, safer OpenCode SDK compatibility, bounded persistence, and stronger lifecycle isolation. Existing command workflows and legacy tool aliases remain supported.
|
|
20
|
+
|
|
21
|
+
- Add canonical typed goal tools alongside backward-compatible aliases and structured completion claims.
|
|
22
|
+
- Register collision-safe goal and verifier agents; completion auditing now requires an owned, distinct verifier identity with edit and shell permissions denied.
|
|
23
|
+
- Fail closed on verifier errors/timeouts and abort timed-out child sessions when the host supports cancellation.
|
|
24
|
+
- Isolate runtime state per workspace, suppress stale asynchronous continuations, handle abort/dispose races, and coalesce duplicate idle events.
|
|
25
|
+
- Add an exclusive persistence lease so two OpenCode processes cannot silently overwrite one workspace state file.
|
|
26
|
+
- Bound goal-definition inputs and rotate the sensitive lifecycle ledger using configurable size/retention ceilings.
|
|
27
|
+
- Add normalized usage diagnostics, compact continuation prompts, a 100-point behavior benchmark, and packed-tarball host-contract verification.
|
|
28
|
+
- Include provider documentation and the reproducible demo in the npm package; declare all public hooks.
|
|
29
|
+
|
|
5
30
|
## 0.5.0 — 2026-07-08
|
|
6
31
|
|
|
7
32
|
- Replace the single-line "Compatibility snapshot" in the README with an OpenCode version compatibility table, manually verified via `tmux` + the OpenCode TUI against the persisted state file for each provider/model combination.
|
|
@@ -102,7 +127,7 @@
|
|
|
102
127
|
|
|
103
128
|
## 0.3.0 — 2026-06-14
|
|
104
129
|
|
|
105
|
-
> A large feature release. Stronger completion integrity (evidence gate, optional auditor, visible audit messages), durable lifecycle ledger with state reconstruction, multiple goals per session with focus and ordered sisyphus sequences, richer goal schema, more auto-continue guardrails, project-local state with migration, a deterministic compaction summary, and
|
|
130
|
+
> A large feature release. Stronger completion integrity (evidence gate, optional auditor, visible audit messages), durable lifecycle ledger with state reconstruction, multiple goals per session with focus and ordered sisyphus sequences, richer goal schema, more auto-continue guardrails, project-local state with migration, a deterministic compaction summary, and release-workflow groundwork. All changes are additive and backward-compatible; older state files load unchanged.
|
|
106
131
|
|
|
107
132
|
### Completion integrity & audit
|
|
108
133
|
|
|
@@ -134,8 +159,8 @@
|
|
|
134
159
|
### Storage, tools & packaging
|
|
135
160
|
|
|
136
161
|
- **Default goal state to a project-local path, with an env override and migration fallbacks.** State resolves as `stateFilePath` option → `OPENCODE_GOAL_STATE_PATH` env var → project-local `<cwd>/.opencode/goals/state.json` (previously `~/.opencode-goal-plugin/state.json`). When the default path is empty, the plugin migrates forward on first load from the legacy home path and the XDG path, then writes project-local. Explicit option/env paths are literal with no fallback; a present-but-corrupt primary is preserved. New `resolveStateFilePath` / `xdgStateFilePath` / `legacyStateFilePaths` helpers. Home-based fallback paths resolve from an injectable `env.HOME` (falling back to `os.homedir()`), making path resolution deterministic across platforms — `os.homedir()` ignores `$HOME` on macOS. Implements megalist items 6.1 and 6.2.
|
|
137
|
-
- _**Correction (2026-06-21):** an earlier version of this entry claimed agent-facing goal tools shipped in 0.3.0. They did not — the work was on an unmerged branch (`wr/agent-tools`) and was never included in the 0.3.0 release. The feature now actually ships; see the **
|
|
138
|
-
- **
|
|
162
|
+
- _**Correction (2026-06-21):** an earlier version of this entry claimed agent-facing goal tools shipped in 0.3.0. They did not — the work was on an unmerged branch (`wr/agent-tools`) and was never included in the 0.3.0 release. The feature now actually ships; see the **0.6.0** section above. Megalist items 7.1 and 7.2._
|
|
163
|
+
- **Release automation note.** Development included a proposed npm Trusted Publishing workflow, but `.github/workflows/publish.yml` was not part of the final release history and is not present in the current repository. Releases therefore remain manual unless a separately reviewed publishing workflow is added. No package is published solely by the CI workflow documented in this repository.
|
|
139
164
|
|
|
140
165
|
## 0.2.0 — 2026-06-14
|
|
141
166
|
|
|
@@ -250,4 +275,4 @@
|
|
|
250
275
|
|
|
251
276
|
## 0.1.0
|
|
252
277
|
|
|
253
|
-
- Initial experimental marker-based `/goal` plugin.
|
|
278
|
+
- Initial experimental marker-based `/goal` plugin.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -16,8 +16,10 @@ Run the local checks before submitting changes:
|
|
|
16
16
|
npm test
|
|
17
17
|
npm run test:coverage
|
|
18
18
|
npm run smoke
|
|
19
|
+
npm run smoke:packed-host
|
|
20
|
+
npm run benchmark:behavior
|
|
21
|
+
npm run verify
|
|
19
22
|
npm run check
|
|
20
|
-
npm run smoke
|
|
21
23
|
npm run pack:check
|
|
22
24
|
```
|
|
23
25
|
|
|
@@ -28,11 +30,11 @@ For behavior changes, add or update tests in `test/goal-plugin.test.js`.
|
|
|
28
30
|
This plugin depends on OpenCode plugin hooks, including experimental hooks. When changing hook usage, command behavior, or system-prompt transforms:
|
|
29
31
|
|
|
30
32
|
1. Check the current OpenCode plugin and command documentation.
|
|
31
|
-
2. Run `npm run smoke` to verify the
|
|
33
|
+
2. Run `npm run smoke` and `npm run smoke:packed-host` to verify the source and installed-tarball host contracts.
|
|
32
34
|
3. Test against a real OpenCode install when possible.
|
|
33
35
|
4. Update the README compatibility snapshot if the tested surface changes.
|
|
34
36
|
|
|
35
|
-
`npm run smoke` verifies the package export path and `/goal` command hook without invoking a model.
|
|
37
|
+
`npm run smoke` verifies the package export path and `/goal` command hook without invoking a model. `npm run smoke:packed-host` installs the packed artifact in an isolated directory and checks the public hook/tool contract. `npm run benchmark:behavior` covers deterministic autonomy and token-efficiency scenarios. None replaces a real OpenCode smoke test after hook, SDK, or command behavior changes.
|
|
36
38
|
|
|
37
39
|
## Release checklist
|
|
38
40
|
|
|
@@ -42,15 +44,14 @@ Before publishing or tagging a release:
|
|
|
42
44
|
- run `npm test`
|
|
43
45
|
- run `npm run test:coverage`
|
|
44
46
|
- run `npm run smoke`
|
|
47
|
+
- run `npm run smoke:packed-host`
|
|
48
|
+
- run `npm run benchmark:behavior`
|
|
49
|
+
- run `npm run verify`
|
|
45
50
|
- run `npm run check`
|
|
46
51
|
- run `npm run pack:check`
|
|
47
52
|
- perform at least one manual OpenCode smoke test if hook behavior changed
|
|
48
53
|
- refresh compatibility notes if the tested OpenCode surface changed
|
|
49
54
|
|
|
50
|
-
`npm run smoke` verifies the published package entrypoint and `/goal` command hook without
|
|
51
|
-
invoking a model. It does not replace a manual OpenCode smoke test after hook or command
|
|
52
|
-
behavior changes.
|
|
53
|
-
|
|
54
55
|
## Pull Requests
|
|
55
56
|
|
|
56
57
|
Keep pull requests focused. Include:
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/opencode-goal-plugin)
|
|
4
4
|
[](https://www.npmjs.com/package/opencode-goal-plugin)
|
|
5
5
|
[](https://github.com/willytop8/OpenCode-goal-plugin/actions/workflows/ci.yml)
|
|
6
|
-
[](test/)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
9
|
An experimental session-scoped `/goal` command for [OpenCode](https://opencode.ai/).
|
|
@@ -12,23 +12,15 @@ Set a goal and the plugin keeps it in context, auto-continues the session whenev
|
|
|
12
12
|
|
|
13
13
|
Compatibility: this plugin relies on experimental OpenCode hooks. Re-test against the exact OpenCode build and provider/backend stack you plan to use for unattended work.
|
|
14
14
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
| Goal history | ✅ | ❌ | ✅ `/goal history` |
|
|
25
|
-
| Goal persistence | ❌ | ❌ | ✅ Survives restart, ledger-backed |
|
|
26
|
-
| Multiple concurrent goals | ❌ | ❌ | ✅ `/goal add` / `/goal focus` |
|
|
27
|
-
| Ordered goal sequences | ❌ | ❌ | ✅ `/goal sisyphus` |
|
|
28
|
-
| Evidence-gated completion | ❌ | ❌ | ✅ `[goal:evidence]` required |
|
|
29
|
-
| Independent completion audit | ✅ | ❌ | ✅ Optional child-session auditor |
|
|
30
|
-
| Budget wrap-up prompts | ❌ | ❌ | ✅ 80% threshold |
|
|
31
|
-
| Open source | ❌ | ❌ | ✅ MIT |
|
|
15
|
+
## What it provides
|
|
16
|
+
|
|
17
|
+
- Session-scoped goals that remain visible across turns and compaction.
|
|
18
|
+
- Guarded auto-continuation with turn, duration, token, no-progress, and no-tool-call limits.
|
|
19
|
+
- Project-local restart recovery backed by persisted state and a bounded lifecycle ledger.
|
|
20
|
+
- Evidence-gated completion with an optional independent, fail-closed verifier.
|
|
21
|
+
- Canonical agent tools, collision-safe goal/verifier agents, multiple goals, and ordered goal sequences.
|
|
22
|
+
|
|
23
|
+
This project is independently implemented for OpenCode. Product names used elsewhere identify their respective owners; no feature-parity or endorsement claim is implied.
|
|
32
24
|
|
|
33
25
|
## Compatibility snapshot
|
|
34
26
|
|
|
@@ -137,6 +129,18 @@ Clear the active goal:
|
|
|
137
129
|
|
|
138
130
|
`/goal stop`, `/goal off`, `/goal reset`, `/goal none`, and `/goal cancel` are aliases for `/goal clear`.
|
|
139
131
|
|
|
132
|
+
### Session forks and child sessions
|
|
133
|
+
|
|
134
|
+
Goals are scoped to the session where they were created. A child session or a
|
|
135
|
+
fork does not automatically inherit its parent's active goal; set a goal in the
|
|
136
|
+
new session when you want it to continue independently.
|
|
137
|
+
|
|
138
|
+
This isolation is intentional. OpenCode currently includes `parentID` for
|
|
139
|
+
ordinary child sessions but does not expose the source session in the
|
|
140
|
+
`session.created` event for forks. Inferring ancestry from a mutable title such
|
|
141
|
+
as `(fork #1)` could attach a goal to the wrong session. Automatic inheritance
|
|
142
|
+
can be added once the host exposes an explicit fork relationship.
|
|
143
|
+
|
|
140
144
|
### Multiple goals
|
|
141
145
|
|
|
142
146
|
A session can hold more than one goal. `/goal <condition>` replaces the focused goal, while `/goal add <condition>` keeps the current goal (backgrounding it) and focuses a new one. Only the **focused** goal is auto-continued; backgrounded goals are paused until you focus them.
|
|
@@ -186,10 +190,8 @@ An ordered sequence, run as a strict pipeline:
|
|
|
186
190
|
|
|
187
191
|
1. When you set a goal, the plugin stores it in session memory and injects it into the system prompt so the assistant keeps it in view on every turn.
|
|
188
192
|
2. Each time the session goes idle, the plugin sends a continuation prompt containing the goal, the remaining budget, and a completion audit asking the assistant to verify the current state before declaring done.
|
|
189
|
-
3. The plugin stops auto-continuing when the assistant ends a response with `[goal:complete]` or `[goal:blocked]`, or when a safety limit is reached.
|
|
190
|
-
4. If OpenCode compacts the session, the plugin injects a deterministic summary into the compaction context so the goal survives the compaction and the assistant keeps the thread. The summary — objective, status, budget usage, recent checkpoints, and recent lifecycle events — is reconstructed from the plugin's persisted goal record rather than from chat memory, so it is stable and reproducible. While a goal is active, the plugin also disables OpenCode's generic post-compaction auto-continue so it does not race the plugin's own continuation.
|
|
191
193
|
3. The plugin stops auto-continuing when the assistant ends a response with a substantiated `[goal:complete]` or `[goal:blocked]`, or when a safety limit is reached. A `[goal:complete]` is only honored when it is preceded by a `[goal:evidence]` line; a `[goal:blocked]` is only honored when a concrete blocker is stated. Unsubstantiated claims are rejected and the plugin re-prompts for the missing evidence or blocker.
|
|
192
|
-
4. If OpenCode compacts the session, the plugin injects
|
|
194
|
+
4. If OpenCode compacts the session, the plugin injects a deterministic summary into the compaction context so the goal survives the compaction and the assistant keeps the thread. The summary — objective, status, budget usage, recent checkpoints, and recent lifecycle events — is reconstructed from the plugin's persisted goal record rather than from chat memory, so it is stable and reproducible. While a goal is active, the plugin also disables OpenCode's generic post-compaction auto-continue so it does not race the plugin's own continuation.
|
|
193
195
|
5. If you send a message of your own while the goal is running, the plugin treats it as the latest instruction and pauses auto-continue so it does not talk over you. The plugin's own continuation prompts are ignored for this check (they are not "your" messages). Run `/goal resume` to hand control back to the goal loop.
|
|
194
196
|
|
|
195
197
|
## Completion markers
|
|
@@ -205,7 +207,7 @@ The deploy step needs a production API token I don't have.
|
|
|
205
207
|
[goal:blocked]
|
|
206
208
|
```
|
|
207
209
|
|
|
208
|
-
`[goal:complete]` — goal is satisfied. It is **only honored when the
|
|
210
|
+
`[goal:complete]` — goal is satisfied. It is **only honored when the immediately adjacent evidence line begins with `[goal:evidence]` and contains a non-empty summary** of what was verified (commands run and their results, files checked). The historical two-line form (`[goal:evidence]`, then one evidence line) is also accepted. A stale or non-adjacent evidence marker is rejected. The accepted evidence is shown in `/goal status` after completion.
|
|
209
211
|
`[goal:blocked]` — the assistant needs input from you. The line immediately before the marker must explain the specific blocker; `/goal status` shows it while the goal remains in memory. A `[goal:blocked]` with no concrete blocker is rejected and the plugin keeps working.
|
|
210
212
|
|
|
211
213
|
Markers must appear on their own final line. The bracketed form is canonical, but the plugin also accepts bare `goal:complete`, `goal:blocked`, and `goal:evidence` lines because some models omit brackets. Natural-language phrases like "goal complete" are intentionally ignored.
|
|
@@ -222,7 +224,7 @@ Markers must appear on their own final line. The bracketed form is canonical, bu
|
|
|
222
224
|
| Budget wrap-up threshold | 80% of context token budget |
|
|
223
225
|
| Auto-continue failure pause | 3 consecutive prompt failures |
|
|
224
226
|
|
|
225
|
-
**Effective turn count.** Each LLM turn on a real task typically takes 30–90 seconds.
|
|
227
|
+
**Effective turn count.** Each LLM turn on a real task typically takes 30–90 seconds. The default 10 auto-continues is normally the binding brake before the 15-minute window; raise `--max-turns` and/or `--max-minutes` deliberately for longer work.
|
|
226
228
|
|
|
227
229
|
**Token budget.** The plugin tracks the session's context window size (`input + output + reasoning` tokens on the latest message). This matches the token count that OpenCode displays, so the numbers should be consistent. When the context window reaches the `--max-tokens` limit, the plugin sends a wrap-up prompt and stops. In high-context sessions (large codebases, long conversation history), the context can grow quickly — treat the budget as a safety brake.
|
|
228
230
|
|
|
@@ -240,7 +242,7 @@ The state-file location is resolved with this precedence:
|
|
|
240
242
|
2. the `OPENCODE_GOAL_STATE_PATH` environment variable, if set;
|
|
241
243
|
3. the project-local default `<cwd>/.opencode/goals/state.json`.
|
|
242
244
|
|
|
243
|
-
When the default path has no state yet, the plugin migrates forward from older locations on first load: the legacy `~/.opencode-goal-plugin/state.json` and the XDG path `${XDG_STATE_HOME:-~/.local/state}/opencode-goal-plugin/state.json`. An explicit `stateFilePath` or `OPENCODE_GOAL_STATE_PATH` is used literally with no migration fallback.
|
|
245
|
+
When the default path has no state yet, the plugin migrates forward from older locations on first load: the legacy `~/.opencode-goal-plugin/state.json` and the XDG path `${XDG_STATE_HOME:-~/.local/state}/opencode-goal-plugin/state.json`. Migration is exclusively claimed; after the project-local write succeeds, the legacy file is retired to a timestamped `.migrated…` backup so another project cannot import the same private goal state. An explicit `stateFilePath` or `OPENCODE_GOAL_STATE_PATH` is used literally with no migration fallback.
|
|
244
246
|
|
|
245
247
|
The state directory is created with owner-only permissions, and the JSON state file is written as `0600` because it may contain goal text, assistant checkpoints, and workflow history.
|
|
246
248
|
|
|
@@ -312,13 +314,17 @@ Additional plugin-level options:
|
|
|
312
314
|
|
|
313
315
|
- `maxRecentMessages` — how many recent session messages to scan when looking for the latest assistant turn before auto-continuing. Higher values make long, tool-heavy sessions less likely to lose the most recent assistant response.
|
|
314
316
|
- `noProgressTurnsBeforePause` — grace window for low-output stalls. The plugin pauses only after this many consecutive stalled low-output turns rather than on the first one.
|
|
315
|
-
- `noToolCallTurnsBeforePause` — grace window for tool-free continuation turns. The plugin pauses after this many consecutive continuation turns that produced no tool calls (anti self-chat loop). Default `2
|
|
317
|
+
- `noToolCallTurnsBeforePause` — grace window for tool-free continuation turns. The plugin pauses after this many consecutive continuation turns that produced no tool calls (anti self-chat loop). Default `2`; set the plugin option to `0` for legitimate tool-free writing/research workflows.
|
|
316
318
|
- `warnTurnsRemaining` / `warnDurationMsRemaining` / `warnTokensRemaining` — thresholds at which the auto-continue prompt appends a "limits are near" warning (default `3` turns, `60000` ms, `25000` context tokens). Lower them to warn closer to the limit, or raise them to warn earlier.
|
|
317
319
|
- `commandName` — the slash command the plugin owns (default `goal`). Set it to e.g. `objective` to drive the workflow with `/objective` instead of `/goal`; a leading slash is tolerated. Remember to register the matching command name in your OpenCode `command` config. User-facing hints (`/goal status`, `/goal resume`, …) follow the configured name.
|
|
318
320
|
- `registerCommand` — whether the plugin installs its `command.execute.before` hook at all (default `true`). Set it to `false` if you only want the auto-continue/persistence behavior driven programmatically and don't want the plugin to own a slash command.
|
|
319
321
|
- `registerTools` — whether the plugin registers the agent-facing goal tools (default `true`). Requires the optional `@opencode-ai/plugin` peer dependency to be present; when it is absent, tool registration is skipped and the command/event hooks still work. Set to `false` to omit the programmatic tool surface entirely. See [Agent tools](#agent-tools-optional).
|
|
322
|
+
- `registerAgents` — whether the config hook adds native `goal` and `goal-verify` agents (default `true`). Existing agents with those names are preserved unchanged; the plugin never changes your default agent.
|
|
323
|
+
- `goalAgentName` / `verifierAgentName` — customize the registered native agent names (defaults `goal` and `goal-verify`). The verifier is a hidden subagent with a default-deny tool policy; only `read`, `glob`, and `grep` are allowed.
|
|
324
|
+
- `sdkShape` — OpenCode session-client argument shape: `legacy` (the default generated `PluginInput` client using `{ path, body, query }`) or `flat` (clients using `{ sessionID, ... }`). Read-only `messages`/`get` calls may probe the alternate shape after an argument/schema `TypeError`; mutating calls are never replayed, so set this option correctly for embedded clients.
|
|
320
325
|
- `persistState` — whether to persist active goals and recent goal results to disk.
|
|
321
326
|
- `stateFilePath` — where the persisted state JSON is written. Overrides the default project-local path and the `OPENCODE_GOAL_STATE_PATH` env var. Useful if you want a fixed or ephemeral location. When unset, the default is `<cwd>/.opencode/goals/state.json` (see the persistence section above), and `OPENCODE_GOAL_STATE_PATH` can override it without editing config.
|
|
327
|
+
- `ledgerMaxBytes` / `ledgerRetentionFiles` — bound the lifecycle ledger to 2 MiB per generation and three rotated generations by default. Set retention to `0` to discard the active ledger when it reaches the size ceiling.
|
|
322
328
|
- `resultRetentionMs` — how long a completed goal summary remains available through `/goal status` after the goal leaves active memory.
|
|
323
329
|
- `maxStoredResults` — maximum number of completed-goal summaries retained in process memory before the oldest ones are evicted.
|
|
324
330
|
|
|
@@ -328,11 +334,10 @@ In addition to the `/goal` command, the plugin can expose the same workflow to t
|
|
|
328
334
|
|
|
329
335
|
Registered tools:
|
|
330
336
|
|
|
331
|
-
- `
|
|
332
|
-
- `get_goal_history`
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
- `clear_goal` — clear the current goal and discard its saved status.
|
|
337
|
+
- `goal_status`, `goal_set`, `goal_pause`, `goal_resume`, `goal_block`, and `goal_complete` are the canonical narrow operations. They return compact versioned JSON envelopes so agents can branch reliably without parsing prose.
|
|
338
|
+
- `get_goal`, `get_goal_history`, `set_goal`, `update_goal`, and `clear_goal` remain compatibility aliases with their existing text responses.
|
|
339
|
+
|
|
340
|
+
`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.
|
|
336
341
|
|
|
337
342
|
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 (sisyphus) sequence auto-promotes the next.
|
|
338
343
|
|
|
@@ -340,7 +345,7 @@ These operate on the same per-session multi-goal state as the command path: a to
|
|
|
340
345
|
|
|
341
346
|
## Audit messages
|
|
342
347
|
|
|
343
|
-
When the assistant marks a goal complete or blocked, the plugin announces the audit instead of doing it silently: an audit-start message ("Auditing goal completion…") and an audit-result message ("completion accepted — goal archived" / "paused as blocked — …"). By default these are
|
|
348
|
+
When the assistant marks a goal complete or blocked, the plugin announces the audit instead of doing it silently: an audit-start message ("Auditing goal completion…") and an audit-result message ("completion accepted — goal archived" / "paused as blocked — …"). By default these are written to OpenCode's structured log and shown as a TUI toast when that client capability is available. Provide an `auditMessenger(sessionID, text)` plugin option to route them elsewhere, or set `auditMessages: false` to disable them.
|
|
344
349
|
## Completion auditor (optional)
|
|
345
350
|
|
|
346
351
|
By default a `[goal:complete]` is accepted on the assistant's word. You can require an independent audit before a goal is archived:
|
|
@@ -348,20 +353,24 @@ By default a `[goal:complete]` is accepted on the assistant's word. You can requ
|
|
|
348
353
|
- `completionAudit: true` — the plugin spawns an independent OpenCode child session to verify the completion against the goal and workspace. The auditor replies with `[audit:approved]` or `[audit:rejected]` (with a reason).
|
|
349
354
|
- `auditor: async ({ goal, sessionID, latestText }) => ({ approved, reason })` — supply your own auditor function (takes precedence over `completionAudit`).
|
|
350
355
|
|
|
351
|
-
On **approval** the goal is archived as achieved. On **rejection** the goal is *not* archived — it is paused with stop reason `audit rejected` and the reason in its status, so you can address the gap and `/goal resume`. The built-in
|
|
356
|
+
On **approval** the goal is archived as achieved. On **rejection** the goal is *not* archived — it is paused with stop reason `audit rejected` and the reason in its status, so you can address the gap and `/goal resume`. The built-in and custom auditors fail closed by default. The audit is off unless one of these options is set.
|
|
352
357
|
|
|
353
358
|
Pass `auditorOptions` to tune the built-in auditor:
|
|
354
359
|
|
|
355
360
|
```js
|
|
356
|
-
GoalPlugin(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
361
|
+
await GoalPlugin(
|
|
362
|
+
{ client },
|
|
363
|
+
{
|
|
364
|
+
completionAudit: true,
|
|
365
|
+
auditorOptions: {
|
|
366
|
+
timeoutMs: 60_000, // default 120 000 ms; set lower for faster CI feedback
|
|
367
|
+
failurePolicy: "reject",
|
|
368
|
+
},
|
|
360
369
|
},
|
|
361
|
-
|
|
370
|
+
)
|
|
362
371
|
```
|
|
363
372
|
|
|
364
|
-
`timeoutMs` caps how long the built-in child-session auditor waits for a verdict.
|
|
373
|
+
`timeoutMs` caps how long the built-in child-session auditor waits for a verdict. `failurePolicy` defaults to `reject`: an unavailable API, missing child-session ID, provider error, or timeout rejects the audit and pauses the goal for review. Set it to `approve` only as an explicit compatibility escape hatch; an actual negative or malformed verifier verdict still rejects. `auditorOptions` is ignored when a custom `auditor` function is supplied.
|
|
365
374
|
|
|
366
375
|
## Prompt safety
|
|
367
376
|
|
|
@@ -369,12 +378,28 @@ The goal text is wrapped in `<goal_objective>` tags and labeled as user-provided
|
|
|
369
378
|
|
|
370
379
|
## Limitations
|
|
371
380
|
|
|
372
|
-
|
|
381
|
+
The assistant still signals candidate outcomes with `[goal:complete]` or `[goal:blocked]`. Completion can additionally be checked by a custom `auditor` callback or the built-in child-session auditor before the goal becomes terminal. Marker quality therefore remains model-dependent when auditing is disabled, and audit quality depends on the configured verifier model and evidence available in the session. The built-in verifier performs static inspection with `read`, `glob`, and `grep`; it cannot execute shell commands.
|
|
373
382
|
|
|
374
383
|
OpenCode's current `command.execute.before` hook does not fully intercept command text. The plugin can update in-memory goal state as a side effect, but the goal text may still be routed into the normal assistant conversation alongside the state update.
|
|
375
384
|
|
|
376
385
|
The plugin depends on `experimental.chat.system.transform` and other OpenCode plugin hooks that may change between OpenCode versions.
|
|
377
386
|
|
|
387
|
+
Only one persistence-enabled OpenCode plugin instance may own a given `stateFilePath` at a time. A second process fails initialization with the owning PID/host instead of risking last-writer-wins state loss. Dispose the first host or configure a different path.
|
|
388
|
+
|
|
389
|
+
## Diagnostics and recovery
|
|
390
|
+
|
|
391
|
+
Start with `/goal status`, then `/goal history`. Together they show whether a goal is active, its stop reason and budget usage, and the recent lifecycle/checkpoint trail without exposing the entire on-disk ledger to the model.
|
|
392
|
+
|
|
393
|
+
If a goal does not continue:
|
|
394
|
+
|
|
395
|
+
1. Check for a deliberate pause: user intervention, a hard limit, repeated tool-free/no-progress turns, prompt failures, or a rejected completion audit all stop unattended work by design.
|
|
396
|
+
2. Run `/goal resume` only after resolving the reported reason. Resume creates a fresh local budget window; it does not erase the objective or history.
|
|
397
|
+
3. Check OpenCode's structured logs for persistence, SDK-shape, prompt, or auditor errors.
|
|
398
|
+
4. Confirm the configured project directory and state-path precedence described under [Safety limits](#safety-limits). A daemon started elsewhere can otherwise make a manually configured relative path surprising.
|
|
399
|
+
5. Run `npm run verify`, `npm run smoke`, and `npm run smoke:packed-host` against the installed source when diagnosing registration or packaging problems. `npm run benchmark:behavior` exercises completion, false-completion, loop, interruption, compaction, and restart behavior without a provider call.
|
|
400
|
+
|
|
401
|
+
Do not paste `state.json`, its ledger, or verbose logs into a public issue without reviewing them first: they can contain goal text, assistant checkpoints, blockers, local paths, and command evidence. Prefer the bounded status/history output and redact project-specific content. There is intentionally no broad "dump diagnostics" tool: exposing process-wide session state or persistence paths to the model would add more privacy risk than troubleshooting value.
|
|
402
|
+
|
|
378
403
|
## Local development
|
|
379
404
|
|
|
380
405
|
Point OpenCode at the source file directly for local testing:
|
|
@@ -403,6 +428,9 @@ Keep test files outside OpenCode's auto-loaded plugin directory — OpenCode wil
|
|
|
403
428
|
npm test # run the test suite
|
|
404
429
|
npm run test:coverage # run tests with coverage
|
|
405
430
|
npm run smoke # verify package export + command hook without a model call
|
|
431
|
+
npm run smoke:packed-host # install the packed tarball and exercise the host contract
|
|
432
|
+
npm run benchmark:behavior # deterministic autonomy + token-efficiency scenarios
|
|
433
|
+
npm run verify # verify the installed plugin hook surface
|
|
406
434
|
npm run check # syntax check + tests
|
|
407
435
|
npm run pack:check # verify package contents before publishing
|
|
408
436
|
```
|
package/SECURITY.md
CHANGED
|
@@ -27,4 +27,32 @@ Relevant security-sensitive areas include:
|
|
|
27
27
|
- leakage of goal text through logs or status output
|
|
28
28
|
- malformed persisted state causing stale or unexpected goal recovery
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
## Threat Model and Trust Boundaries
|
|
31
|
+
|
|
32
|
+
The plugin is an orchestration layer, not a sandbox. OpenCode, the selected model/provider, enabled tools, repository instructions, and the local operating-system account remain separate trust boundaries. The plugin does not grant a model new tool permissions, but auto-continuation gives already-authorized tools more opportunities to run. Use OpenCode's permission controls and avoid unattended goals in repositories or sessions you do not trust.
|
|
33
|
+
|
|
34
|
+
The following inputs are untrusted:
|
|
35
|
+
|
|
36
|
+
- goal objectives, criteria, constraints, blockers, and completion evidence
|
|
37
|
+
- assistant/provider output and completion markers
|
|
38
|
+
- session events and SDK responses
|
|
39
|
+
- persisted state and lifecycle-ledger content
|
|
40
|
+
- repository files and instructions encountered by the agent
|
|
41
|
+
|
|
42
|
+
The plugin bounds and escapes prompt-control-like goal text, validates public tool inputs, limits completion-claim size, coalesces duplicate idle events, and pauses recovered goals. These controls reduce accidental loops and prompt confusion; they do not turn model output into trusted data or prove that cited evidence is true. Enable the independent completion auditor for higher-assurance work and keep its default fail-closed failure policy.
|
|
43
|
+
|
|
44
|
+
## Data Stored and Exposed
|
|
45
|
+
|
|
46
|
+
Persistence may contain objectives, criteria, constraints, assistant checkpoints, lifecycle events, blockers, completion evidence, changed-file names, and commands reported by the agent. Files are created with owner-only permissions where the platform supports POSIX modes, but filesystem permissions do not provide encryption at rest. State is project-local by default and is not intentionally uploaded by this plugin; the configured model/provider still receives prompt context through OpenCode. The lifecycle ledger rotates at a finite size and retention count, but retained generations still contain sensitive task text until they are rotated out or deleted.
|
|
47
|
+
|
|
48
|
+
Status and history are session-scoped. The plugin intentionally avoids a process-wide diagnostic dump because a long-running OpenCode host may serve multiple projects or sessions. Before sharing logs or persistence files, remove secrets, proprietary text, usernames, absolute paths, and provider identifiers.
|
|
49
|
+
|
|
50
|
+
## Security Assumptions and Residual Risk
|
|
51
|
+
|
|
52
|
+
- A compromised OpenCode host, provider, dependency, or local account is outside the plugin's protection boundary.
|
|
53
|
+
- Completion auditing improves confidence but is not a cryptographic attestation and may share the same provider failure modes as the working agent.
|
|
54
|
+
- Disabling persistence avoids local state files but also removes restart recovery and ledger durability.
|
|
55
|
+
- Setting the auditor's `failurePolicy` to `approve` weakens completion integrity and should be treated as a compatibility-only escape hatch.
|
|
56
|
+
- Experimental OpenCode hook or SDK changes can alter interception, display, or continuation behavior; re-test the exact version/provider combination used for unattended work.
|
|
57
|
+
|
|
58
|
+
Goal text is wrapped in `<goal_objective>` tags and labeled as user-provided task data. The plugin neutralizes opening and closing tags that match its prompt-control vocabulary, including role-like tags, before inserting user-controlled text. This is defense in depth for a plaintext model prompt, not a privilege boundary: treat goal text as untrusted content and do not assume tag escaping can make arbitrary third-party instructions safe.
|
package/demo/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# opencode-goal-plugin demo
|
|
2
|
+
|
|
3
|
+
A minimal, reproducible demo: a failing test suite and a `/goal` that fixes
|
|
4
|
+
it autonomously, with no supervision after you set the goal.
|
|
5
|
+
|
|
6
|
+
`src/math.js` has a deliberate bug — `add(a, b)` returns `a - b` instead of
|
|
7
|
+
`a + b`. `test/math.test.js` catches it. The goal below tells the agent to
|
|
8
|
+
fix the failing tests; the plugin auto-continues the session until the
|
|
9
|
+
agent verifies the suite passes and reports evidence-backed completion.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- Node.js >= 18
|
|
14
|
+
- OpenCode installed (`opencode --version`)
|
|
15
|
+
- At least one provider configured (`opencode auth login`)
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
1. From the repo root, confirm the bug is real:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
cd demo
|
|
23
|
+
npm test
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You should see 2 failing tests — `add(2, 3)` returns `-1` instead of `5`.
|
|
27
|
+
|
|
28
|
+
2. Launch OpenCode in this directory. `demo/opencode.json` already points
|
|
29
|
+
the plugin at the source file (`file:../src/goal-plugin.js`) and
|
|
30
|
+
registers the `goal` command, so no install step is needed:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
opencode
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. Set the goal. If this `demo/` directory sits inside a larger checkout
|
|
37
|
+
(e.g. you cloned the whole plugin repo), be specific about scope —
|
|
38
|
+
"this repo" can otherwise be read as the surrounding project rather
|
|
39
|
+
than the demo itself:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
/goal fix the failing tests in demo/test/math.test.js --max-turns 10
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
4. Walk away. The plugin keeps the goal in context, auto-continues after
|
|
46
|
+
each idle turn, and stops when the agent reports a substantiated
|
|
47
|
+
completion or a safety limit is reached (10 turns here).
|
|
48
|
+
|
|
49
|
+
5. Check the result:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npm test
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Both tests should now pass, and `src/math.js` should return `a + b`.
|
|
56
|
+
|
|
57
|
+
## What to expect
|
|
58
|
+
|
|
59
|
+
- The agent should read `test/math.test.js`, find the failing assertions,
|
|
60
|
+
locate the bug in `src/math.js`, fix `a - b` → `a + b`, rerun `npm test`
|
|
61
|
+
to confirm, and end its response with a `[goal:evidence]` line
|
|
62
|
+
summarizing what it verified, followed by `[goal:complete]`. A
|
|
63
|
+
`[goal:complete]` with no preceding `[goal:evidence]` is rejected by the
|
|
64
|
+
plugin and re-prompted — the agent can't just claim done, it has to show
|
|
65
|
+
its work.
|
|
66
|
+
- Run `/goal status` at any point to see the current goal, elapsed
|
|
67
|
+
turns/time/tokens, and remaining budget (plus the accepted evidence once
|
|
68
|
+
complete). Run `/goal history` to see the lifecycle events and the
|
|
69
|
+
latest checkpoint. Run `/goal list` to see all live/backgrounded goals
|
|
70
|
+
in the session.
|
|
71
|
+
- **Hook output display varies by OpenCode version and provider** — the
|
|
72
|
+
plugin's own status text may or may not render directly in the TUI (see
|
|
73
|
+
the [compatibility table](../README.md#compatibility-snapshot)). Either
|
|
74
|
+
way, the goal's state (limits, turn count, completion) is tracked
|
|
75
|
+
correctly; you can always verify it directly by inspecting the
|
|
76
|
+
persisted state at `.opencode/goals/state.json` in this directory.
|
|
77
|
+
|
|
78
|
+
## Resetting the demo
|
|
79
|
+
|
|
80
|
+
To run the demo again from a clean slate:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
git checkout -- src/math.js
|
|
84
|
+
/goal clear
|
|
85
|
+
rm -rf .opencode/goals
|
|
86
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"plugin": ["file:../src/goal-plugin.js"],
|
|
4
|
+
"command": {
|
|
5
|
+
"goal": {
|
|
6
|
+
"description": "Set a session-scoped goal and auto-continue until complete.",
|
|
7
|
+
"template": "$ARGUMENTS",
|
|
8
|
+
"agent": "build"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
package/demo/src/math.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import test from "node:test"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import { add } from "../src/math.js"
|
|
4
|
+
|
|
5
|
+
test("add returns the sum of two numbers", () => {
|
|
6
|
+
assert.equal(add(2, 3), 5)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test("add handles negative numbers", () => {
|
|
10
|
+
assert.equal(add(-1, -1), -2)
|
|
11
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Provider / model compatibility
|
|
2
|
+
|
|
3
|
+
opencode-goal-plugin works with any model OpenCode can talk to, but two things
|
|
4
|
+
vary by provider and model:
|
|
5
|
+
|
|
6
|
+
1. **Marker compliance** — whether the model reliably ends its response with
|
|
7
|
+
`[goal:complete]` (or the bracket-less form) on its own final line, with a
|
|
8
|
+
preceding `[goal:evidence]` line summarizing what it actually verified.
|
|
9
|
+
Since the completion-evidence requirement was added, a bare
|
|
10
|
+
`[goal:complete]` with no evidence line is **rejected** and the plugin
|
|
11
|
+
re-prompts with an explicit `<evidence_required>` block — this is a
|
|
12
|
+
real safety net, not just documentation: a model that initially skips the
|
|
13
|
+
evidence line gets one automatic correction cycle before hitting its
|
|
14
|
+
turn/time/token budget.
|
|
15
|
+
2. **Hook output display** — whether the plugin's own response text (e.g.
|
|
16
|
+
`No active goal. Set one with...`) is rendered in the OpenCode TUI, or
|
|
17
|
+
whether the raw command argument is instead routed to the model as a
|
|
18
|
+
normal chat turn. This is an OpenCode-host behavior, not model-specific,
|
|
19
|
+
but is included here since it's most visible while testing a new model.
|
|
20
|
+
See the [OpenCode version compatibility table](../README.md#opencode-version-compatibility)
|
|
21
|
+
in the README for the current findings.
|
|
22
|
+
|
|
23
|
+
All rows below were verified manually against the real OpenCode TUI (`tmux`
|
|
24
|
+
+ live provider credentials, no mocks) on OpenCode 1.17.15, driving the
|
|
25
|
+
plugin through `/goal status`, `/goal <condition> --max-turns N`, and
|
|
26
|
+
inspecting the plugin's persisted state file to confirm state mutations
|
|
27
|
+
(limit parsing, turn/stop accounting, evidence-gated completion detection)
|
|
28
|
+
independent of what was rendered in the terminal.
|
|
29
|
+
|
|
30
|
+
## Tested models
|
|
31
|
+
|
|
32
|
+
| Provider | Model | Marker compliance | Notes |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| `opencode-go` | `qwen3.7-plus` | ✅ Self-corrects | First attempt emitted bare `[goal:complete]` with no evidence line and was correctly rejected by the plugin. On the very next turn it read the `<evidence_required>` re-prompt, added a `[goal:evidence]` line, and completed cleanly — a good demonstration of the evidence gate actually improving behavior rather than just failing closed. |
|
|
35
|
+
| `opencode-go` | `glm-5.2` | ✅ Clean | Emitted a correct `[goal:evidence] ... [goal:complete]` pair on the first attempt. (An earlier plugin version without the evidence requirement showed GLM-5.2 sometimes trailing extra text after a bare marker — the more structured `<completion_audit>` prompt this plugin version sends appears to help.) |
|
|
36
|
+
| `deepseek` | `deepseek-chat` | ✅ Clean | Emitted a correct `[goal:evidence] ... [goal:complete]` pair on the first attempt, both in a short synthetic goal and in the full [demo](../demo/) (autonomously located and fixed a real bug, then reported evidence-backed completion). Correctly parses per-goal flags (`--max-turns`, etc.) out of the condition text. |
|
|
37
|
+
|
|
38
|
+
Untested at time of writing: `deepseek-reasoner`, `mistral/*`, `openrouter/*`,
|
|
39
|
+
and any `nvidia`/`google` provider — add rows here as they're verified. See
|
|
40
|
+
[Testing a new model](#testing-a-new-model) below.
|
|
41
|
+
|
|
42
|
+
## Strict-template backends
|
|
43
|
+
|
|
44
|
+
Some backends (notably certain Qwen deployments on vLLM, and several
|
|
45
|
+
Llama.cpp/Mistral chat templates) reject a `system` role message that isn't
|
|
46
|
+
the very first message in the conversation, with an error like `"System
|
|
47
|
+
message must be at the beginning."` opencode-goal-plugin's
|
|
48
|
+
`experimental.chat.system.transform` hook merges the goal continuation block
|
|
49
|
+
into the primary system entry instead of appending a separate one, which
|
|
50
|
+
avoids this. This is covered by regression tests in
|
|
51
|
+
[`test/goal-plugin.test.js`](../test/goal-plugin.test.js) and does not
|
|
52
|
+
require any provider-specific configuration.
|
|
53
|
+
|
|
54
|
+
## Testing a new model
|
|
55
|
+
|
|
56
|
+
No LLM call is required to verify the plugin loads correctly — run
|
|
57
|
+
`npm run verify` first. To check marker compliance and hook behavior for a
|
|
58
|
+
specific provider/model:
|
|
59
|
+
|
|
60
|
+
1. Point an OpenCode config at the plugin (see
|
|
61
|
+
[Local development](../README.md#local-development) in the README) and
|
|
62
|
+
register the `goal` command.
|
|
63
|
+
2. Launch OpenCode against the model you want to test:
|
|
64
|
+
```sh
|
|
65
|
+
opencode -m <provider>/<model>
|
|
66
|
+
```
|
|
67
|
+
3. Run a short, deterministic goal that should complete in one or two turns:
|
|
68
|
+
```
|
|
69
|
+
/goal say hi and end with [goal:complete] --max-turns 2
|
|
70
|
+
```
|
|
71
|
+
4. Check whether the first attempt includes a proper `[goal:evidence]` line
|
|
72
|
+
before `[goal:complete]`. If not, confirm the plugin rejects it and
|
|
73
|
+
re-prompts with `<evidence_required>` — then check whether the model
|
|
74
|
+
self-corrects within its remaining turn budget, or exhausts it.
|
|
75
|
+
5. Run `/goal status` and check whether the plugin's text (e.g. `No active
|
|
76
|
+
goal...` or `Active goal: ...`) is rendered directly, or whether the
|
|
77
|
+
command text was instead routed to the model as a chat turn.
|
|
78
|
+
6. Cross-check state mutations directly against the persisted state file
|
|
79
|
+
(`.opencode/goals/state.json`, project-local by default) to confirm the
|
|
80
|
+
goal's `options` reflect the flags you passed and `state`/`stopReason`
|
|
81
|
+
reflect what actually happened — this is authoritative even when the TUI
|
|
82
|
+
display doesn't show the plugin's own output.
|
|
83
|
+
7. Add a row to the table above with your findings, including the OpenCode
|
|
84
|
+
version you tested against.
|