diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d6c7fd4..1433bcc 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,6 +1,6 @@ -# Architecture (0.9.0) +# Architecture (0.9) -Goal Mode is a **single server plugin** plus **goal** agent/command files. It targets the normal OpenCode config tree — not a separate app. +Goal Mode is a **single server plugin** plus a **goal** agent file. It targets the normal OpenCode config tree — not a separate app. ## Data flow @@ -11,7 +11,7 @@ opencode.jsonc plugins/goal-mode.js { id: "opencode-goal-mode", server() } ↓ plugins/goal-mode/plugin.js → hooks + tools -agents/goal.md → primary agent (OpenCode native); goals via tools only +agents/goal.md → primary agent; goals via tools only ``` ## When the loop runs @@ -46,6 +46,8 @@ event: session.status (idle) or session.idle | `abortSuppressMs` | User cancel → no immediate synthetic continue | | `MAX_CONSECUTIVE_CONTINUATIONS` (8) | Hard cap on chained auto-continues | | `maxTurns` | Optional user cap | +| `idleTimeoutMs` | Cap one idle-resolve attempt | +| `evaluatorRetryLimit` / `evaluatorRetryIntervalMs` | Bounded retries when evaluator is down | | Evaluator failure | Fail open — no infinite retry storm | | `completionMarker` | Fallback achieve if evaluator unreachable | @@ -71,7 +73,7 @@ plugins/goal-mode/ | --- | --- | | `chat.params` | Track goal agent + model for evaluator default | | `chat.message` | Clear abort flag on real user message; track agent | -| `event` | `session.idle`, `session.error` (abort) | +| `event` | `session.idle` / `session.status` (idle), `session.error` (abort) | | `tool` | Register goal_* tools | | `experimental.session.compacting` | Keep condition + last eval reason in compaction context | @@ -89,4 +91,4 @@ Long sessions: compaction hook injects active condition and last evaluator note ## OpenCode plugin contract File plugins using the v1 shape must export **`id`**. This package uses **`opencode-goal-mode`**. -The TUI plugin list only shows TUI plugins — this server plugin will not appear there; that is expected. \ No newline at end of file +The TUI plugin list only shows TUI plugins — this server plugin will not appear there; that is expected. diff --git a/CHANGELOG.md b/CHANGELOG.md index f194712..c5de5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.9.7 — 2026-07-08 + +**Patch release** — documentation accuracy and small lint cleanups. + +### Fixed + +- Documented all plugin config keys (`maxConditionLength`, `transcriptMaxChars`, `idleTimeoutMs`, `evaluatorRetryLimit`, `evaluatorRetryIntervalMs`) in README and CUSTOMIZE. +- Removed stale references to a missing sibling test harness, slash-command files, and “built into OpenCode” wording. +- Corrected config-precedence comment in `config.js` to match the `./plugins/goal-mode.js` install path. +- Deduplicated `publishConfig` in `package.json`. +- Removed unused `execSync` import from `postinstall.mjs`; optional-chained a null check in `sdk-session.js`. + +--- + ## 0.9.6 — 2026-07-08 **Patch release** — dependency refresh plus release housekeeping. diff --git a/CUSTOMIZE.md b/CUSTOMIZE.md index a45ccf0..a2760a4 100644 --- a/CUSTOMIZE.md +++ b/CUSTOMIZE.md @@ -25,11 +25,16 @@ Goal Mode 0.9 is **config + markdown**. Change numbers in `opencode.jsonc`, chan | `noProgressLimit` | `3` | Pause after N evaluator NOs with identical reason | | `abortSuppressMs` | `120000` | Suppress auto-continue after user cancel | | `idleGraceMs` | `1200` | Delay after idle before evaluate | +| `maxConditionLength` | `4000` | Max characters in a `goal_set` condition (longer values are truncated) | +| `transcriptMaxChars` | `24000` | Max characters of transcript sent to the evaluator (bookend truncation) | +| `idleTimeoutMs` | `120000` | Max time for one idle-resolve attempt before releasing the lock | +| `evaluatorRetryLimit` | `5` | How many times to retry when the evaluator is unavailable | +| `evaluatorRetryIntervalMs` | `15000` | Delay between evaluator-unavailable retries | | `completionMarker` | `Goal Completed` | Assistant prefix when evaluator unavailable | Booleans: `true`/`false`/`1`/`0`/`yes`/`no`/`on`/`off`. -## Agent & command (behavior) +## Agent (behavior) | File | Customize | | --- | --- | @@ -41,7 +46,7 @@ Symlink for dev: node scripts/install.mjs --global --link ``` -## Native install paths +## Install paths ```text ~/.config/opencode/agents/goal.md @@ -70,4 +75,4 @@ These were 0.8 / goal-guard ideas — **removed**: ## Code changes -See [ARCHITECTURE.md](./ARCHITECTURE.md). Run `npm test` and `npm run lint` after behavioral edits. \ No newline at end of file +See [ARCHITECTURE.md](./ARCHITECTURE.md). Run `npm test` and `npm run lint` after behavioral edits. diff --git a/GOAL.md b/GOAL.md index da38f96..8d93bff 100644 --- a/GOAL.md +++ b/GOAL.md @@ -1,6 +1,6 @@ # How Goal Mode works -Goal Mode is meant to feel **simple** in a **native** OpenCode setup: one agent markdown file, one command, one server plugin. Everything else is OpenCode as you already use it. +Goal Mode is meant to feel **simple** in a normal OpenCode setup: one agent markdown file, one server plugin. Everything else is OpenCode as you already use it. ## The cast @@ -63,9 +63,9 @@ Even with an active goal, the plugin **pauses** when: Evaluator errors **fail open** — you keep the session; the loop does not brick OpenCode. -## What “native OpenCode” means here +## What this package adds -- **Agents** and **commands** are plain markdown in `~/.config/opencode/`. +- **Agent** is plain markdown in `~/.config/opencode/agents/`. - **Plugin** is a local `./plugins/goal-mode.js` entry in `opencode.jsonc`, not a TUI npm package. - **Permissions** for the goal agent are the normal OpenCode `permission:` block in `agents/goal.md`. - **Providers/models** are your existing OpenCode config — Goal Mode only adds an optional `evaluatorModel`. @@ -79,10 +79,6 @@ Think **Claude Code `/goal`**: one worker, one judge after each step, same chat ```bash # Unit tests (no API) npm test - -# Isolated TUI (recommended while hacking the plugin) -cd ../goal-mode-opencode-test-instance -./opencode-test.sh # real Terminal.app / iTerm — needs a TTY ``` -In TUI: agent **goal**, ask for a tiny goal (agent **`goal_set`** “reply contains exactly DONE”), watch idle → continue until YES or you clear it. \ No newline at end of file +In OpenCode: agent **goal**, ask for a tiny goal (agent **`goal_set`** “reply contains exactly DONE”), watch idle → continue until YES or you clear it. diff --git a/README.md b/README.md index 0b1382a..93863ad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OpenCode Goal Mode -**Simple, native Goal Mode for [OpenCode](https://opencode.ai).** +**Simple Goal Mode for [OpenCode](https://opencode.ai).** One primary agent, one small server plugin, three tools — no npm sidebar, no command guard, no reviewer army. [![license](https://img.shields.io/npm/l/opencode-goal-mode)](LICENSE) @@ -18,9 +18,9 @@ That is the whole product: **work → idle → judge → continue or stop**. Casual chat without `goal_set` behaves like a normal agent — no hidden loops. -## Native OpenCode layout +## OpenCode layout -OpenCode already knows how to load this. You only need files under your config directory: +OpenCode loads this from your config directory: ```text ~/.config/opencode/ @@ -111,6 +111,11 @@ See [CUSTOMIZE.md](./CUSTOMIZE.md). Short version: | `noProgressLimit` | `3` | Pause if evaluator repeats the same NO | | `abortSuppressMs` | `120000` | After user cancel, no auto-continue | | `idleGraceMs` | `1200` | Wait after idle before evaluating | +| `maxConditionLength` | `4000` | Max chars in a `goal_set` condition | +| `transcriptMaxChars` | `24000` | Max transcript chars sent to evaluator | +| `idleTimeoutMs` | `120000` | Cap one idle-resolve attempt | +| `evaluatorRetryLimit` | `5` | Retries when evaluator is unavailable | +| `evaluatorRetryIntervalMs` | `15000` | Delay between those retries | | `completionMarker` | `Goal Completed` | Fallback if evaluator unavailable | Environment: `GOAL_MODE_*` mirrors keys (`GOAL_MODE_EVALUATOR_MODEL`, etc.). @@ -122,16 +127,15 @@ Read [GOAL.md](./GOAL.md) for the user-facing walkthrough, or [ARCHITECTURE.md]( ## Development ```bash +npm ci npm test npm run lint ``` -Isolated test harness: `goal-mode-opencode-test-instance/opencode-test.sh` (see that folder’s `NOTES.md`). - ## Publishing (maintainers) - Tag a release: `git tag -a vX.Y.Z -m "..." && git push origin vX.Y.Z` -- The `publish.yml` workflow runs on the tag and publishes to npm with provenance. +- The `publish.yml` workflow runs on the tag and publishes to npm. - README + package.json already expose the `opencode-goal-mode` and `opencode-goal-mode-install` bins. ## License diff --git a/agents/goal.md b/agents/goal.md index 744ec6d..01d1c14 100644 --- a/agents/goal.md +++ b/agents/goal.md @@ -1,5 +1,5 @@ --- -description: Native OpenCode Goal Mode — set a verifiable finish line with goal_set; auto-continue on idle until an evaluator says YES. +description: OpenCode Goal Mode — set a verifiable finish line with goal_set; auto-continue on idle until an evaluator says YES. mode: primary color: error permission: diff --git a/package.json b/package.json index d3c0812..69cf00b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "opencode-goal-mode", - "version": "0.9.6", - "description": "OpenCode Goal Mode 0.9 — native goal agent + server plugin: goal_set, idle evaluator, auto-continue until YES. Simple, no sidebar, no guard.", + "version": "0.9.7", + "description": "OpenCode Goal Mode 0.9 — goal agent + server plugin: goal_set, idle evaluator, auto-continue until YES. Simple, no sidebar, no guard.", "type": "module", "exports": { "./package.json": "./package.json" @@ -14,13 +14,6 @@ "opencode-goal-mode": "scripts/install.mjs", "opencode-goal-mode-install": "scripts/install.mjs" }, - "publishConfig": { - "access": "public", - "bin": { - "opencode-goal-mode": "scripts/install.mjs", - "opencode-goal-mode-install": "scripts/install.mjs" - } - }, "files": [ "agents/", "plugins/", @@ -67,4 +60,4 @@ "devDependencies": { "@biomejs/biome": "2.5.2" } -} \ No newline at end of file +} diff --git a/plugins/goal-mode/config.js b/plugins/goal-mode/config.js index d856d1c..3071464 100644 --- a/plugins/goal-mode/config.js +++ b/plugins/goal-mode/config.js @@ -4,7 +4,7 @@ * Precedence (lowest → highest): * 1. DEFAULT_CONFIG below * 2. GOAL_MODE_* environment variables - * 3. Plugin options from opencode.json: ["opencode-goal-mode", { ... }] + * 3. Plugin options from opencode.jsonc: ["./plugins/goal-mode.js", { ... }] * * There are deliberately NO guardrail keys: Goal Mode never blocks commands, * never rewrites messages, and never launches reviewer subagents. The only diff --git a/plugins/goal-mode/sdk-session.js b/plugins/goal-mode/sdk-session.js index 01b4e2d..2efd538 100644 --- a/plugins/goal-mode/sdk-session.js +++ b/plugins/goal-mode/sdk-session.js @@ -88,7 +88,7 @@ export function sessionTransport(client, http) { // SDK responses are { data, error } and do NOT throw on API errors — treat // a present .error (or missing .data) as failure so fallbacks engage. function unwrapStrict(res) { - if (res && res.error) { + if (res?.error) { throw new Error(String(res.error?.message || res.error?.name || JSON.stringify(res.error).slice(0, 200))); } return unwrap(res); diff --git a/scripts/install.mjs b/scripts/install.mjs index 9b3a507..3b935ea 100755 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** - * Optional helper — OpenCode already loads goal mode natively when these paths exist: + * Optional helper — OpenCode loads goal mode when these paths exist under the config dir: * * ~/.config/opencode/agents/goal.md * ~/.config/opencode/plugins/goal-mode.js (+ plugins/goal-mode/) @@ -66,7 +66,7 @@ const MANIFEST_NAME = ".goal-mode-manifest.json"; if (values.help) { console.log(`OpenCode Goal Mode — install helper (optional) -OpenCode discovers goal mode natively under your config directory: +OpenCode discovers goal mode under your config directory: agents/goal.md, plugins/goal-mode.js Optional plugin options in opencode.jsonc (relative to that config dir): @@ -225,7 +225,7 @@ Use --force to replace it.`); } } if (!dryRun && existsSync(manifestPath)) rmSync(manifestPath, { force: true }); - console.log(`\nRestart OpenCode. Plugin loads from plugins/goal-mode.js (native discovery).`); + console.log(`\nRestart OpenCode. Plugin loads from plugins/goal-mode.js.`); console.log(`Optional: add [["./plugins/goal-mode.js", { ... }]] to opencode.jsonc for options.`); console.log(`To uninstall later: opencode-goal-mode-install --global --uninstall`); } @@ -339,7 +339,7 @@ function doInstallCopy() { console.log(`${dryRun ? "Would install" : "Installed"} OpenCode Goal Mode ${pkg.version} into ${target}`); console.log(`Copied: ${summary.copied.length}; unchanged: ${summary.unchanged.length}; pruned: ${summary.pruned.length}`); - console.log("\nRestart OpenCode. Native paths: agents/, plugins/goal-mode.js"); + console.log("\nRestart OpenCode. Config paths: agents/, plugins/goal-mode.js"); console.log(`To uninstall later: opencode-goal-mode-install --global --uninstall`); } diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs index cf10e52..4a83f59 100644 --- a/scripts/postinstall.mjs +++ b/scripts/postinstall.mjs @@ -6,7 +6,6 @@ * layout or multiple OpenCode configs). Instead we print a short, copy-pasteable * one-liner so the user can finish the install in one step. */ -import { execSync } from "node:child_process"; const isGlobal = process.env.npm_config_global === "true" || process.argv.includes("--global");