Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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 |

Expand All @@ -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 |

Expand All @@ -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.
The TUI plugin list only shows TUI plugins — this server plugin will not appear there; that is expected.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
11 changes: 8 additions & 3 deletions CUSTOMIZE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| --- | --- |
Expand All @@ -41,7 +46,7 @@ Symlink for dev:
node scripts/install.mjs --global --link
```

## Native install paths
## Install paths

```text
~/.config/opencode/agents/goal.md
Expand Down Expand Up @@ -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.
See [ARCHITECTURE.md](./ARCHITECTURE.md). Run `npm test` and `npm run lint` after behavioral edits.
12 changes: 4 additions & 8 deletions GOAL.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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`.
Expand All @@ -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.
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.
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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/
Expand Down Expand Up @@ -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.).
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion agents/goal.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
13 changes: 3 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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/",
Expand Down Expand Up @@ -67,4 +60,4 @@
"devDependencies": {
"@biomejs/biome": "2.5.2"
}
}
}
2 changes: 1 addition & 1 deletion plugins/goal-mode/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion plugins/goal-mode/sdk-session.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions scripts/install.mjs
Original file line number Diff line number Diff line change
@@ -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/)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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`);
}
Expand Down Expand Up @@ -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`);
}

Expand Down
1 change: 0 additions & 1 deletion scripts/postinstall.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down