Skip to content
Closed
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
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

**Plugin for [OpenCode](https://github.com/anomalyco/opencode) that automatically detects and recovers from LLM session failures — stalls, broken tool calls, hallucination loops, stuck subagent parents, and more. Fully silent, zero UI pollution.**

> **OpenCode v2 is here (stable).** This repo ships both the v1 plugin
> (`src/index.ts`) and a complete v2 port (`src/v2/index.ts`) targeting the
> stable `@opencode/plugin` 2.0.5 API. Install it with the
> [v2 install guide](docs/v2/installing.md); see
> [docs/v2/migration.md](docs/v2/migration.md) for the full migration notes.

## What it does

LLM sessions fail in predictable ways. This plugin monitors all sessions and automatically recovers without user intervention. Each recovery path below references the upstream OpenCode issues that motivated it — these are problems not yet resolved in the official project.
Expand Down Expand Up @@ -303,6 +309,31 @@ With options:
}
```

### OpenCode v2 (stable)

The v2 plugin uses the `Plugin.define` API with `ctx.event.subscribe()` (AsyncIterable) instead of the v1 hooks-object pattern, and targets the stable **`@opencode/plugin` 2.0.5** API (opencode v2.0.5+). Add to your `opencode.json`:

```jsonc
{
"plugins": [
{
"package": "./plugins/auto-resume-v2.ts",
"options": {
"chunkTimeoutMs": 45000,
"maxRetries": 3
}
}
]
}
```

Or place `src/v2/index.ts` directly in `~/.config/opencode/plugins/` for auto-discovery (no config entry needed).

Disable via `"-auto-resume.v2"` in `plugins`.

- **Step-by-step install guide:** [docs/v2/installing.md](docs/v2/installing.md)
- **Migration notes (v1 → v2, stable validation):** [docs/v2/migration.md](docs/v2/migration.md)

### Configurable options

| Option | Default | Description |
Expand Down
165 changes: 165 additions & 0 deletions docs/v2/installing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Installing opencode-auto-resume on OpenCode v2

This guide installs the **v2 port** of the plugin. It targets the stable v2
plugin API (`@opencode/plugin` 2.0.5, shipped with `opencode` v2.0.5).

> Coming from OpenCode v1? Read [`migration.md`](./migration.md) first — the v1
> plugin file does **not** run on v2 and must be replaced by the v2 port.

## Requirements

- **opencode v2.0.5 or newer** (`opencode --version`).
- **`@opencode/plugin` must be resolvable by opencode.** A local plugin is loaded
with a normal ESM import, so the API package has to be installed next to it
(see [Step 1](#step-1--install-the-plugin-api-package)). Without it opencode
logs `failed to load plugin … Cannot find package '@opencode/plugin'`.
- Node.js/Bun is only needed to install the API package and run the
test/typecheck tooling; opencode itself loads the plugin.
- The plugin source: [`src/v2/index.ts`](../../src/v2/index.ts) from this repo.

## Install

### Step 1 — install the plugin API package

Global plugins resolve imports from `~/.config/opencode`, so install the stable
API package there once:

```sh
cd ~/.config/opencode
bun add @opencode/plugin@2.0.5 # or: npm install @opencode/plugin@2.0.5
```

For a per-project plugin, install it in the project instead:

```sh
npm install @opencode/plugin@2.0.5
```

### Step 2 — add the plugin

#### Option A — drop-in file (no config)

OpenCode auto-loads every plugin found in these directories:

- Global: `~/.config/opencode/plugins/`
- Per project: `<project>/.opencode/plugins/`

Copy the v2 port there:

```sh
mkdir -p ~/.config/opencode/plugins
cp src/v2/index.ts ~/.config/opencode/plugins/auto-resume-v2.ts
```

That's it — no `opencode.json` change is required. Restart opencode (or reload
plugins) and the plugin is active.

#### Option B — `opencode.json(c)` entry

Use this when you want to load the file from another location, pass options, or
pin a published package. Add an entry to the `plugins` array:

```jsonc title="~/.config/opencode/opencode.jsonc"
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
// 1. plain path (relative to the config file) or absolute path
"./plugins/auto-resume-v2.ts",

