opencode-goal-plugin 0.6.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 CHANGED
@@ -2,6 +2,18 @@
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
+
5
17
  ## 0.6.0 — 2026-07-10
6
18
 
7
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.
@@ -147,7 +159,7 @@
147
159
  ### Storage, tools & packaging
148
160
 
149
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.
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 actually ships; see the **Unreleased** section above. Megalist items 7.1 and 7.2._
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._
151
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.
152
164
 
153
165
  ## 0.2.0 — 2026-06-14
package/README.md CHANGED
@@ -207,7 +207,7 @@ The deploy step needs a production API token I don't have.
207
207
  [goal:blocked]
208
208
  ```
209
209
 
210
- `[goal:complete]` — goal is satisfied. It is **only honored when the line immediately before it (or an earlier line) begins with `[goal:evidence]` and contains a non-empty summary** of what was verified (commands run and their results, files checked). A `[goal:complete]` with no `[goal:evidence]` line is rejected, not recorded, and the plugin re-prompts for evidence. The accepted evidence is shown in `/goal status` after completion.
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.
211
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.
212
212
 
213
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.
@@ -224,7 +224,7 @@ Markers must appear on their own final line. The bracketed form is canonical, bu
224
224
  | Budget wrap-up threshold | 80% of context token budget |
225
225
  | Auto-continue failure pause | 3 consecutive prompt failures |
226
226
 
227
- **Effective turn count.** Each LLM turn on a real task typically takes 30–90 seconds. At that latency, raising `--max-minutes` is usually more useful than raising `--max-turns`. At 45 s/turn, the default 15-minute window gives roughly 15–20 turns of headroom before the turn limit becomes the binding brake.
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.
228
228
 
229
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.
230
230
 
@@ -242,7 +242,7 @@ The state-file location is resolved with this precedence:
242
242
  2. the `OPENCODE_GOAL_STATE_PATH` environment variable, if set;
243
243
  3. the project-local default `<cwd>/.opencode/goals/state.json`.
244
244
 
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.
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.
246
246
 
247
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.
248
248
 
@@ -314,14 +314,14 @@ Additional plugin-level options:
314
314
 
315
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.
316
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.
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`.
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.
318
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.
319
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.
320
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.
321
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
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 workspace and goal mutation tools disabled.
324
- - `sdkShape` — OpenCode session-client argument shape: `legacy` (the default generated `PluginInput` client using `{ path, body, query }`) or `flat` (clients using `{ sessionID, ... }`). The plugin remembers a successful shape per operation and only falls back after an explicit argument/schema `TypeError`; provider and transport errors are never retried as shape mismatches.
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.
325
325
  - `persistState` — whether to persist active goals and recent goal results to disk.
326
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
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.
@@ -345,7 +345,7 @@ These operate on the same per-session multi-goal state as the command path: a to
345
345
 
346
346
  ## Audit messages
347
347
 
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 delivered through OpenCode's structured log (`client.app.log`, visible to the user). Provide an `auditMessenger(sessionID, text)` plugin option to route them elsewhere (for example into the live conversation once a suitable message API is available), or set `auditMessages: false` to disable them.
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.
349
349
  ## Completion auditor (optional)
350
350
 
351
351
  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 +358,16 @@ On **approval** the goal is archived as achieved. On **rejection** the goal is *
358
358
  Pass `auditorOptions` to tune the built-in auditor:
359
359
 
360
360
  ```js
361
- GoalPlugin({
362
- completionAudit: true,
363
- auditorOptions: {
364
- timeoutMs: 60_000, // default 120 000 ms; set lower for faster CI feedback
365
- failurePolicy: "reject",
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
+ },
366
369
  },
367
- })
370
+ )
368
371
  ```
369
372
 
370
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.
@@ -375,7 +378,7 @@ The goal text is wrapped in `<goal_objective>` tags and labeled as user-provided
375
378
 
376
379
  ## Limitations
377
380
 
378
- The assistant still signals candidate outcomes with `[goal:complete]` or `[goal:blocked]`. Completion can additionally be checked by a custom `completionAudit` 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.
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.
379
382
 
380
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.
381
384
 
package/index.d.ts CHANGED
@@ -121,7 +121,7 @@ export interface GoalPluginOptions {
121
121
  * Grace window for tool-free continuation turns (a "talk only" turn that
122
122
  * calls no tool). Complements the no-progress check by catching
123
123
  * self-chat loops that still produce output. Overridable per-goal with
124
- * `--no-tool-turns`.
124
+ * `--no-tool-turns`. Set the plugin option to `0` to disable this heuristic.
125
125
  * @default 2
126
126
  */
127
127
  noToolCallTurnsBeforePause?: number
@@ -331,6 +331,9 @@ export function GoalPlugin(
331
331
  options?: GoalPluginOptions,
332
332
  ): Promise<GoalPluginHooks>
333
333
 
334
+ /** Internal diagnostic/test helpers. Not covered by semantic-version compatibility guarantees. */
335
+ export const testInternals: Readonly<Record<string, unknown>>
336
+
334
337
  /**
335
338
  * Default export consumed by OpenCode's plugin loader:
336
339
  * `{ "opencode-goal-plugin": { ... } }` in `opencode.json` resolves `id`
package/package.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "name": "opencode-goal-plugin",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Durable, guarded goal workflows for OpenCode.",
5
5
  "type": "module",
6
6
  "main": "./src/goal-plugin.js",
7
7
  "types": "./index.d.ts",
8
8
  "exports": {
9
- ".": "./src/goal-plugin.js",
10
- "./server": "./src/goal-plugin.js"
9
+ ".": {
10
+ "types": "./index.d.ts",
11
+ "import": "./src/goal-plugin.js",
12
+ "default": "./src/goal-plugin.js"
13
+ },
14
+ "./server": {
15
+ "types": "./index.d.ts",
16
+ "import": "./src/goal-plugin.js",
17
+ "default": "./src/goal-plugin.js"
18
+ }
11
19
  },
12
20
  "bin": {
13
21
  "opencode-goal-plugin": "./scripts/verify.mjs"
@@ -52,7 +60,8 @@
52
60
  },
53
61
  "license": "MIT",
54
62
  "engines": {
55
- "node": ">=18"
63
+ "node": ">=18",
64
+ "opencode": ">=1.17.15 <2"
56
65
  },
57
66
  "repository": {
58
67
  "type": "git",