Skip to content

Commit 56d15d1

Browse files
authored
feat: Self-Repair protocol for automatic adapter fixing (#866)
* feat: add Self-Repair protocol for automatic adapter fixing When an AI agent uses opencli and a command fails, the agent automatically diagnoses the failure, fixes the adapter, and retries. - Add CLAUDE.md with Self-Repair protocol (auto-loaded by Claude Code) - Add designs/self-repair-protocol.md documenting the approach - Update opencli-repair skill: add Safety Boundaries (AUTH/BROWSER → STOP, sourcePath-only scope, max 3 rounds), fix AUTH_REQUIRED guidance - Update opencli-usage skill: add Self-Repair section Key design decisions: - Repair target is always RepairContext.adapter.sourcePath (works for both repo-local clis/ and user-local ~/.opencli/clis/) - Only adapter files may be modified, never core src/ - Max 3 repair rounds per failure - AUTH_REQUIRED and BROWSER_CONNECT are hard stops (report, don't modify) * fix: align auth boundary and scope language across all documents - Remove "Auth changed (AUTH_REQUIRED)" exploration section from opencli-repair skill — contradicted the hard stop rule above it - Update design doc: scope language matches repo-local + explicit skill delivery model, not universal product behavior - Update usage skill: reference sourcePath instead of "files under clis/" * fix: replace remaining repo-relative clis/ paths with sourcePath in design doc * refactor: rename opencli-repair to opencli-autofix, remove CLAUDE.md CLAUDE.md was wrong — users don't work inside the opencli repo, and the protocol shouldn't assume Claude Code. The skill is the portable delivery mechanism for any AI agent. - Rename skills/opencli-repair → skills/opencli-autofix - Remove CLAUDE.md (not the right delivery mechanism) - Update all references in usage skill and design doc - Design doc rewritten to reflect skill-first approach * fix: use sourcePath in example repair session * feat: emit AutoFix hint on repairable adapter errors When a command fails with a repairable error (SELECTOR, EMPTY_RESULT, COMMAND_EXEC, or generic http/not-found), the error output now includes a hint telling agents to re-run with OPENCLI_DIAGNOSTIC=1 for repair context. This is the trigger mechanism that bridges the gap between "command failed" and "agent enters autofix loop". Non-repairable errors (AUTH_REQUIRED, BROWSER_CONNECT, ARGUMENT) do not emit the hint — these require user action, not adapter fixes. * fix: narrow AutoFix hint to adapter-drift errors only Remove hint from CommandExecutionError (covers env/launcher/runtime issues, not adapter drift) and generic http errors (often temporary site issues). Keep hint only for SelectorError, EmptyResultError, and generic not-found — clear adapter-drift signals.
1 parent 57d59d5 commit 56d15d1

4 files changed

Lines changed: 198 additions & 26 deletions

File tree

designs/self-repair-protocol.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Self-Repair Protocol — Design Document
2+
3+
**Authors**: @opus0, @codex-mini0
4+
**Date**: 2026-04-07
5+
**Status**: Approved
6+
**Supersedes**: `designs/autofix-incident-repair.md` (PR #863, deferred to Phase 2)
7+
8+
---
9+
10+
## Problem Statement
11+
12+
When an AI agent uses `opencli <site> <command>` and the command fails (site changed DOM, API, or response schema), the agent should **automatically repair the adapter and retry** — without human intervention or pre-written spec files.
13+
14+
### Why the simpler approach
15+
16+
The previous design (PR #863) required pre-authoring `command-specs.json` with verify checks, safety profiles, and failure taxonomy before any command could be repaired. This created a chicken-and-egg problem: you can only repair commands you've already written specs for.
17+
18+
From first principles, the agent already has everything it needs:
19+
1. **The failing command** — it just ran it
20+
2. **The error output** — stdout/stderr
21+
3. **The adapter source** — resolved via `RepairContext.adapter.sourcePath`
22+
4. **Diagnostic context** — DOM snapshot, network requests (via `OPENCLI_DIAGNOSTIC=1`)
23+
5. **A verify oracle** — re-run the same command
24+
25+
No spec file needed. The command itself is the spec.
26+
27+
---
28+
29+
## Design: Online Self-Repair
30+
31+
### Core Protocol
32+
33+
```
34+
Agent runs: opencli <site> <command> [args...]
35+
→ Command succeeds → continue task
36+
→ Command fails →
37+
1. Re-run with OPENCLI_DIAGNOSTIC=1 to collect RepairContext
38+
2. Read adapter source from RepairContext.adapter.sourcePath
39+
3. Analyze: error code + DOM snapshot + network requests → root cause
40+
4. Edit the adapter file at RepairContext.adapter.sourcePath
41+
5. Retry the original command
42+
6. If still failing → repeat (max 3 rounds)
43+
7. If 3 rounds exhausted → report failure, do not loop further
44+
```
45+
46+
### Scope Constraint
47+
48+
**Only modify the adapter file identified by `RepairContext.adapter.sourcePath`.**
49+
50+
The diagnostic resolves the actual editable source path at runtime — it may be:
51+
- `clis/<site>/*.ts` — repo-local adapters (dev/source checkout)
52+
- `~/.opencli/clis/<site>/*.ts` — user-local adapters (npm install scenario)
53+
54+
The agent must use the path from the diagnostic, not guess a repo-relative path. This is critical for npm-installed users where `clis/` is not in the repo.
55+
56+
**Never modify:**
57+
- `src/**` — core runtime (npm package, requires version release)
58+
- `extension/**` — browser extension
59+
- `autoresearch/**` — research infrastructure
60+
- `tests/**` — test files
61+
- `package.json`, `tsconfig.json` — project config
62+
63+
### When NOT to Self-Repair
64+
65+
The agent should recognize non-repairable failures and stop:
66+
67+
| Signal | Meaning | Action |
68+
|--------|---------|--------|
69+
| Auth/login error | Not logged into site in Chrome | Tell user to log in, don't modify code |
70+
| Browser bridge not connected | Extension/daemon not running | Tell user to run `opencli doctor` |
71+
| CAPTCHA | Site requires human verification | Report, don't modify code |
72+
| Rate limited / IP blocked | Not an adapter issue | Report, wait and retry later |
73+
| Feature removed by site | Data no longer exists | Report, adapter may need deprecation |
74+
75+
### Retry Budget
76+
77+
- **Max 3 repair rounds per command failure**
78+
- Each round: diagnose → edit adapter → retry command
79+
- If the error is identical after a repair attempt, the fix didn't work — try a different approach
80+
- After 3 rounds, stop and report what was tried
81+
82+
---
83+
84+
## Implementation
85+
86+
### What Already Exists
87+
88+
| Component | Status | Location |
89+
|-----------|--------|----------|
90+
| Diagnostic output (RepairContext) | ✅ Done | `src/diagnostic.ts` |
91+
| Diagnostic wiring in execution | ✅ Done | `src/execution.ts` |
92+
| Error taxonomy (CliError codes) | ✅ Done | `src/errors.ts` |
93+
| Adapter source resolution | ✅ Done | `src/diagnostic.ts:resolveAdapterSourcePath` |
94+
95+
### What's New (This Design)
96+
97+
| Component | Description |
98+
|-----------|-------------|
99+
| `skills/opencli-autofix/SKILL.md` (renamed from `opencli-repair`) | AutoFix skill with safety boundaries, sourcePath-based scope, 3-round limit. The primary delivery mechanism for the self-repair protocol. |
100+
| `skills/opencli-usage/SKILL.md` (updated) | Self-Repair section for discoverability |
101+
102+
### Delivery mechanism
103+
104+
The `opencli-autofix` skill is the portable self-repair protocol. Any AI agent — regardless of framework, provider, or working directory — can load this skill to get the full autofix workflow. It is not tied to any specific agent framework or repo location.
105+
106+
- **No new runtime code** — the diagnostic infrastructure already exists
107+
- **No CLAUDE.md dependency** — the skill is the protocol, not a repo-local file
108+
109+
---
110+
111+
## The AutoFix Protocol (in the skill)
112+
113+
The `opencli-autofix` skill instructs agents:
114+
115+
1. When `opencli <site> <command>` fails, **don't just report the error**
116+
2. Re-run with `OPENCLI_DIAGNOSTIC=1` to get structured context
117+
3. Parse the RepairContext (error code, adapter source, DOM snapshot)
118+
4. Read and fix the adapter at `RepairContext.adapter.sourcePath`
119+
5. Retry the original command
120+
6. Max 3 repair rounds, then stop
121+
122+
---
123+
124+
## Relationship to PR #863
125+
126+
PR #863 (spec/runner/incident framework) is **not needed for Phase 1**. It becomes useful later as a "hardening layer":
127+
128+
- **Phase 1 (now)**: Self-Repair via `opencli-autofix` skill — agent repairs on the fly
129+
- **Phase 2 (later)**: High-frequency failures get hardened into `command-specs.json` for offline regression testing and CI
130+
131+
The spec/runner framework is the "asset layer" — it turns ad-hoc repairs into reusable, verifiable test cases. But it's not the entry point.
132+
133+
---
134+
135+
## Usage
136+
137+
No new commands. No new scripts. The agent loads the `opencli-autofix` skill and uses opencli normally:
138+
139+
```bash
140+
# Agent runs a command as part of its task
141+
opencli weibo hot --limit 5 -f json
142+
143+
# If it fails, the agent automatically:
144+
# 1. Runs OPENCLI_DIAGNOSTIC=1 opencli weibo hot --limit 5 -f json 2>diag.json
145+
# 2. Reads the diagnostic context
146+
# 3. Fixes the adapter at RepairContext.adapter.sourcePath
147+
# 4. Retries: opencli weibo hot --limit 5 -f json
148+
# 5. Continues with the task
149+
```
Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
---
2-
name: opencli-repair
3-
description: Diagnose and fix broken OpenCLI adapters when websites change. Use when an opencli command fails with SELECTOR, EMPTY_RESULT, API_ERROR, or PAGE_CHANGED errors. Reads structured diagnostic output and uses browser automation to discover what changed and patch the adapter.
2+
name: opencli-autofix
3+
description: Automatically fix broken OpenCLI adapters when commands fail. Load this skill when an opencli command fails — it guides you through diagnosing the failure via OPENCLI_DIAGNOSTIC, patching the adapter, and retrying. Works with any AI agent.
44
allowed-tools: Bash(opencli:*), Read, Edit, Write
55
---
66

7-
# OpenCLI RepairAI-Driven Adapter Self-Repair
7+
# OpenCLI AutoFixAutomatic Adapter Self-Repair
88

9-
When an adapter breaks because a website changed its DOM, API, or auth flow, use this skill to diagnose the failure and patch the adapter.
9+
When an `opencli` command fails because a website changed its DOM, API, or response schema, **automatically diagnose, fix the adapter, and retry** — don't just report the error.
10+
11+
## Safety Boundaries
12+
13+
**Before starting any repair, check these hard stops:**
14+
15+
- **`AUTH_REQUIRED`** (exit code 77) — **STOP.** Do not modify code. Tell the user to log into the site in Chrome.
16+
- **`BROWSER_CONNECT`** (exit code 69) — **STOP.** Do not modify code. Tell the user to run `opencli doctor`.
17+
- **CAPTCHA / rate limiting****STOP.** Not an adapter issue.
18+
19+
**Scope constraint:**
20+
- **Only modify the file at `RepairContext.adapter.sourcePath`** — this is the authoritative adapter location (may be `clis/<site>/` in repo or `~/.opencli/clis/<site>/` for npm installs)
21+
- **Never modify** `src/`, `extension/`, `tests/`, `package.json`, or `tsconfig.json`
22+
23+
**Retry budget:** Max **3 repair rounds** per failure. If 3 rounds of diagnose → fix → retry don't resolve it, stop and report what was tried.
1024

1125
## Prerequisites
1226

@@ -16,7 +30,7 @@ opencli doctor # Verify extension + daemon connectivity
1630

1731
## When to Use This Skill
1832

19-
Use when `opencli <site> <command>` fails with errors like:
33+
Use when `opencli <site> <command>` fails with repairable errors:
2034
- **SELECTOR** — element not found (DOM changed)
2135
- **EMPTY_RESULT** — no data returned (API response changed)
2236
- **API_ERROR** / **NETWORK** — endpoint moved or broke
@@ -72,7 +86,7 @@ Read the diagnostic context and the adapter source. Classify the root cause:
7286
| SELECTOR | DOM restructured, class/id renamed | Explore current DOM → find new selector |
7387
| EMPTY_RESULT | API response schema changed, or data moved | Check network → find new response path |
7488
| API_ERROR | Endpoint URL changed, new params required | Discover new API via network intercept |
75-
| AUTH_REQUIRED | Login flow changed, cookies expired | Walk login flow with operate |
89+
| AUTH_REQUIRED | Login flow changed, cookies expired | **STOP** — tell user to log in, do not modify code |
7690
| TIMEOUT | Page loads differently, spinner/lazy-load | Add/update wait conditions |
7791
| PAGE_CHANGED | Major redesign | May need full adapter rewrite |
7892

@@ -109,23 +123,13 @@ opencli operate click <N> && opencli operate network
109123
opencli operate network --detail <index>
110124
```
111125

112-
### Auth changed (AUTH_REQUIRED)
113-
114-
```bash
115-
# Check current auth state
116-
opencli operate open https://example.com && opencli operate state
117-
118-
# If login page: inspect the login form
119-
opencli operate state # Look for login form fields
120-
```
121-
122126
## Step 4: Patch the Adapter
123127

124-
Read the adapter source file and make targeted fixes:
128+
Read the adapter source file at the path from `RepairContext.adapter.sourcePath` and make targeted fixes. This path is authoritative — it may be in the repo (`clis/`) or user-local (`~/.opencli/clis/`).
125129

126130
```bash
127-
# Read the adapter
128-
cat <sourcePath from diagnostic>
131+
# Read the adapter (use the exact path from diagnostic)
132+
cat <RepairContext.adapter.sourcePath>
129133
```
130134

131135
### Common Fixes
@@ -169,18 +173,21 @@ cat <sourcePath from diagnostic>
169173
opencli <site> <command> [args...]
170174
```
171175

172-
If it still fails, go back to Step 3 and explore further. If the website has fundamentally changed (major redesign, removed feature), report that the adapter needs a full rewrite.
173-
174-
## When to Give Up
176+
If it still fails, go back to Step 1 and collect fresh diagnostics. You have a budget of **3 repair rounds** (diagnose → fix → retry). If the same error persists after a fix, try a different approach. After 3 rounds, stop and report what was tried.
175177

176-
Not all failures are repairable with a quick patch:
178+
## When to Stop
177179

180+
**Hard stops (do not modify code):**
181+
- **AUTH_REQUIRED / BROWSER_CONNECT** — environment issue, not adapter bug
178182
- **Site requires CAPTCHA** — can't automate this
183+
- **Rate limited / IP blocked** — not an adapter issue
184+
185+
**Soft stops (report after attempting):**
186+
- **3 repair rounds exhausted** — stop, report what was tried and what failed
179187
- **Feature completely removed** — the data no longer exists
180188
- **Major redesign** — needs full adapter rewrite via `opencli-explorer` skill
181-
- **Rate limited / IP blocked** — not an adapter issue
182189

183-
In these cases, clearly communicate the situation to the user rather than making futile patches.
190+
In all stop cases, clearly communicate the situation to the user rather than making futile patches.
184191

185192
## Example Repair Session
186193

@@ -196,7 +203,7 @@ In these cases, clearly communicate the situation to the user rather than making
196203
4. AI explores: opencli operate open https://www.zhihu.com/hot && opencli operate state
197204
→ Confirms new class name ".HotItem" with child ".HotItem-content"
198205
199-
5. AI patches: Edit clis/zhihu/hot.ts — replace ".HotList-item" with ".HotItem"
206+
5. AI patches: Edit adapter at RepairContext.adapter.sourcePath — replace ".HotList-item" with ".HotItem"
200207
201208
6. AI verifies: opencli zhihu hot
202209
→ Success: returns hot topics

skills/opencli-usage/SKILL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,13 @@ opencli record <url> # Record API calls manually
156156

157157
All commands support: `--format` / `-f` with `table` `json` `yaml` `md` `csv`
158158

159+
## Self-Repair
160+
161+
If a command fails due to a site change (selector, API, or response schema), **automatically fix the adapter and retry** instead of reporting failure. Re-run the command with `OPENCLI_DIAGNOSTIC=1` to get structured diagnostic context, then fix the adapter file at `RepairContext.adapter.sourcePath` and retry. Max 3 repair rounds. See the `opencli-autofix` skill for the full workflow.
162+
159163
## Related Skills
160164

161165
- **opencli-operate** — Browser automation for AI agents (navigate, click, type, extract via Chrome)
162166
- **opencli-explorer** — Full guide for creating new adapters (API discovery, auth strategy, YAML/TS writing)
163167
- **opencli-oneshot** — Quick 4-step template for adding a single command from a URL
168+
- **opencli-autofix** — Automatically fix broken adapters when commands fail

src/commanderAdapter.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
CommandExecutionError,
3232
} from './errors.js';
3333
import { checkDaemonStatus } from './browser/discover.js';
34+
import { isDiagnosticEnabled } from './diagnostic.js';
3435

3536
export function normalizeArgValue(argType: string | undefined, value: unknown, name: string): unknown {
3637
if (argType !== 'bool' && argType !== 'boolean') return value;
@@ -192,6 +193,14 @@ function renderBridgeStatus(running: boolean, extensionConnected: boolean): void
192193
}
193194
}
194195

196+
/** Emit AutoFix hint for repairable adapter errors (skipped if already in diagnostic mode). */
197+
function emitAutoFixHint(cmdName: string): void {
198+
if (isDiagnosticEnabled()) return; // Already collecting diagnostics, don't repeat
199+
console.error();
200+
console.error(chalk.cyan('💡 AutoFix: re-run with OPENCLI_DIAGNOSTIC=1 for repair context.'));
201+
console.error(chalk.dim(` OPENCLI_DIAGNOSTIC=1 ${cmdName}`));
202+
}
203+
195204
async function renderError(err: unknown, cmdName: string, verbose: boolean): Promise<void> {
196205
// ── BrowserConnectError: real-time diagnosis, kind as fallback ────────
197206
if (err instanceof BrowserConnectError) {
@@ -233,6 +242,7 @@ async function renderError(err: unknown, cmdName: string, verbose: boolean): Pro
233242
console.error(chalk.yellow(`→ ${err.hint ?? 'The page structure may have changed — this adapter may be outdated.'}`));
234243
console.error(chalk.dim(` Debug: ${cmdName} --verbose`));
235244
console.error(chalk.dim(` Report: ${ISSUES_URL}`));
245+
emitAutoFixHint(cmdName);
236246
return;
237247
}
238248

@@ -277,6 +287,7 @@ async function renderError(err: unknown, cmdName: string, verbose: boolean): Pro
277287
console.error(chalk.red(`${classified.icon} ${msg}`));
278288
console.error(chalk.yellow(`→ ${classified.hint}`));
279289
if (classified.kind === 'not-found') console.error(chalk.dim(` Report: ${ISSUES_URL}`));
290+
if (classified.kind === 'not-found') emitAutoFixHint(cmdName);
280291
return;
281292
}
282293

0 commit comments

Comments
 (0)