// 2. with options
{
"package": "./plugins/auto-resume-v2.ts",
"options": {
"chunkTimeoutMs": 45000,
"maxRetries": 3
}
}
]
}
```

Both `.opencode/plugin/` (v1 directory name) and `.opencode/plugins/` are
discovered; use `.opencode/plugins/` for v2 files.

### Step 3 — restart opencode

Restart opencode after installing. The plugin loader resolves plugin
dependencies when the server starts, so a hot file reload is **not** enough to
pick up a newly installed `@opencode/plugin` package.

## Options

All options are optional and are read from `ctx.options` in `setup`.

| Option | Default | Description |
|---|---|---|
| `chunkTimeoutMs` | `45000` | Silence on a **busy** session before recovery is considered. |
| `gracePeriodMs` | `3000` | Extra grace added to the timeout before acting. |
| `checkIntervalMs` | `5000` | Watchdog polling interval. |
| `maxRetries` | `3` | Resume attempts per stall before escalating. |
| `baseBackoffMs` | `1000` | Base delay for exponential backoff between attempts. |
| `maxBackoffMs` | `8000` | Ceiling for the backoff delay. |
| `loopMaxContinues` | `3` | Continues allowed inside `loopWindowMs` before forcing interrupt + resume. |
| `loopWindowMs` | `600000` | Window (ms) for the hallucination-loop guard (default 10 min). |
| `maxRecoveryRetries` | `2` | Cap for targeted recovery prompts (tool-as-text / intent nudges). |
| `continuePrompt` | `"continue"` | Prompt used for a plain resume / ready-to-continue nudge. |
| `toolTextRecoveryPrompt` | _(built-in)_ | Prompt used when a tool call is printed as text instead of executed. |
| `doneWithoutWorkPrompt` | _(built-in)_ | Prompt used to verify a suspicious terse "done" claim. |
| `actionIntentPrompt` | _(falls back to `continuePrompt`)_ | Prompt used when the model ends with an unexecuted intent. |
| `debug` | `false` | Verbose `[auto-resume:debug]` logging. |

Example:

```jsonc
{
"plugins": [
{
"package": "./plugins/auto-resume-v2.ts",
"options": { "chunkTimeoutMs": 60000, "debug": true }
}
]
}
```

## Verify it loaded

Start opencode; the plugin logs its banner at load:

```
[auto-resume] ready (opencode v2). timeout=45000ms interval=5000ms retries=3 loop=3/600s
```

To see an intervention in action, let a session go quiet past
`chunkTimeoutMs`; the plugin injects a visible **synthetic** message in the
session timeline (`auto-resume: …`) and resumes the turn. Every recovery is
also appended to the opencode log with an `[auto-resume]` prefix.

## Disable / uninstall

- **Disable by id** without touching other plugins: add `"-auto-resume.v2"` to
the `plugins` array.
- **Temporarily off**: delete/move the file out of the `plugins/` directory.
- **Uninstall**: remove the file and any `plugins` entry you added.

## Troubleshooting

| Symptom | Check |
|---|---|
| No `[auto-resume] ready …` banner | File is in a `plugins/` dir opencode scans, or listed in `plugins`; restart opencode. |
| Logs say `failed to load plugin … Cannot find package '@opencode/plugin'` | Install the API package next to the plugin (Step 1) **and restart** opencode. |
| Nothing happens on a stall | Increase verbosity with `"debug": true`; confirm `chunkTimeoutMs` isn't larger than your real stall. |
| Recovers but you don't see a notice | Your model/provider may reject `session.synthetic()`; the plugin falls back to `session.prompt()` (no TUI banner). |
| Never recovers a parent waiting on a subagent | Intentional: parent sessions blocked on a running subagent are left alone. |
| Never recovers while a permission dialog is open | Intentional: recovery is held until the permission is answered. |

## Development

```sh
# typecheck the v2 port against the stable types
bun add -d @opencode/plugin@2.0.5 typescript
bunx tsc --noEmit --strict --target ESNext --module ESNext \
--moduleResolution bundler --skipLibCheck src/v2/index.ts
```

See [`migration.md`](./migration.md) for the full v1→v2 mapping and the
stable-vs-beta validation notes.
Loading