opencode-goal-plugin 0.6.0 → 0.6.2
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 +34 -15
- package/CONTRIBUTING.md +10 -13
- package/README.md +30 -19
- package/SECURITY.md +7 -9
- package/docs/compatibility.md +41 -0
- package/docs/providers.md +22 -3
- package/docs/releasing.md +41 -0
- package/index.d.ts +58 -6
- package/package.json +24 -6
- package/scripts/verify.mjs +1 -0
- package/src/goal-plugin.js +660 -229
- package/src/native-agent-config.js +5 -1
- package/src/opencode-session-api.js +11 -1
- package/src/persistence-lease.js +14 -2
- package/scripts/behavior-benchmark.mjs +0 -272
- package/scripts/packed-host-contract.mjs +0 -160
- package/scripts/smoke-command-hook.mjs +0 -51
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.6.2 — 2026-07-11
|
|
6
|
+
|
|
7
|
+
- Keep paused, blocked, and crash-recovered goals inert in model turns with a stopped-goal system guard, and enforce status/history/list/pause/clear control turns as read-only through the host's tool-execution hook so routed command text cannot mutate or resurrect work.
|
|
8
|
+
- Add installed-package TypeScript and full tool-surface contracts, critical mutation testing, generated lifecycle-model testing, Linux/macOS/Windows filesystem CI, CodeQL, dependency updates, and a verified release workflow.
|
|
9
|
+
- Preserve multi-goal creation order when a paused goal resumes; execution epochs rotate through `runId` while the public `goalId` remains stable.
|
|
10
|
+
- Document the supported compatibility surface, release integrity process, and private vulnerability reporting path.
|
|
11
|
+
|
|
12
|
+
## 0.6.1 — 2026-07-10
|
|
13
|
+
|
|
14
|
+
- Default-deny verifier tools except read/glob/grep, and fail closed unless the owned verifier agent registers successfully.
|
|
15
|
+
- Require exact final-line audit verdicts and adjacent bounded completion evidence to prevent quoted-marker false approvals.
|
|
16
|
+
- Prevent mutating SDK shape retries, validate child-session ancestry, and make auditor timeout independent of a hanging abort.
|
|
17
|
+
- Move context-budget reset to the successful `session.compacted` event and prevent ordered goals from reusing a prior goal's completion message.
|
|
18
|
+
- Drain accepted persistence writes before disposal, suppress late disposed-instance writes, and re-check goal identity after asynchronous audits/announcements.
|
|
19
|
+
- Improve 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.
|
|
20
|
+
- Fail closed when neither terminal state nor ledger can be persisted; keep the goal paused instead of claiming archival.
|
|
21
|
+
- 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.
|
|
22
|
+
- Add conditional TypeScript export mappings, an OpenCode engine range, honest auditor/visibility documentation, and cache-safe near-limit continuation warnings.
|
|
23
|
+
|
|
5
24
|
## 0.6.0 — 2026-07-10
|
|
6
25
|
|
|
7
26
|
> 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.
|
|
@@ -110,7 +129,7 @@
|
|
|
110
129
|
|
|
111
130
|
## 0.4.0 — 2026-06-21
|
|
112
131
|
|
|
113
|
-
- **Expose agent-facing goal tools (`get_goal`, `get_goal_history`, `set_goal`, `update_goal`, `clear_goal`)** when the host provides `@opencode-ai/plugin` (a new *optional* peer dependency, loaded via a cached dynamic import so the zero-runtime-dependency posture is preserved). `set_goal` is constrained by its description to explicit user requests (so the agent does not set goals on its own); it accepts optional `maxTurns` / `maxTokens` / `maxDurationMs` overrides plus `successCriteria` / `constraints` / `mode`. `update_goal` supports objective edits and `complete` / `blocked` / `paused` / `resumed` transitions (with `evidence` / `blocker`). Tools create, replace, and clear goals through the **multi-goal registry** (the same `buildGoalState` → `registerSessionGoal` → `focusGoal` path the `/goal` command uses), so tool-created goals persist, appear in `/goal list`, and are driven by the idle handler; `complete` archives with evidence and auto-promotes the next goal in an ordered (sisyphus) sequence. Registration is skipped gracefully when the package is absent or with `registerTools: false`. New `buildAgentToolHandlers` / `buildAgentTools` / `agentToolSessionID` helpers.
|
|
132
|
+
- **Expose agent-facing goal tools (`get_goal`, `get_goal_history`, `set_goal`, `update_goal`, `clear_goal`)** when the host provides `@opencode-ai/plugin` (a new *optional* peer dependency, loaded via a cached dynamic import so the zero-runtime-dependency posture is preserved). `set_goal` is constrained by its description to explicit user requests (so the agent does not set goals on its own); it accepts optional `maxTurns` / `maxTokens` / `maxDurationMs` overrides plus `successCriteria` / `constraints` / `mode`. `update_goal` supports objective edits and `complete` / `blocked` / `paused` / `resumed` transitions (with `evidence` / `blocker`). Tools create, replace, and clear goals through the **multi-goal registry** (the same `buildGoalState` → `registerSessionGoal` → `focusGoal` path the `/goal` command uses), so tool-created goals persist, appear in `/goal list`, and are driven by the idle handler; `complete` archives with evidence and auto-promotes the next goal in an ordered (sisyphus) sequence. Registration is skipped gracefully when the package is absent or with `registerTools: false`. New `buildAgentToolHandlers` / `buildAgentTools` / `agentToolSessionID` helpers. _(This is the work the 0.3.0 changelog mistakenly listed as already shipped; it is now actually implemented and adapted to the current multi-goal architecture.)_
|
|
114
133
|
- **Fix a goal-registry leak when resuming.** `resetGoalBudget` rotates a goal's `goalId`, but the multi-goal registry is keyed by `goalId`, so resuming and then clearing/replacing left a stale entry behind (visible in `/goal list` and persisted). Both the `/goal resume` command path and the agent `update_goal {status:"resumed"}` path now re-key the registry to the new id (the focused pointer holds the same object). Regression test added for the command path.
|
|
115
134
|
|
|
116
135
|
## 0.3.0 — 2026-06-14
|
|
@@ -119,35 +138,35 @@
|
|
|
119
138
|
|
|
120
139
|
### Completion integrity & audit
|
|
121
140
|
|
|
122
|
-
- **Require evidence to complete a goal and a concrete blocker to block one.** A `[goal:complete]` marker is now only honored when the assistant also supplies a non-empty `[goal:evidence] <summary>` line (on or before the completion marker); a `[goal:blocked]` is only honored when a concrete blocker is stated on the line before it. An unsubstantiated `[goal:complete]` or `[goal:blocked]` is rejected (not recorded / does not stop the goal) and the plugin sends a corrective continuation prompt demanding the missing evidence or blocker. The accepted evidence is stored on the result and shown in `/goal status` / `/goal history`. New `extractCompletionEvidence` helper, an `<evidence_required>` structural tag (added to the injection-escaping set), and continuation/system/compaction/creation prompts all updated to instruct the evidence requirement.
|
|
123
|
-
- **Add an optional separate completion auditor that verifies before archival.** When a completion auditor is configured, a `[goal:complete]` (with evidence) is verified before the goal is archived: on approval it archives as achieved, on rejection the goal is *restored* (paused with stop reason `audit rejected` and the reason surfaced) rather than archived. Enable the built-in auditor — which spawns an independent OpenCode child session that replies `[audit:approved]`/`[audit:rejected]` — with `completionAudit: true`, or supply a custom `auditor({ goal, sessionID, latestText }) => { approved, reason }` (takes precedence).
|
|
124
|
-
- **Announce completion/blocker audits with visible messages instead of silent background work.** When the assistant marks a goal complete or blocked, the plugin emits an audit-start and an audit-result message (e.g. "Auditing goal completion…" → "Audit result: completion accepted — goal archived"). Delivery defaults to OpenCode's structured log (`client.app.log`) and is pluggable via an `auditMessenger(sessionID, text)` option or disable-able with `auditMessages: false`. New `defaultAuditMessenger` helper.
|
|
141
|
+
- **Require evidence to complete a goal and a concrete blocker to block one.** A `[goal:complete]` marker is now only honored when the assistant also supplies a non-empty `[goal:evidence] <summary>` line (on or before the completion marker); a `[goal:blocked]` is only honored when a concrete blocker is stated on the line before it. An unsubstantiated `[goal:complete]` or `[goal:blocked]` is rejected (not recorded / does not stop the goal) and the plugin sends a corrective continuation prompt demanding the missing evidence or blocker. The accepted evidence is stored on the result and shown in `/goal status` / `/goal history`. New `extractCompletionEvidence` helper, an `<evidence_required>` structural tag (added to the injection-escaping set), and continuation/system/compaction/creation prompts all updated to instruct the evidence requirement.
|
|
142
|
+
- **Add an optional separate completion auditor that verifies before archival.** When a completion auditor is configured, a `[goal:complete]` (with evidence) is verified before the goal is archived: on approval it archives as achieved, on rejection the goal is *restored* (paused with stop reason `audit rejected` and the reason surfaced) rather than archived. Enable the built-in auditor — which spawns an independent OpenCode child session that replies `[audit:approved]`/`[audit:rejected]` — with `completionAudit: true`, or supply a custom `auditor({ goal, sessionID, latestText }) => { approved, reason }` (takes precedence). In this release, the built-in child-session auditor approved when the session API was unavailable; later releases changed operational failures to reject by default. A custom auditor that throws is treated as a rejection. New `parseAuditVerdict` / `buildAuditPrompt` / `createChildSessionAuditor` helpers. Off by default.
|
|
143
|
+
- **Announce completion/blocker audits with visible messages instead of silent background work.** When the assistant marks a goal complete or blocked, the plugin emits an audit-start and an audit-result message (e.g. "Auditing goal completion…" → "Audit result: completion accepted — goal archived"). Delivery defaults to OpenCode's structured log (`client.app.log`) and is pluggable via an `auditMessenger(sessionID, text)` option or disable-able with `auditMessages: false`. New `defaultAuditMessenger` helper.
|
|
125
144
|
|
|
126
145
|
### Durability
|
|
127
146
|
|
|
128
|
-
- **Add an append-only JSONL lifecycle ledger with state reconstruction, and fail-closed terminal-state persistence.** Every lifecycle event (`pushHistory`) is also appended as one JSON line to `<stateFile>.ledger.jsonl` (synchronous, owner-only `0600`). Because in-memory history is capped, the ledger is the durable record: when the main state file is missing on startup, the plugin reconstructs still-active (non-`completed`/`cleared`) goals from the ledger and reloads them paused (new `reconstructed` load status). Terminal events are written to the ledger before the main state write, so a goal's terminal outcome survives a failed state write (fail-closed); `persistState` now returns success/failure and a failed terminal persist is logged at error level. Tied to `persistState`. New `appendLedgerLine` / `readLedgerEntries` / `reconstructGoalsFromLedger` helpers.
|
|
129
|
-
- **Build the compaction summary deterministically from the persisted goal record.** `buildCompactionContext` folds in a reproducible progress summary — recent checkpoints and lifecycle events — derived from the goal's persisted `checkpoints`/`history` (new `buildCompactionProgressSummary` helper) rather than chat memory, and labels it as such.
|
|
147
|
+
- **Add an append-only JSONL lifecycle ledger with state reconstruction, and fail-closed terminal-state persistence.** Every lifecycle event (`pushHistory`) is also appended as one JSON line to `<stateFile>.ledger.jsonl` (synchronous, owner-only `0600`). Because in-memory history is capped, the ledger is the durable record: when the main state file is missing on startup, the plugin reconstructs still-active (non-`completed`/`cleared`) goals from the ledger and reloads them paused (new `reconstructed` load status). Terminal events are written to the ledger before the main state write, so a goal's terminal outcome survives a failed state write (fail-closed); `persistState` now returns success/failure and a failed terminal persist is logged at error level. Tied to `persistState`. New `appendLedgerLine` / `readLedgerEntries` / `reconstructGoalsFromLedger` helpers.
|
|
148
|
+
- **Build the compaction summary deterministically from the persisted goal record.** `buildCompactionContext` folds in a reproducible progress summary — recent checkpoints and lifecycle events — derived from the goal's persisted `checkpoints`/`history` (new `buildCompactionProgressSummary` helper) rather than chat memory, and labels it as such.
|
|
130
149
|
|
|
131
150
|
### Auto-continue guardrails
|
|
132
151
|
|
|
133
|
-
- **Pause auto-continue on repeated tool-free continuation turns (no-tool-call gate).** Complementing the low-output no-progress check, the plugin tracks continuation turns whose assistant message has no tool calls (OpenCode `tool` / `subtask` parts) and, after `noToolCallTurnsBeforePause` consecutive such turns (default `2`), pauses with stop reason `no tool calls` to guard against self-chat loops. A tool-using turn resets the counter. Configurable via the `noToolCallTurnsBeforePause` option and `--no-tool-turns <n>` flag. New `messageHasToolCall` helper.
|
|
134
|
-
- **Pause auto-continue when a real user message arrives ("latest instruction wins").** The idle handler detects a genuine human message that arrived after the plugin's most recent continuation and pauses the goal (stop reason `user intervention`) instead of talking over the user; `/goal resume` hands control back. Plugin-generated continuation prompts (user-role messages framed in `<goal_continuation>`) are ignored, and detection requires `turnCount > 0` plus a visible plugin continuation so the first idle and scrolled-out sessions are never misread. New `isPluginContinuationMessage` / `userInterventionDetected` helpers.
|
|
152
|
+
- **Pause auto-continue on repeated tool-free continuation turns (no-tool-call gate).** Complementing the low-output no-progress check, the plugin tracks continuation turns whose assistant message has no tool calls (OpenCode `tool` / `subtask` parts) and, after `noToolCallTurnsBeforePause` consecutive such turns (default `2`), pauses with stop reason `no tool calls` to guard against self-chat loops. A tool-using turn resets the counter. Configurable via the `noToolCallTurnsBeforePause` option and `--no-tool-turns <n>` flag. New `messageHasToolCall` helper.
|
|
153
|
+
- **Pause auto-continue when a real user message arrives ("latest instruction wins").** The idle handler detects a genuine human message that arrived after the plugin's most recent continuation and pauses the goal (stop reason `user intervention`) instead of talking over the user; `/goal resume` hands control back. Plugin-generated continuation prompts (user-role messages framed in `<goal_continuation>`) are ignored, and detection requires `turnCount > 0` plus a visible plugin continuation so the first idle and scrolled-out sessions are never misread. New `isPluginContinuationMessage` / `userInterventionDetected` helpers.
|
|
135
154
|
|
|
136
155
|
### Multiple goals
|
|
137
156
|
|
|
138
|
-
- **Support multiple goals per session with `/goal add`, `/goal list`, and `/goal focus`.** A session can hold several live goals via a new `sessionGoals` registry; `goalStates` continues to track the single *focused* goal the idle handler drives. `/goal <condition>` replaces the focused goal; `/goal add <condition>` backgrounds the current goal and focuses a new one (only the focused goal auto-continues). `/goal list` shows numbered live goals plus a per-session archive of completed/cleared goals, and `/goal focus <number|id>` switches the active goal (numeric refs are index-only). Focus is tracked per session and persisted (state files gain a per-goal `focused` flag and an `archives` array; older single-goal files load with their goal focused). New `buildGoalState` / `formatGoalList` / session-registry helpers.
|
|
139
|
-
- **Add `/goal sisyphus` ordered goal sequences.** `/goal sisyphus <obj 1>; <obj 2>; …` sets up a strict execution sequence: the first objective is focused and the rest queued, and when the focused goal completes the plugin auto-promotes the next until the sequence is exhausted. The ordered flag is tracked per session, shown in `/goal list`, persisted (`orderedSessions`), and cleared by `/goal clear`. New `promoteNextOrderedGoal` helper.
|
|
157
|
+
- **Support multiple goals per session with `/goal add`, `/goal list`, and `/goal focus`.** A session can hold several live goals via a new `sessionGoals` registry; `goalStates` continues to track the single *focused* goal the idle handler drives. `/goal <condition>` replaces the focused goal; `/goal add <condition>` backgrounds the current goal and focuses a new one (only the focused goal auto-continues). `/goal list` shows numbered live goals plus a per-session archive of completed/cleared goals, and `/goal focus <number|id>` switches the active goal (numeric refs are index-only). Focus is tracked per session and persisted (state files gain a per-goal `focused` flag and an `archives` array; older single-goal files load with their goal focused). New `buildGoalState` / `formatGoalList` / session-registry helpers.
|
|
158
|
+
- **Add `/goal sisyphus` ordered goal sequences.** `/goal sisyphus <obj 1>; <obj 2>; …` sets up a strict execution sequence: the first objective is focused and the rest queued, and when the focused goal completes the plugin auto-promotes the next until the sequence is exhausted. The ordered flag is tracked per session, shown in `/goal list`, persisted (`orderedSessions`), and cleared by `/goal clear`. New `promoteNextOrderedGoal` helper.
|
|
140
159
|
|
|
141
160
|
### Schema & command UX
|
|
142
161
|
|
|
143
|
-
- **Add success-criteria, constraints/non-goals, and mode to the goal schema.** A goal can carry `successCriteria` (`--success`), `constraints` (`--constraints` / `--non-goals`), and a `mode` of `normal` or `ordered` (`--mode`, `sisyphus` alias). These thread through state, persistence, the injected goal block (escaped, new `success_criteria` / `constraints` structural tags), creation output, and `/goal status`. New `normalizeMode` helper.
|
|
144
|
-
- **Add an inline `--budget <n>` flag** on the create command — a shorthand for the context-token limit accepting a plain integer or `k`/`m` suffix (e.g. `--budget 100k`). New `parseTokenBudget` helper.
|
|
145
|
-
- **Make the slash command configurable (`commandName`) and optional (`registerCommand`).** `commandName` (default `goal`, leading slash tolerated) lets the plugin own e.g. `/objective`, with all user-facing hints following the configured name; `registerCommand: false` skips installing the command hook entirely. New `normalizeCommandOptions` helper.
|
|
162
|
+
- **Add success-criteria, constraints/non-goals, and mode to the goal schema.** A goal can carry `successCriteria` (`--success`), `constraints` (`--constraints` / `--non-goals`), and a `mode` of `normal` or `ordered` (`--mode`, `sisyphus` alias). These thread through state, persistence, the injected goal block (escaped, new `success_criteria` / `constraints` structural tags), creation output, and `/goal status`. New `normalizeMode` helper.
|
|
163
|
+
- **Add an inline `--budget <n>` flag** on the create command — a shorthand for the context-token limit accepting a plain integer or `k`/`m` suffix (e.g. `--budget 100k`). New `parseTokenBudget` helper.
|
|
164
|
+
- **Make the slash command configurable (`commandName`) and optional (`registerCommand`).** `commandName` (default `goal`, leading slash tolerated) lets the plugin own e.g. `/objective`, with all user-facing hints following the configured name; `registerCommand: false` skips installing the command hook entirely. New `normalizeCommandOptions` helper.
|
|
146
165
|
|
|
147
166
|
### Storage, tools & packaging
|
|
148
167
|
|
|
149
|
-
- **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.
|
|
150
|
-
- _**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
|
|
168
|
+
- **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.
|
|
169
|
+
- _**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 ships; see the **0.4.0** section above._
|
|
151
170
|
- **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.
|
|
152
171
|
|
|
153
172
|
## 0.2.0 — 2026-06-14
|
package/CONTRIBUTING.md
CHANGED
|
@@ -15,8 +15,11 @@ Run the local checks before submitting changes:
|
|
|
15
15
|
```sh
|
|
16
16
|
npm test
|
|
17
17
|
npm run test:coverage
|
|
18
|
+
npm run type:check
|
|
19
|
+
npm run test:mutation
|
|
18
20
|
npm run smoke
|
|
19
21
|
npm run smoke:packed-host
|
|
22
|
+
npm run smoke:packed-tools
|
|
20
23
|
npm run benchmark:behavior
|
|
21
24
|
npm run verify
|
|
22
25
|
npm run check
|
|
@@ -30,25 +33,19 @@ For behavior changes, add or update tests in `test/goal-plugin.test.js`.
|
|
|
30
33
|
This plugin depends on OpenCode plugin hooks, including experimental hooks. When changing hook usage, command behavior, or system-prompt transforms:
|
|
31
34
|
|
|
32
35
|
1. Check the current OpenCode plugin and command documentation.
|
|
33
|
-
2. Run `npm run smoke`
|
|
36
|
+
2. Run `npm run smoke`, `npm run smoke:packed-host`, `npm run smoke:packed-tools`, and `npm run type:check` to verify the source and installed-tarball contracts.
|
|
34
37
|
3. Test against a real OpenCode install when possible.
|
|
35
38
|
4. Update the README compatibility snapshot if the tested surface changes.
|
|
36
39
|
|
|
37
|
-
`npm run smoke` verifies the package export path and `/goal` command hook without invoking a model.
|
|
40
|
+
`npm run smoke` verifies the package export path and `/goal` command hook without invoking a model. The packed-host and packed-tool checks install the npm artifact in isolated consumer projects and verify the public hook and complete optional-peer tool contracts. `npm run type:check` compiles installed-package consumers with NodeNext and Bundler resolution. `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.
|
|
38
41
|
|
|
39
42
|
## Release checklist
|
|
40
43
|
|
|
41
|
-
Before publishing or tagging a release:
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
- run `npm run
|
|
46
|
-
- run `npm run smoke`
|
|
47
|
-
- run `npm run smoke:packed-host`
|
|
48
|
-
- run `npm run benchmark:behavior`
|
|
49
|
-
- run `npm run verify`
|
|
50
|
-
- run `npm run check`
|
|
51
|
-
- run `npm run pack:check`
|
|
44
|
+
Before publishing or tagging a release, follow [the release process](docs/releasing.md). The required automated gate is:
|
|
45
|
+
|
|
46
|
+
- run `npm ci`
|
|
47
|
+
- update `CHANGELOG.md` and both package-version files
|
|
48
|
+
- run `npm run release:check`
|
|
52
49
|
- perform at least one manual OpenCode smoke test if hook behavior changed
|
|
53
50
|
- refresh compatibility notes if the tested OpenCode surface changed
|
|
54
51
|
|
package/README.md
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
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
|
-
[](https://github.com/willytop8/OpenCode-goal-plugin/actions/workflows/codeql.yml)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A session-scoped `/goal` workflow for [OpenCode](https://opencode.ai/).
|
|
10
10
|
|
|
11
11
|
Set a goal and the plugin keeps it in context, auto-continues the session whenever the assistant goes idle, and stops when the goal is marked complete, a blocker is reported, or a safety limit is reached.
|
|
12
12
|
|
|
@@ -27,9 +27,13 @@ This project is independently implemented for OpenCode. Product names used elsew
|
|
|
27
27
|
| Surface | Status |
|
|
28
28
|
|---|---|
|
|
29
29
|
| Node.js | Declared support: `>=18`; CI covers Node 18, 20, 22, and 24 |
|
|
30
|
-
|
|
|
30
|
+
| Operating systems | Filesystem-sensitive lifecycle tests run on Linux, macOS, and Windows |
|
|
31
|
+
| Package entrypoint | Installed-tarball contracts verify both export paths, consumer TypeScript resolution, hooks, and all 11 tools |
|
|
31
32
|
| Provider/backend quirks | Strict-template backends require the goal block to merge into the primary `system` message; covered by regression tests |
|
|
32
33
|
|
|
34
|
+
See the [compatibility policy](docs/compatibility.md) for the supported public
|
|
35
|
+
surface and versioning expectations.
|
|
36
|
+
|
|
33
37
|
### OpenCode version compatibility
|
|
34
38
|
|
|
35
39
|
Manually tested via the OpenCode TUI (`tmux` + real provider credentials, no mocks), verified against the plugin's own persisted state rather than terminal display alone:
|
|
@@ -207,7 +211,7 @@ The deploy step needs a production API token I don't have.
|
|
|
207
211
|
[goal:blocked]
|
|
208
212
|
```
|
|
209
213
|
|
|
210
|
-
`[goal:complete]` — goal is satisfied. It is **only honored when the
|
|
214
|
+
`[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.
|
|
211
215
|
`[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.
|
|
212
216
|
|
|
213
217
|
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.
|
|
@@ -224,7 +228,7 @@ Markers must appear on their own final line. The bracketed form is canonical, bu
|
|
|
224
228
|
| Budget wrap-up threshold | 80% of context token budget |
|
|
225
229
|
| Auto-continue failure pause | 3 consecutive prompt failures |
|
|
226
230
|
|
|
227
|
-
**Effective turn count.** Each LLM turn on a real task typically takes 30–90 seconds.
|
|
231
|
+
**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.
|
|
228
232
|
|
|
229
233
|
**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.
|
|
230
234
|
|
|
@@ -242,7 +246,7 @@ The state-file location is resolved with this precedence:
|
|
|
242
246
|
2. the `OPENCODE_GOAL_STATE_PATH` environment variable, if set;
|
|
243
247
|
3. the project-local default `<cwd>/.opencode/goals/state.json`.
|
|
244
248
|
|
|
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`. An explicit `stateFilePath` or `OPENCODE_GOAL_STATE_PATH` is used literally with no migration fallback.
|
|
249
|
+
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.
|
|
246
250
|
|
|
247
251
|
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.
|
|
248
252
|
|
|
@@ -314,14 +318,14 @@ Additional plugin-level options:
|
|
|
314
318
|
|
|
315
319
|
- `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.
|
|
316
320
|
- `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.
|
|
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
|
|
321
|
+
- `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.
|
|
318
322
|
- `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.
|
|
319
323
|
- `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.
|
|
320
324
|
- `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.
|
|
321
325
|
- `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
326
|
- `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
|
|
324
|
-
- `sdkShape` — OpenCode session-client argument shape: `legacy` (the default generated `PluginInput` client using `{ path, body, query }`) or `flat` (clients using `{ sessionID, ... }`).
|
|
327
|
+
- `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.
|
|
328
|
+
- `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.
|
|
325
329
|
- `persistState` — whether to persist active goals and recent goal results to disk.
|
|
326
330
|
- `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
331
|
- `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.
|
|
@@ -345,7 +349,7 @@ These operate on the same per-session multi-goal state as the command path: a to
|
|
|
345
349
|
|
|
346
350
|
## Audit messages
|
|
347
351
|
|
|
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
|
|
352
|
+
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.
|
|
349
353
|
## Completion auditor (optional)
|
|
350
354
|
|
|
351
355
|
By default a `[goal:complete]` is accepted on the assistant's word. You can require an independent audit before a goal is archived:
|
|
@@ -358,13 +362,16 @@ On **approval** the goal is archived as achieved. On **rejection** the goal is *
|
|
|
358
362
|
Pass `auditorOptions` to tune the built-in auditor:
|
|
359
363
|
|
|
360
364
|
```js
|
|
361
|
-
GoalPlugin(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
365
|
+
await GoalPlugin(
|
|
366
|
+
{ client },
|
|
367
|
+
{
|
|
368
|
+
completionAudit: true,
|
|
369
|
+
auditorOptions: {
|
|
370
|
+
timeoutMs: 60_000, // default 120 000 ms; set lower for faster CI feedback
|
|
371
|
+
failurePolicy: "reject",
|
|
372
|
+
},
|
|
366
373
|
},
|
|
367
|
-
|
|
374
|
+
)
|
|
368
375
|
```
|
|
369
376
|
|
|
370
377
|
`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.
|
|
@@ -375,9 +382,9 @@ The goal text is wrapped in `<goal_objective>` tags and labeled as user-provided
|
|
|
375
382
|
|
|
376
383
|
## Limitations
|
|
377
384
|
|
|
378
|
-
The assistant still signals candidate outcomes with `[goal:complete]` or `[goal:blocked]`. Completion can additionally be checked by a custom `
|
|
385
|
+
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.
|
|
379
386
|
|
|
380
|
-
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.
|
|
387
|
+
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. The plugin therefore guards `/goal status`, `/goal history`, `/goal list`, `/goal pause`, and `/goal clear` (including its aliases) with `tool.execute.before`: inspection tools remain available, while mutation-capable tools are rejected for that routed command turn. Paused goals also inject a system guard that omits the objective and requires an explicit resume before goal work continues.
|
|
381
388
|
|
|
382
389
|
The plugin depends on `experimental.chat.system.transform` and other OpenCode plugin hooks that may change between OpenCode versions.
|
|
383
390
|
|
|
@@ -393,7 +400,7 @@ If a goal does not continue:
|
|
|
393
400
|
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.
|
|
394
401
|
3. Check OpenCode's structured logs for persistence, SDK-shape, prompt, or auditor errors.
|
|
395
402
|
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.
|
|
396
|
-
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.
|
|
403
|
+
5. Run `npm run verify`, `npm run smoke`, and `npm run smoke:packed-host` against the installed source when diagnosing registration or packaging problems. Maintainers can run `npm run release:check` for the complete artifact and quality gate. `npm run benchmark:behavior` exercises completion, false-completion, loop, interruption, compaction, and restart behavior without a provider call.
|
|
397
404
|
|
|
398
405
|
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.
|
|
399
406
|
|
|
@@ -424,12 +431,16 @@ Keep test files outside OpenCode's auto-loaded plugin directory — OpenCode wil
|
|
|
424
431
|
```sh
|
|
425
432
|
npm test # run the test suite
|
|
426
433
|
npm run test:coverage # run tests with coverage
|
|
434
|
+
npm run type:check # compile installed-package consumers
|
|
435
|
+
npm run test:mutation # prove critical regressions are detected
|
|
427
436
|
npm run smoke # verify package export + command hook without a model call
|
|
428
437
|
npm run smoke:packed-host # install the packed tarball and exercise the host contract
|
|
438
|
+
npm run smoke:packed-tools # verify all tools from an installed tarball
|
|
429
439
|
npm run benchmark:behavior # deterministic autonomy + token-efficiency scenarios
|
|
430
440
|
npm run verify # verify the installed plugin hook surface
|
|
431
441
|
npm run check # syntax check + tests
|
|
432
442
|
npm run pack:check # verify package contents before publishing
|
|
443
|
+
npm run release:check # run the complete release gate
|
|
433
444
|
```
|
|
434
445
|
|
|
435
446
|
## License
|
package/SECURITY.md
CHANGED
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Supported Versions
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Security fixes are provided for the latest published version only. Users should
|
|
6
|
+
upgrade to the newest patch release before reporting a vulnerability.
|
|
6
7
|
|
|
7
8
|
## Reporting a Vulnerability
|
|
8
9
|
|
|
9
|
-
GitHub private vulnerability
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
1. open a minimal public issue asking for a private contact path, or
|
|
16
|
-
2. contact the maintainer through their GitHub profile and request a private handoff.
|
|
10
|
+
Use [GitHub's private vulnerability report](https://github.com/willytop8/OpenCode-goal-plugin/security/advisories/new).
|
|
11
|
+
Do **not** open a public issue with exploit details, credentials, local paths, or
|
|
12
|
+
reproduction steps that could expose user data or local system access. If private
|
|
13
|
+
reporting is temporarily unavailable, contact the maintainer through their GitHub
|
|
14
|
+
profile and request a private handoff.
|
|
17
15
|
|
|
18
16
|
## Scope
|
|
19
17
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Compatibility policy
|
|
2
|
+
|
|
3
|
+
## Supported package surface
|
|
4
|
+
|
|
5
|
+
The latest published release is the supported line. Public compatibility covers:
|
|
6
|
+
|
|
7
|
+
- the package root and `opencode-goal-plugin/server` ESM exports
|
|
8
|
+
- the declarations exported by `index.d.ts`
|
|
9
|
+
- the documented `GoalPluginOptions` fields
|
|
10
|
+
- the documented OpenCode hook names
|
|
11
|
+
- the six canonical goal tools and five legacy tool aliases
|
|
12
|
+
- persisted-state recovery from versions documented in the changelog
|
|
13
|
+
|
|
14
|
+
The package requires Node.js 18 or newer and OpenCode 1.17.15 through the latest
|
|
15
|
+
compatible 1.x release. CI runs the complete unit suite on Node 18, 20, 22, and
|
|
16
|
+
24. Installed-package contracts compile TypeScript consumers using both NodeNext
|
|
17
|
+
and Bundler resolution and load the npm tarball with and without the optional
|
|
18
|
+
`@opencode-ai/plugin` peer.
|
|
19
|
+
|
|
20
|
+
Filesystem-sensitive lifecycle tests run on Linux, macOS, and Windows. POSIX file
|
|
21
|
+
mode and symbolic-link protections are applied where the operating system supports
|
|
22
|
+
them; the plugin does not claim that Windows provides equivalent POSIX semantics.
|
|
23
|
+
|
|
24
|
+
## OpenCode host compatibility
|
|
25
|
+
|
|
26
|
+
OpenCode's experimental hooks and SDK request shapes may change within the 1.x
|
|
27
|
+
line. Automated tests cover both current flattened session inputs and the legacy
|
|
28
|
+
generated-client shape, but a real-host smoke test remains required when hook or
|
|
29
|
+
SDK behavior changes. The current manual provider matrix is maintained in
|
|
30
|
+
[providers.md](providers.md).
|
|
31
|
+
|
|
32
|
+
## Versioning
|
|
33
|
+
|
|
34
|
+
Semantic-versioning intent is:
|
|
35
|
+
|
|
36
|
+
- patch: compatible fixes, documentation, and stronger verification
|
|
37
|
+
- minor: backward-compatible options, hooks, commands, or tools
|
|
38
|
+
- major: removal or incompatible change to a documented public surface
|
|
39
|
+
|
|
40
|
+
`testInternals` is exported for diagnostics and the project's own tests; it is not
|
|
41
|
+
part of the semantic-version compatibility guarantee.
|
package/docs/providers.md
CHANGED
|
@@ -20,9 +20,9 @@ vary by provider and model:
|
|
|
20
20
|
See the [OpenCode version compatibility table](../README.md#opencode-version-compatibility)
|
|
21
21
|
in the README for the current findings.
|
|
22
22
|
|
|
23
|
-
All rows below were verified
|
|
24
|
-
|
|
25
|
-
plugin through `/goal status`, `/goal <condition> --max-turns N`, and
|
|
23
|
+
All rows below were verified against real OpenCode processes with live
|
|
24
|
+
provider credentials and no mocked plugin hooks on OpenCode 1.17.15, driving
|
|
25
|
+
the plugin through `/goal status`, `/goal <condition> --max-turns N`, and
|
|
26
26
|
inspecting the plugin's persisted state file to confirm state mutations
|
|
27
27
|
(limit parsing, turn/stop accounting, evidence-gated completion detection)
|
|
28
28
|
independent of what was rendered in the terminal.
|
|
@@ -31,10 +31,29 @@ independent of what was rendered in the terminal.
|
|
|
31
31
|
|
|
32
32
|
| Provider | Model | Marker compliance | Notes |
|
|
33
33
|
|---|---|---|---|
|
|
34
|
+
| `opencode` | `deepseek-v4-flash-free` | ✅ Canonical tools | OpenCode 1.17.15, isolated HOME/XDG/project, loading the exact 0.6.2 branch source by file URL. In a real interactive PTY, the model fixed an intentionally failing two-test project, ran the tests to 2/2 passing, and completed through `goal_complete`. A second goal checkpointed after writing `step-1`; the plugin ledger then recorded `auto-continue 1/2`, after which the model wrote and verified `step-2` and completed. Sessions: `ses_0b021d93affeCsNvWkmJWwZzF2` and `ses_0b01f3767ffej4Mn6DMSWft0aX`. `/goal status` text was routed to the model by this host version, which then called `goal_status`; the persisted ledger remained authoritative. |
|
|
34
35
|
| `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
36
|
| `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
37
|
| `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
|
|
|
39
|
+
## OpenCode 1.17.15 lifecycle canaries
|
|
40
|
+
|
|
41
|
+
The `opencode/deepseek-v4-flash-free` row was also exercised through an
|
|
42
|
+
isolated project, HOME, and XDG directories while loading the exact 0.6.2
|
|
43
|
+
source by `file://` URL. Persisted state, ledger entries, and file contents
|
|
44
|
+
were checked independently of the model's prose.
|
|
45
|
+
|
|
46
|
+
| Scenario | Result | Evidence |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| Normal completion | ✅ Pass | `ses_0b021d93affeCsNvWkmJWwZzF2`: fixed an intentionally failing project, reran 2/2 tests, and completed with structured evidence. |
|
|
49
|
+
| Idle auto-continuation | ✅ Pass | `ses_0b01f3767ffej4Mn6DMSWft0aX`: checkpointed `step-1`, ledger recorded auto-continue 1/2, then verified `step-2` and completed. |
|
|
50
|
+
| Pause and explicit resume across processes | ✅ Pass | `ses_0b01470c1ffeug0LX69fageiVm`: remained paused between OpenCode invocations; explicit resume released it and completion was archived. |
|
|
51
|
+
| Concrete blocker and restart | ✅ Pass | `ses_0b013a903ffeg3I6tGbArE2b5E`: stopped with the missing approval-file reason and did not auto-continue after a fresh process loaded it. |
|
|
52
|
+
| Hard process interruption and recovery | ✅ Pass | `ses_0b00b37a8ffeDrBshGDutFTqUm`: a running process was terminated during a shell wait; restart recovered paused, status blocked a stale `goal_resume`, the file stayed unchanged, and a later explicit resume completed. |
|
|
53
|
+
| Real host compaction | ✅ Pass | `ses_0b00958e3ffekLLH5ztkkvHIPL`: the documented session summarize endpoint returned `true`; exported session data contained a real compaction part plus injected goal/checkpoint context, token accounting reset, and the paused goal resumed cleanly. |
|
|
54
|
+
| Clear with stale conversation history | ✅ Pass | `ses_0affb9235ffeWl5LuZxXmvhSfM`: after command-side clearing, the routed model turn attempted `clear_goal` and `goal_resume`; the tool hook rejected all attempts, the file remained `before-clear`, and no goal survived in state. |
|
|
55
|
+
| Interactive Esc during a running shell tool | ⚠️ Not established | Esc sent through the automated PTY did not interrupt OpenCode's shell tool. Hard-process recovery is verified above and host-abort hooks have deterministic tests, but this specific TUI input path is not claimed as passed. |
|
|
56
|
+
|
|
38
57
|
Untested at time of writing: `deepseek-reasoner`, `mistral/*`, `openrouter/*`,
|
|
39
58
|
and any `nvidia`/`google` provider — add rows here as they're verified. See
|
|
40
59
|
[Testing a new model](#testing-a-new-model) below.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Release process
|
|
2
|
+
|
|
3
|
+
Releases are deliberately verified before they are published. GitHub Actions does
|
|
4
|
+
not publish to npm automatically; the maintainer reviews and publishes the exact
|
|
5
|
+
artifact after all checks pass.
|
|
6
|
+
|
|
7
|
+
## Prepare
|
|
8
|
+
|
|
9
|
+
1. Start from a clean branch based on `main`.
|
|
10
|
+
2. Update the version in `package.json` and `package-lock.json` together.
|
|
11
|
+
3. Move relevant entries from `Unreleased` into a dated changelog section.
|
|
12
|
+
4. Run `npm ci` followed by `npm run release:check`.
|
|
13
|
+
5. Inspect `npm pack --json` and the generated tarball before publishing.
|
|
14
|
+
|
|
15
|
+
`release:check` runs the unit and coverage suites, consumer type compilation,
|
|
16
|
+
critical mutation contract, behavior benchmark, source and installed-artifact
|
|
17
|
+
smoke tests, full optional-peer tool registration, and package-content check.
|
|
18
|
+
|
|
19
|
+
## Publish
|
|
20
|
+
|
|
21
|
+
After the commit is reviewed and CI is green, create and push an annotated
|
|
22
|
+
`vX.Y.Z` tag at the same commit. The release workflow rejects a tag whose version
|
|
23
|
+
does not match `package.json`, reruns the complete release gate, and retains the
|
|
24
|
+
verified npm tarball as a workflow artifact.
|
|
25
|
+
|
|
26
|
+
Download that artifact, inspect it, and publish the tarball itself:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm publish opencode-goal-plugin-X.Y.Z.tgz --access public
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Confirm the registry digest and unpacked contents match the locally reviewed
|
|
33
|
+
artifact before drafting the GitHub release notes. Never rebuild or edit an
|
|
34
|
+
artifact after it has been published; prepare a new patch release instead.
|
|
35
|
+
|
|
36
|
+
## Trusted publishing
|
|
37
|
+
|
|
38
|
+
For a future fully automated publish, configure npm Trusted Publishing for this
|
|
39
|
+
repository and a narrowly scoped GitHub Actions workflow, keep `id-token: write`
|
|
40
|
+
only on the publish job, require the release environment, and publish with
|
|
41
|
+
provenance. Do not add a long-lived npm token to repository secrets.
|
package/index.d.ts
CHANGED
|
@@ -17,10 +17,58 @@ export interface CompletionAuditVerdict {
|
|
|
17
17
|
reason?: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** A timestamped lifecycle entry retained with an active or archived goal. */
|
|
21
|
+
export interface GoalHistoryEntry {
|
|
22
|
+
type: string
|
|
23
|
+
detail: string
|
|
24
|
+
timestamp: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A bounded progress checkpoint retained with a goal. */
|
|
28
|
+
export interface GoalCheckpoint {
|
|
29
|
+
summary: string
|
|
30
|
+
timestamp: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Normalized token and cost usage accumulated for the current goal run. */
|
|
34
|
+
export interface GoalUsage {
|
|
35
|
+
input: number
|
|
36
|
+
output: number
|
|
37
|
+
reasoning: number
|
|
38
|
+
cacheRead: number
|
|
39
|
+
cacheWrite: number
|
|
40
|
+
cost: number
|
|
41
|
+
costKnown: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Read-only goal snapshot passed to custom completion auditors. */
|
|
45
|
+
export interface GoalAuditSnapshot {
|
|
46
|
+
goalId: string
|
|
47
|
+
runId: string
|
|
48
|
+
condition: string
|
|
49
|
+
successCriteria: string
|
|
50
|
+
constraints: string
|
|
51
|
+
mode: "normal" | "sisyphus"
|
|
52
|
+
sessionID: string
|
|
53
|
+
turnCount: number
|
|
54
|
+
startedAt: number
|
|
55
|
+
pausedAt: number
|
|
56
|
+
totalTokens: number
|
|
57
|
+
usage: Readonly<GoalUsage>
|
|
58
|
+
options: Readonly<GoalPluginOptions>
|
|
59
|
+
lastStatus: string
|
|
60
|
+
blockedReason: string
|
|
61
|
+
stopped: boolean
|
|
62
|
+
stopReason: string
|
|
63
|
+
history: readonly Readonly<GoalHistoryEntry>[]
|
|
64
|
+
checkpoints: readonly Readonly<GoalCheckpoint>[]
|
|
65
|
+
lastCheckpoint: Readonly<GoalCheckpoint> | null
|
|
66
|
+
}
|
|
67
|
+
|
|
20
68
|
/** Arguments passed to a custom {@link GoalPluginOptions.auditor} function. */
|
|
21
69
|
export interface CompletionAuditContext {
|
|
22
70
|
/** The goal being audited (objective, budget usage, checkpoints, etc.). */
|
|
23
|
-
goal:
|
|
71
|
+
goal: Readonly<GoalAuditSnapshot>
|
|
24
72
|
/** The OpenCode session ID the goal belongs to. */
|
|
25
73
|
sessionID: string
|
|
26
74
|
/** The assistant's latest response text, containing the `[goal:evidence]`/`[goal:complete]` claim. */
|
|
@@ -121,7 +169,7 @@ export interface GoalPluginOptions {
|
|
|
121
169
|
* Grace window for tool-free continuation turns (a "talk only" turn that
|
|
122
170
|
* calls no tool). Complements the no-progress check by catching
|
|
123
171
|
* self-chat loops that still produce output. Overridable per-goal with
|
|
124
|
-
* `--no-tool-turns`.
|
|
172
|
+
* `--no-tool-turns`. Set the plugin option to `0` to disable this heuristic.
|
|
125
173
|
* @default 2
|
|
126
174
|
*/
|
|
127
175
|
noToolCallTurnsBeforePause?: number
|
|
@@ -283,10 +331,10 @@ export interface GoalPluginOptions {
|
|
|
283
331
|
|
|
284
332
|
/**
|
|
285
333
|
* Custom sink for audit announcements. Defaults to routing through
|
|
286
|
-
* OpenCode's structured log (`client.app.log`)
|
|
287
|
-
*
|
|
334
|
+
* OpenCode's structured log (`client.app.log`) and TUI toast when those
|
|
335
|
+
* host APIs are available. Provide this to route audit messages elsewhere.
|
|
288
336
|
*/
|
|
289
|
-
auditMessenger?: (sessionID: string, text: string) => Promise<void>
|
|
337
|
+
auditMessenger?: (sessionID: string, text: string) => Promise<void> | void
|
|
290
338
|
}
|
|
291
339
|
|
|
292
340
|
/**
|
|
@@ -300,6 +348,8 @@ export interface GoalPluginHooks {
|
|
|
300
348
|
config: (config: unknown) => Promise<void>
|
|
301
349
|
/** Omitted entirely when {@link GoalPluginOptions.registerCommand} is `false`. */
|
|
302
350
|
"command.execute.before"?: (input: unknown, output: unknown) => Promise<void>
|
|
351
|
+
/** Enforces read-only tool behavior when inspection, pause, or clear command text is routed to the model. */
|
|
352
|
+
"tool.execute.before": (input: unknown, output: unknown) => Promise<void>
|
|
303
353
|
event: (input: unknown) => Promise<void>
|
|
304
354
|
"experimental.chat.system.transform": (input: unknown, output: unknown) => Promise<void>
|
|
305
355
|
"experimental.compaction.autocontinue": (input: unknown, output: unknown) => Promise<void>
|
|
@@ -312,7 +362,6 @@ export interface GoalPluginHooks {
|
|
|
312
362
|
tool?: Record<string, unknown>
|
|
313
363
|
/** Cancels pending continuation work and releases this plugin instance. */
|
|
314
364
|
dispose: () => Promise<void>
|
|
315
|
-
[hook: string]: unknown
|
|
316
365
|
}
|
|
317
366
|
|
|
318
367
|
/**
|
|
@@ -331,6 +380,9 @@ export function GoalPlugin(
|
|
|
331
380
|
options?: GoalPluginOptions,
|
|
332
381
|
): Promise<GoalPluginHooks>
|
|
333
382
|
|
|
383
|
+
/** Internal diagnostic/test helpers. Not covered by semantic-version compatibility guarantees. */
|
|
384
|
+
export const testInternals: Readonly<Record<string, unknown>>
|
|
385
|
+
|
|
334
386
|
/**
|
|
335
387
|
* Default export consumed by OpenCode's plugin loader:
|
|
336
388
|
* `{ "opencode-goal-plugin": { ... } }` in `opencode.json` resolves `id`
|