opencode-rag-plugin 1.17.0 → 1.17.3
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/ReadMe.md +52 -5
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/init-helpers.js +3 -0
- package/dist/cli/commands/status.js +3 -3
- package/dist/cli/commands/update.d.ts +15 -0
- package/dist/cli/commands/update.js +65 -0
- package/dist/cli/index.js +2 -1
- package/dist/core/auto-update-state.d.ts +33 -0
- package/dist/core/auto-update-state.js +69 -0
- package/dist/core/config.d.ts +18 -3
- package/dist/core/config.js +75 -5
- package/dist/core/runtime-overrides.js +3 -1
- package/dist/core/setup-runtime.js +61 -13
- package/dist/core/version-check.d.ts +76 -0
- package/dist/core/version-check.js +113 -2
- package/dist/embedder/cohere.d.ts +1 -1
- package/dist/embedder/cohere.js +2 -2
- package/dist/embedder/factory.js +1 -1
- package/dist/embedder/health.js +1 -1
- package/dist/embedder/ollama.d.ts +1 -1
- package/dist/embedder/ollama.js +2 -2
- package/dist/embedder/openai.d.ts +1 -1
- package/dist/embedder/openai.js +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/indexer/pipeline.js +15 -4
- package/dist/plugin.js +166 -19
- package/dist/tui.js +18 -1
- package/dist/vectorstore/lancedb.d.ts +1 -0
- package/dist/vectorstore/lancedb.js +8 -2
- package/dist/watcher.d.ts +2 -0
- package/dist/watcher.js +2 -1
- package/package.json +1 -1
package/ReadMe.md
CHANGED
|
@@ -41,10 +41,14 @@ opencode-rag query "authentication middleware"
|
|
|
41
41
|
| **OpenCode plugin** | Auto-inject context, read-tool override, TUI settings, Ctrl+Enter to add RAG context, MCP registration on `init` |
|
|
42
42
|
| **Incremental indexing** | File-hash manifest, background watcher, auto-rebuild on corruption |
|
|
43
43
|
| **Privacy-first** | All processing stays local (when using Ollama) |
|
|
44
|
-
| **CLI Tools** | `index`, `query`, `status`, `list`, `show`, `dump`, `clear`, `
|
|
44
|
+
| **CLI Tools** | `init`, `index`, `query`, `status`, `list`, `show`, `dump`, `clear`, `describe-image`, `ui`, `mcp`, `setup`, `eval:sessions`, `eval:analyze`, `eval:compare` |
|
|
45
45
|
| **Proxy-aware** | Corporate proxy support with raw-socket localhost bypass |
|
|
46
46
|
| **OpenAI / Anthropic / Cohere** | Use alternate embedding providers with API key auto-resolution |
|
|
47
47
|
| **Evaluation** | Session-level token tracking, RAG-on vs RAG-off comparison, tiktoken BPE counting |
|
|
48
|
+
| **Documentation mode** | `/doc` slash command: agent adds JSDoc/TSDoc to undocumented files, progress tracked per subdirectory |
|
|
49
|
+
| **Wiki mode** | `/wiki` slash command: agent maintains a persistent knowledge wiki at `.opencode/wiki/` (ingest, query, lint, seed) |
|
|
50
|
+
| **Context optimization** | Post-retrieval dedup, per-file chunk limits, adjacent-merge to fit the context window |
|
|
51
|
+
| **AGENTS.md directive** | `opencode-rag init` merges a tool-usage directive into `AGENTS.md` via sentinel markers |
|
|
48
52
|
|
|
49
53
|
## Web UI
|
|
50
54
|
|
|
@@ -80,14 +84,57 @@ OpenCodeRAG can index image files (PNG, JPEG, WebP, etc.) by sending them to a v
|
|
|
80
84
|
|
|
81
85
|
**Disabled by default** — enable in `opencode-rag.json` to opt in (recommended for dedicated GPUs).
|
|
82
86
|
|
|
83
|
-
##
|
|
87
|
+
## Documentation Mode
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
When `documentationMode.enabled` is `true`, the OpenCode plugin exposes a `/doc` slash command that drives the agent to add JSDoc/TSDoc comments to undocumented source files. No agent tools are registered — documentation is driven entirely through the slash command and the documentation system prompt.
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"documentationMode": {
|
|
94
|
+
"enabled": true,
|
|
95
|
+
"batchSize": 5
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Workflow:** Type `/doc` to list all undocumented files grouped by subdirectory. The agent picks a subdirectory, documents every public symbol (preserving existing comments and implementation code), then types `/doc src/auth/` to mark that subdirectory complete. Progress is persisted in `.opencode/rag_db/doc-mode-progress.json` so subsequent sessions resume where you left off.
|
|
101
|
+
|
|
102
|
+
See [Plugin documentation](doc/plugin.md#5-documentation-mode--slash-command-doc) for details.
|
|
103
|
+
|
|
104
|
+
## Wiki Mode
|
|
105
|
+
|
|
106
|
+
When `wikiMode.enabled` is `true`, the OpenCode plugin injects a wiki-maintainer system prompt that instructs the agent to build and maintain a persistent knowledge wiki at `.opencode/wiki/` — a structured, interlinked collection of markdown pages synthesizing knowledge from the codebase, docs, and conversations.
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"wikiMode": {
|
|
111
|
+
"enabled": true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Slash commands:**
|
|
117
|
+
|
|
118
|
+
| Command | Action |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `/wiki` | Show wiki status (page count, subdirectories, index/log presence, latest log entry) |
|
|
121
|
+
| `/wiki seed` | Generate the initial wiki from README, file structure, and code patterns |
|
|
122
|
+
| `/wiki lint` | Health-check for orphan pages, stale source refs, contradictions, missing cross-links |
|
|
123
|
+
|
|
124
|
+
The agent maintains all wiki pages during normal coding sessions (ingest on learning). The user never writes wiki pages directly. The wiki is git-trackable in `.opencode/` so every edit is a diff.
|
|
125
|
+
|
|
126
|
+
See [Plugin documentation](doc/plugin.md#6-wiki-mode--slash-command-wiki) for the full protocol.
|
|
127
|
+
|
|
128
|
+
## MCP Server (Optional)
|
|
129
|
+
|
|
130
|
+
OpenCodeRAG ships a CLI-based [MCP (Model Context Protocol)](https://spec.modelcontextprotocol.io/) server that exposes semantic code tools to any MCP-compatible client (Claude Desktop, Cursor, etc.).
|
|
86
131
|
|
|
87
132
|
```bash
|
|
88
133
|
opencode-rag mcp
|
|
89
134
|
```
|
|
90
135
|
|
|
136
|
+
> **Note:** The MCP server is **optional**. When running as an OpenCode plugin, the four tools are registered **in-process** and work without the MCP server. The `chat.message` hook for hotkey injection also runs in-process. The MCP server is only needed when an **external** MCP client connects to OpenCodeRAG. The plugin auto-starts the server only if `mcp.enabled` is `true` in your config (default: `false`).
|
|
137
|
+
|
|
91
138
|
### MCP Tools
|
|
92
139
|
|
|
93
140
|
| Tool | Description |
|
|
@@ -124,10 +171,10 @@ OpenCodeRAG registers tools that agents can invoke directly. Agents discover the
|
|
|
124
171
|
|
|
125
172
|
When using OpenCode, the plugin enhances your agent with three discovery mechanisms:
|
|
126
173
|
|
|
127
|
-
### 1. Skill-Based Discovery
|
|
174
|
+
### 1. Skill-Based Discovery
|
|
128
175
|
`opencode-rag init` creates `.opencode/skills/opencode-rag/SKILL.md` - an OpenCode skill that teaches agents the tool workflow. Agents load it on demand via the `skill` tool, keeping token overhead minimal.
|
|
129
176
|
|
|
130
|
-
### 2. System Prompt Guidance
|
|
177
|
+
### 2. System Prompt Guidance
|
|
131
178
|
When chunks are indexed, a brief tool list is prepended to the system prompt so agents know the tools exist. This is skipped when no chunks are indexed to save tokens.
|
|
132
179
|
|
|
133
180
|
### 3. On-Demand RAG Context (Ctrl+Enter / Ctrl+Alt+Enter)
|
|
@@ -12,5 +12,6 @@ export { registerInitCommand } from "./init.js";
|
|
|
12
12
|
export { registerUiCommand } from "./ui.js";
|
|
13
13
|
export { registerMcpCommand } from "./mcp.js";
|
|
14
14
|
export { registerSetupCommand } from "./setup.js";
|
|
15
|
+
export { registerUpdateCommand } from "./update.js";
|
|
15
16
|
export { registerEvalSessionsCommand, registerEvalAnalyzeCommand, registerEvalCompareCommand } from "./eval.js";
|
|
16
17
|
export { registerDescribeImageCommand } from "./describe-image.js";
|
|
@@ -12,6 +12,7 @@ export { registerInitCommand } from "./init.js";
|
|
|
12
12
|
export { registerUiCommand } from "./ui.js";
|
|
13
13
|
export { registerMcpCommand } from "./mcp.js";
|
|
14
14
|
export { registerSetupCommand } from "./setup.js";
|
|
15
|
+
export { registerUpdateCommand } from "./update.js";
|
|
15
16
|
export { registerEvalSessionsCommand, registerEvalAnalyzeCommand, registerEvalCompareCommand } from "./eval.js";
|
|
16
17
|
export { registerDescribeImageCommand } from "./describe-image.js";
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -460,6 +460,9 @@ export function generateDefaultConfigJson() {
|
|
|
460
460
|
timeoutMs: DEFAULT_CONFIG.description.timeoutMs,
|
|
461
461
|
maxContentChars: DEFAULT_CONFIG.description.maxContentChars,
|
|
462
462
|
},
|
|
463
|
+
wikiMode: {
|
|
464
|
+
enabled: DEFAULT_CONFIG.wikiMode.enabled,
|
|
465
|
+
},
|
|
463
466
|
mcp: {
|
|
464
467
|
enabled: DEFAULT_CONFIG.mcp.enabled,
|
|
465
468
|
},
|
|
@@ -147,12 +147,12 @@ export function registerStatusCommand(program) {
|
|
|
147
147
|
logCliInfo(logFilePath, "status", `${c.label("Runtime:")} ${c.warn("version unknown — run `opencode-rag setup`")}`);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
// Async GitHub update check (fire-and-forget, 5s timeout)
|
|
151
|
-
//
|
|
150
|
+
// Async GitHub update check (fire-and-forget, 5s timeout).
|
|
151
|
+
// Runs unless autoUpdate is explicitly disabled.
|
|
152
152
|
if (config.autoUpdate?.enabled) {
|
|
153
153
|
checkForUpdate(pkg.version).then((info) => {
|
|
154
154
|
if (info.updateAvailable) {
|
|
155
|
-
process.stdout.write(` ${c.label("Update:")} ${c.warn(`v${info.latestVersion} available —
|
|
155
|
+
process.stdout.write(` ${c.label("Update:")} ${c.warn(`v${info.latestVersion} available — run \`opencode-rag update\` to install`)}\n`);
|
|
156
156
|
}
|
|
157
157
|
}).catch(() => { });
|
|
158
158
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `update` command — checks GitHub for a newer OpenCodeRAG release and installs it.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* `update` command — checks for a newer published version of OpenCodeRAG and,
|
|
6
|
+
* by default, installs it (via `npm install -g ...@latest`) then re-syncs the
|
|
7
|
+
* OpenCode runtime junctions so the new build is picked up on next restart.
|
|
8
|
+
*/
|
|
9
|
+
import type { Command } from "commander";
|
|
10
|
+
/**
|
|
11
|
+
* Register the `update` command on the given Commander program.
|
|
12
|
+
*
|
|
13
|
+
* @param program - The Commander `Command` instance to register on.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerUpdateCommand(program: Command): void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `update` command — checks GitHub for a newer OpenCodeRAG release and installs it.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* `update` command — checks for a newer published version of OpenCodeRAG and,
|
|
6
|
+
* by default, installs it (via `npm install -g ...@latest`) then re-syncs the
|
|
7
|
+
* OpenCode runtime junctions so the new build is picked up on next restart.
|
|
8
|
+
*/
|
|
9
|
+
import { c } from "../format.js";
|
|
10
|
+
import { checkForUpdate, getCurrentVersion, installLatestUpdate } from "../../core/version-check.js";
|
|
11
|
+
/**
|
|
12
|
+
* Register the `update` command on the given Commander program.
|
|
13
|
+
*
|
|
14
|
+
* @param program - The Commander `Command` instance to register on.
|
|
15
|
+
*/
|
|
16
|
+
export function registerUpdateCommand(program) {
|
|
17
|
+
program
|
|
18
|
+
.command("update")
|
|
19
|
+
.description("Check for and install the newest published version of OpenCodeRAG")
|
|
20
|
+
.option("--check", "only check whether an update is available; do not install")
|
|
21
|
+
.option("-v, --verbose", "stream npm/setup output to the console")
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
const currentVersion = getCurrentVersion();
|
|
24
|
+
console.log(`\n${c.heading("OpenCodeRAG Update")}\n`);
|
|
25
|
+
console.log(` ${c.label("Current version:")} ${c.value(currentVersion)}`);
|
|
26
|
+
console.log(` ${c.label("Checking...")} `);
|
|
27
|
+
let info;
|
|
28
|
+
try {
|
|
29
|
+
info = await checkForUpdate(currentVersion);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
console.log(`\n ${c.warn("Could not reach the update server. Check your network and try again.")}\n`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!info.updateAvailable) {
|
|
37
|
+
console.log(`\n ${c.success("Already up-to-date.")} (${c.value(info.latestVersion || currentVersion)})\n`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(` ${c.label("Latest version:")} ${c.value(info.latestVersion)}`);
|
|
41
|
+
if (info.publishedAt) {
|
|
42
|
+
console.log(` ${c.label("Published:")} ${c.dim(info.publishedAt)}`);
|
|
43
|
+
}
|
|
44
|
+
if (info.releaseUrl) {
|
|
45
|
+
console.log(` ${c.label("Release notes:")} ${c.file(info.releaseUrl)}`);
|
|
46
|
+
}
|
|
47
|
+
if (options.check) {
|
|
48
|
+
console.log(`\n ${c.warn("Update available.")} Run ${c.file("opencode-rag update")} to install.\n`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(`\n ${c.dim("Installing newest version...")}\n`);
|
|
52
|
+
const result = await installLatestUpdate({ verbose: options.verbose });
|
|
53
|
+
if (result.success) {
|
|
54
|
+
console.log(` ${c.success("✓")} ${result.message}`);
|
|
55
|
+
console.log(`\n ${c.dim("Restart OpenCode if it is running to load the new version.")}\n`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.error(` ${c.error("✗")} ${result.message}`);
|
|
59
|
+
console.error(`\n ${c.error("Update failed. You can retry with `opencode-rag update` or install manually:")}\n`);
|
|
60
|
+
console.error(` ${c.dim(" npm install -g opencode-rag-plugin@latest && opencode-rag setup")}\n`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=update.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { realpathSync } from "node:fs";
|
|
|
14
14
|
import { basename, dirname } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { getPackageMetadata } from "./helpers.js";
|
|
17
|
-
import { registerIndexCommand, registerQueryCommand, registerClearCommand, registerStatusCommand, registerListCommand, registerShowCommand, registerDumpCommand, registerInitCommand, registerUiCommand, registerMcpCommand, registerSetupCommand, registerEvalSessionsCommand, registerEvalAnalyzeCommand, registerEvalCompareCommand, registerDescribeImageCommand, } from "./commands/index.js";
|
|
17
|
+
import { registerIndexCommand, registerQueryCommand, registerClearCommand, registerStatusCommand, registerListCommand, registerShowCommand, registerDumpCommand, registerInitCommand, registerUiCommand, registerMcpCommand, registerSetupCommand, registerUpdateCommand, registerEvalSessionsCommand, registerEvalAnalyzeCommand, registerEvalCompareCommand, registerDescribeImageCommand, } from "./commands/index.js";
|
|
18
18
|
/**
|
|
19
19
|
* The top-level Commander program instance that defines the `opencode-rag` CLI.
|
|
20
20
|
*
|
|
@@ -39,6 +39,7 @@ registerDescribeImageCommand(program);
|
|
|
39
39
|
registerUiCommand(program);
|
|
40
40
|
registerMcpCommand(program);
|
|
41
41
|
registerSetupCommand(program);
|
|
42
|
+
registerUpdateCommand(program);
|
|
42
43
|
registerEvalSessionsCommand(program);
|
|
43
44
|
registerEvalAnalyzeCommand(program);
|
|
44
45
|
registerEvalCompareCommand(program);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Persisted state for the background auto-update mechanism.
|
|
3
|
+
* Tracks when an install was last attempted, for which version,
|
|
4
|
+
* and consecutive failure counts to implement cooldown and backoff.
|
|
5
|
+
*/
|
|
6
|
+
/** Persisted state of a background auto-update attempt. */
|
|
7
|
+
export interface AutoUpdateState {
|
|
8
|
+
/** Epoch ms of the most recent attempt. */
|
|
9
|
+
lastAttemptAt: number;
|
|
10
|
+
/** The latestVersion we tried to install (or checked). */
|
|
11
|
+
lastAttemptedVersion: string;
|
|
12
|
+
/** Outcome of the last attempt. */
|
|
13
|
+
lastResult: "success" | "failure";
|
|
14
|
+
/** Consecutive failures so far (reset on success). */
|
|
15
|
+
consecutiveFailures: number;
|
|
16
|
+
}
|
|
17
|
+
/** Full path to the state file for a given store path. */
|
|
18
|
+
export declare function statePath(storePath: string): string;
|
|
19
|
+
/** Load persisted auto-update state, or return null if missing / corrupt. */
|
|
20
|
+
export declare function loadAutoUpdateState(storePath: string): AutoUpdateState | null;
|
|
21
|
+
/** Persist auto-update state to disk (best-effort, never throws). */
|
|
22
|
+
export declare function saveAutoUpdateState(storePath: string, state: AutoUpdateState): void;
|
|
23
|
+
/**
|
|
24
|
+
* Determine whether an auto-install attempt should proceed, based on
|
|
25
|
+
* the persisted state, the version we just discovered, and configured
|
|
26
|
+
* limits.
|
|
27
|
+
*
|
|
28
|
+
* @returns An object with `attempt: boolean` and `reason: string`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function shouldAttemptInstall(state: AutoUpdateState | null, latestVersion: string, cooldownMs: number, maxConsecutiveFailures: number): {
|
|
31
|
+
attempt: boolean;
|
|
32
|
+
reason: string;
|
|
33
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Persisted state for the background auto-update mechanism.
|
|
3
|
+
* Tracks when an install was last attempted, for which version,
|
|
4
|
+
* and consecutive failure counts to implement cooldown and backoff.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
/** Name of the state file written to the store path. */
|
|
9
|
+
const STATE_FILE_NAME = ".auto-update-state.json";
|
|
10
|
+
/** Full path to the state file for a given store path. */
|
|
11
|
+
export function statePath(storePath) {
|
|
12
|
+
return path.join(storePath, STATE_FILE_NAME);
|
|
13
|
+
}
|
|
14
|
+
/** Load persisted auto-update state, or return null if missing / corrupt. */
|
|
15
|
+
export function loadAutoUpdateState(storePath) {
|
|
16
|
+
try {
|
|
17
|
+
const p = statePath(storePath);
|
|
18
|
+
if (!existsSync(p))
|
|
19
|
+
return null;
|
|
20
|
+
const raw = readFileSync(p, "utf-8");
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (typeof parsed.lastAttemptAt !== "number" ||
|
|
23
|
+
typeof parsed.lastAttemptedVersion !== "string" ||
|
|
24
|
+
(parsed.lastResult !== "success" && parsed.lastResult !== "failure") ||
|
|
25
|
+
typeof parsed.consecutiveFailures !== "number") {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Persist auto-update state to disk (best-effort, never throws). */
|
|
35
|
+
export function saveAutoUpdateState(storePath, state) {
|
|
36
|
+
try {
|
|
37
|
+
writeFileSync(statePath(storePath), JSON.stringify(state, null, 2), "utf-8");
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* best-effort */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Determine whether an auto-install attempt should proceed, based on
|
|
45
|
+
* the persisted state, the version we just discovered, and configured
|
|
46
|
+
* limits.
|
|
47
|
+
*
|
|
48
|
+
* @returns An object with `attempt: boolean` and `reason: string`.
|
|
49
|
+
*/
|
|
50
|
+
export function shouldAttemptInstall(state, latestVersion, cooldownMs, maxConsecutiveFailures) {
|
|
51
|
+
if (!state) {
|
|
52
|
+
return { attempt: true, reason: "first run" };
|
|
53
|
+
}
|
|
54
|
+
const elapsed = Date.now() - state.lastAttemptAt;
|
|
55
|
+
if (state.lastAttemptedVersion === latestVersion && elapsed < cooldownMs) {
|
|
56
|
+
return {
|
|
57
|
+
attempt: false,
|
|
58
|
+
reason: `version ${latestVersion} already attempted ${Math.round(elapsed / 1000)}s ago (cooldown ${Math.round(cooldownMs / 1000)}s)`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (state.lastResult === "failure" && state.consecutiveFailures >= maxConsecutiveFailures && elapsed < cooldownMs) {
|
|
62
|
+
return {
|
|
63
|
+
attempt: false,
|
|
64
|
+
reason: `${state.consecutiveFailures} consecutive failures within cooldown window`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return { attempt: true, reason: "proceeding" };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=auto-update-state.js.map
|
package/dist/core/config.d.ts
CHANGED
|
@@ -114,6 +114,13 @@ export interface DocumentationModeConfig {
|
|
|
114
114
|
/** System prompt for the documentation agent. */
|
|
115
115
|
systemPrompt: string;
|
|
116
116
|
}
|
|
117
|
+
/** Configuration for the wiki mode that instructs the AI agent to maintain a persistent knowledge wiki. */
|
|
118
|
+
export interface WikiModeConfig {
|
|
119
|
+
/** Whether wiki mode is enabled. */
|
|
120
|
+
enabled: boolean;
|
|
121
|
+
/** System prompt for the wiki maintainer agent. */
|
|
122
|
+
systemPrompt: string;
|
|
123
|
+
}
|
|
117
124
|
/** Configuration for the terminal UI (TUI) keybindings. */
|
|
118
125
|
export interface TuiConfig {
|
|
119
126
|
/** Keybinding to toggle the file list panel. */
|
|
@@ -121,15 +128,21 @@ export interface TuiConfig {
|
|
|
121
128
|
/** Keybinding to toggle the chunk viewer panel. */
|
|
122
129
|
chunksKeybinding: string;
|
|
123
130
|
}
|
|
124
|
-
/** Configuration for MCP (Model Context Protocol) server
|
|
131
|
+
/** Configuration for the standalone MCP (Model Context Protocol) server. */
|
|
125
132
|
export interface McpConfig {
|
|
126
|
-
/** Whether the MCP server
|
|
133
|
+
/** Whether to auto-start the MCP server on plugin load. Default false — agent tools run in-process regardless. Set true only when an external MCP client needs to connect via `opencode-rag mcp`. */
|
|
127
134
|
enabled: boolean;
|
|
128
135
|
}
|
|
129
136
|
/** Configuration for automatic self-updates. */
|
|
130
137
|
export interface AutoUpdateConfig {
|
|
131
|
-
/** Whether
|
|
138
|
+
/** Whether update checking is enabled (defaults to true). */
|
|
132
139
|
enabled: boolean;
|
|
140
|
+
/** When true, automatically install a newer release on startup (default false). */
|
|
141
|
+
autoInstall?: boolean;
|
|
142
|
+
/** Cooldown window (ms) before re-attempting the same version (default 3 600 000 = 1h). */
|
|
143
|
+
cooldownMs?: number;
|
|
144
|
+
/** Back off after this many consecutive failures (default 3). */
|
|
145
|
+
maxConsecutiveFailures?: number;
|
|
133
146
|
}
|
|
134
147
|
/** Configuration for post-retrieval context window optimization (adjacent merge, similarity dedup, file diversity cap). */
|
|
135
148
|
export interface ContextOptimizationConfig {
|
|
@@ -251,6 +264,8 @@ export interface RagConfig {
|
|
|
251
264
|
imageDescription?: ImageDescriptionConfig;
|
|
252
265
|
/** Automated documentation mode config. */
|
|
253
266
|
documentationMode?: DocumentationModeConfig;
|
|
267
|
+
/** Wiki mode config that instructs the AI agent to maintain a persistent knowledge wiki. */
|
|
268
|
+
wikiMode?: WikiModeConfig;
|
|
254
269
|
/** MCP server integration config. */
|
|
255
270
|
mcp?: McpConfig;
|
|
256
271
|
/** Auto-update checking config. */
|
package/dist/core/config.js
CHANGED
|
@@ -10,7 +10,7 @@ export const DEFAULT_CONFIG = {
|
|
|
10
10
|
provider: "ollama",
|
|
11
11
|
baseUrl: "http://127.0.0.1:11434/api",
|
|
12
12
|
model: "qwen3-embedding:0.6b",
|
|
13
|
-
timeoutMs:
|
|
13
|
+
timeoutMs: 120000,
|
|
14
14
|
},
|
|
15
15
|
indexing: {
|
|
16
16
|
includeExtensions: [
|
|
@@ -104,7 +104,7 @@ export const DEFAULT_CONFIG = {
|
|
|
104
104
|
concurrency: 8,
|
|
105
105
|
embedBatchSize: 100,
|
|
106
106
|
embedConcurrency: 6,
|
|
107
|
-
ollamaMaxBatchSize:
|
|
107
|
+
ollamaMaxBatchSize: 500,
|
|
108
108
|
descriptionConcurrency: 4,
|
|
109
109
|
maxSvgSizeBytes: 1_048_576,
|
|
110
110
|
},
|
|
@@ -183,11 +183,75 @@ export const DEFAULT_CONFIG = {
|
|
|
183
183
|
"## Output format\n\n" +
|
|
184
184
|
"Return your changes as a list of file paths with the full new content of the comment block for each modified symbol. Do NOT output the entire file unless asked.",
|
|
185
185
|
},
|
|
186
|
+
wikiMode: {
|
|
187
|
+
enabled: false,
|
|
188
|
+
systemPrompt: "You are a wiki maintainer for this codebase. You incrementally build and maintain " +
|
|
189
|
+
"a persistent knowledge wiki at `.opencode/wiki/` — a structured, interlinked collection " +
|
|
190
|
+
"of markdown files that synthesizes knowledge from the codebase, documentation, and your conversations.\n\n" +
|
|
191
|
+
"## Three layers\n\n" +
|
|
192
|
+
"- **Raw sources**: code, docs, READMEs (immutable — read with `search_semantic`, `get_file_skeleton`, `find_usages`, `read`)\n" +
|
|
193
|
+
"- **Wiki**: `.opencode/wiki/*.md` (you own this layer — create, update, and maintain ALL pages)\n" +
|
|
194
|
+
"- **Schema**: this prompt (the protocol you follow)\n\n" +
|
|
195
|
+
"## Wiki layout\n\n" +
|
|
196
|
+
"`.opencode/wiki/`\n" +
|
|
197
|
+
"- `index.md` — content catalog: every page listed with a link and one-line summary\n" +
|
|
198
|
+
"- `log.md` — append-only timeline: `## [date] op | title`\n" +
|
|
199
|
+
"- `entities/` — pages for modules, services, key classes, components\n" +
|
|
200
|
+
"- `concepts/` — pages for patterns, conventions, architectural decisions, gotchas\n" +
|
|
201
|
+
"- `sources/` — summaries of external sources (articles, specs, discussions)\n\n" +
|
|
202
|
+
"## Page frontmatter\n\n" +
|
|
203
|
+
"Every page should start with:\n" +
|
|
204
|
+
"```\n" +
|
|
205
|
+
"---\n" +
|
|
206
|
+
"title: <page title>\n" +
|
|
207
|
+
"tags: [relevant tags]\n" +
|
|
208
|
+
"sourceRefs: [file paths or URLs this page synthesizes]\n" +
|
|
209
|
+
"lastReviewed: <date>\n" +
|
|
210
|
+
"---\n" +
|
|
211
|
+
"```\n\n" +
|
|
212
|
+
"## Operations\n\n" +
|
|
213
|
+
"### Ingest (when you learn something new)\n" +
|
|
214
|
+
"1. Read the source (code, doc, external article)\n" +
|
|
215
|
+
"2. Extract key information — what it does, why it exists, how it connects to existing knowledge\n" +
|
|
216
|
+
"3. Create or update relevant entity/concept pages\n" +
|
|
217
|
+
"4. Add cross-references with `[[wiki/page-name]]` links between related pages\n" +
|
|
218
|
+
"5. Update `index.md` with any new pages\n" +
|
|
219
|
+
"6. Append to `log.md`: `## [date] ingest | page title`\n\n" +
|
|
220
|
+
"### Query (when the user asks a question)\n" +
|
|
221
|
+
"1. Read `index.md` first to find relevant wiki pages\n" +
|
|
222
|
+
"2. Read those pages with the `read` tool\n" +
|
|
223
|
+
"3. If the wiki is insufficient, use `search_semantic` for code-level retrieval\n" +
|
|
224
|
+
"4. Synthesize an answer citing wiki pages where possible\n" +
|
|
225
|
+
"5. If the answer produced valuable analysis, file it back as a new wiki page\n\n" +
|
|
226
|
+
"### Lint (periodically, or when `/wiki lint` is invoked)\n" +
|
|
227
|
+
"Check for:\n" +
|
|
228
|
+
"- Orphan pages with no inbound `[[links]]`\n" +
|
|
229
|
+
"- Stale pages whose `sourceRefs` files changed since `lastReviewed`\n" +
|
|
230
|
+
"- Contradictions between pages\n" +
|
|
231
|
+
"- Important concepts mentioned but lacking their own page\n" +
|
|
232
|
+
"Append findings to `log.md`.\n\n" +
|
|
233
|
+
"### Seed (when `/wiki seed` is invoked)\n" +
|
|
234
|
+
"Generate initial wiki from existing sources:\n" +
|
|
235
|
+
"1. Read README and top-level docs for project overview → create overview page\n" +
|
|
236
|
+
"2. Scan file structure with `get_file_skeleton` for major modules → create entity pages\n" +
|
|
237
|
+
"3. Identify common conventions from code patterns → create concept pages\n" +
|
|
238
|
+
"4. Create `index.md` listing all pages\n" +
|
|
239
|
+
"5. Create `log.md` with initial entry\n" +
|
|
240
|
+
"6. Keep pages focused — one concept per page, short prose\n\n" +
|
|
241
|
+
"## Principles\n\n" +
|
|
242
|
+
"- You write and maintain ALL wiki pages — never ask the user to do it\n" +
|
|
243
|
+
"- Good answers and analyses get filed back as new pages — explorations compound\n" +
|
|
244
|
+
"- One concept per page — short, focused, cross-referenced\n" +
|
|
245
|
+
"- The wiki is git-trackable in `.opencode/` — every edit is a diff",
|
|
246
|
+
},
|
|
186
247
|
mcp: {
|
|
187
|
-
enabled:
|
|
248
|
+
enabled: false,
|
|
188
249
|
},
|
|
189
250
|
autoUpdate: {
|
|
190
|
-
enabled:
|
|
251
|
+
enabled: true,
|
|
252
|
+
autoInstall: false,
|
|
253
|
+
cooldownMs: 3_600_000,
|
|
254
|
+
maxConsecutiveFailures: 3,
|
|
191
255
|
},
|
|
192
256
|
ui: {
|
|
193
257
|
port: 3210,
|
|
@@ -215,7 +279,7 @@ export function validateConfig(config) {
|
|
|
215
279
|
const KNOWN_TOP_KEYS = new Set([
|
|
216
280
|
"embedding", "indexing", "vectorStore", "retrieval",
|
|
217
281
|
"openCode", "chunkers", "chunking", "description",
|
|
218
|
-
"imageDescription", "documentationMode", "mcp", "autoUpdate", "ui", "tui", "logging",
|
|
282
|
+
"imageDescription", "documentationMode", "wikiMode", "mcp", "autoUpdate", "ui", "tui", "logging",
|
|
219
283
|
]);
|
|
220
284
|
const topKeys = new Set(Object.keys(config));
|
|
221
285
|
for (const key of topKeys) {
|
|
@@ -333,6 +397,8 @@ export function loadConfig(filePath, validate = true) {
|
|
|
333
397
|
let parsed;
|
|
334
398
|
try {
|
|
335
399
|
raw = readFileSync(filePath, "utf-8");
|
|
400
|
+
if (raw.charCodeAt(0) === 0xfeff)
|
|
401
|
+
raw = raw.slice(1);
|
|
336
402
|
parsed = JSON.parse(raw);
|
|
337
403
|
}
|
|
338
404
|
catch (err) {
|
|
@@ -399,6 +465,10 @@ export function loadConfig(filePath, validate = true) {
|
|
|
399
465
|
...DEFAULT_CONFIG.documentationMode,
|
|
400
466
|
...(safeObj(parsed.documentationMode) ?? {}),
|
|
401
467
|
},
|
|
468
|
+
wikiMode: {
|
|
469
|
+
...DEFAULT_CONFIG.wikiMode,
|
|
470
|
+
...(safeObj(parsed.wikiMode) ?? {}),
|
|
471
|
+
},
|
|
402
472
|
mcp: {
|
|
403
473
|
...DEFAULT_CONFIG.mcp,
|
|
404
474
|
...(safeObj(parsed.mcp) ?? {}),
|
|
@@ -11,7 +11,9 @@ export function loadRuntimeOverrides(storePath) {
|
|
|
11
11
|
if (!existsSync(overridePath))
|
|
12
12
|
return {};
|
|
13
13
|
try {
|
|
14
|
-
|
|
14
|
+
const raw = readFileSync(overridePath, "utf-8");
|
|
15
|
+
const stripped = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
|
16
|
+
return JSON.parse(stripped);
|
|
15
17
|
}
|
|
16
18
|
catch {
|
|
17
19
|
return {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import os from "node:os";
|
|
3
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, symlinkSync, } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, symlinkSync, lstatSync, } from "node:fs";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
5
|
const PLUGIN_NAME = "opencode-rag-plugin";
|
|
6
6
|
export function getRuntimeDir() {
|
|
@@ -131,38 +131,86 @@ export async function setupRuntime(options) {
|
|
|
131
131
|
writeFileSync(runtimePkg, JSON.stringify({ private: true, type: "module" }, null, 2) + "\n", "utf-8");
|
|
132
132
|
}
|
|
133
133
|
removeIfExists(runtimePluginDir);
|
|
134
|
+
// Ensure stale directory is fully gone before creating junction
|
|
135
|
+
let retries = 3;
|
|
136
|
+
while (retries > 0 && existsSync(runtimePluginDir)) {
|
|
137
|
+
try {
|
|
138
|
+
rmSync(runtimePluginDir, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
catch { /* retry */ }
|
|
141
|
+
retries--;
|
|
142
|
+
}
|
|
134
143
|
mkdirSync(path.dirname(runtimePluginDir), { recursive: true });
|
|
144
|
+
let junctionOk = false;
|
|
135
145
|
try {
|
|
136
146
|
createJunction(globalPluginDir, runtimePluginDir);
|
|
147
|
+
junctionOk = process.platform !== "win32";
|
|
148
|
+
if (process.platform === "win32") {
|
|
149
|
+
const stat = lstatSync(runtimePluginDir);
|
|
150
|
+
junctionOk = stat.isSymbolicLink();
|
|
151
|
+
}
|
|
137
152
|
}
|
|
138
153
|
catch {
|
|
154
|
+
// fall through to cpSync
|
|
155
|
+
}
|
|
156
|
+
if (!junctionOk) {
|
|
157
|
+
if (!options?.silent) {
|
|
158
|
+
console.error(" [warn] Junction not supported, falling back to copy...");
|
|
159
|
+
}
|
|
139
160
|
const { cpSync } = await import("node:fs");
|
|
140
161
|
cpSync(globalPluginDir, runtimePluginDir, { recursive: true });
|
|
141
162
|
}
|
|
163
|
+
// Ensure the @opencode-ai/plugin SDK is available globally.
|
|
164
|
+
// We must NOT run `npm install` inside the runtime dir because npm
|
|
165
|
+
// re-resolves all of node_modules/ and replaces the plugin junction
|
|
166
|
+
// with the published npm version (corrupting the local link).
|
|
167
|
+
if (!existsSync(globalSdkPluginDir)) {
|
|
168
|
+
try {
|
|
169
|
+
execSync(`npm install -g @opencode-ai/plugin`, {
|
|
170
|
+
stdio: "pipe",
|
|
171
|
+
timeout: 60_000,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (cause) {
|
|
175
|
+
errors.push(`Failed to install @opencode-ai/plugin SDK globally: ${cause.message}`);
|
|
176
|
+
return { success: false, errors };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Create junction from global SDK to runtime (same pattern as plugin above)
|
|
142
180
|
if (existsSync(globalSdkPluginDir)) {
|
|
143
181
|
removeIfExists(runtimeSdkPluginDir);
|
|
182
|
+
retries = 3;
|
|
183
|
+
while (retries > 0 && existsSync(runtimeSdkPluginDir)) {
|
|
184
|
+
try {
|
|
185
|
+
rmSync(runtimeSdkPluginDir, { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
catch { /* retry */ }
|
|
188
|
+
retries--;
|
|
189
|
+
}
|
|
144
190
|
mkdirSync(runtimeSdkDir, { recursive: true });
|
|
191
|
+
let sdkJunctionOk = false;
|
|
145
192
|
try {
|
|
146
193
|
createJunction(globalSdkPluginDir, runtimeSdkPluginDir);
|
|
194
|
+
sdkJunctionOk = process.platform !== "win32";
|
|
195
|
+
if (process.platform === "win32") {
|
|
196
|
+
const stat = lstatSync(runtimeSdkPluginDir);
|
|
197
|
+
sdkJunctionOk = stat.isSymbolicLink();
|
|
198
|
+
}
|
|
147
199
|
}
|
|
148
200
|
catch {
|
|
201
|
+
// fall through to cpSync
|
|
202
|
+
}
|
|
203
|
+
if (!sdkJunctionOk) {
|
|
204
|
+
if (!options?.silent) {
|
|
205
|
+
console.error(" [warn] SDK junction not supported, falling back to copy...");
|
|
206
|
+
}
|
|
149
207
|
const { cpSync } = await import("node:fs");
|
|
150
208
|
cpSync(globalSdkPluginDir, runtimeSdkPluginDir, { recursive: true });
|
|
151
209
|
}
|
|
152
210
|
}
|
|
153
211
|
else {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
execSync(`npm install @opencode-ai/plugin --no-save`, {
|
|
157
|
-
cwd: runtimeDir,
|
|
158
|
-
stdio: "pipe",
|
|
159
|
-
timeout: 60_000,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
catch (cause) {
|
|
163
|
-
errors.push(`Failed to install @opencode-ai/plugin SDK: ${cause.message}`);
|
|
164
|
-
return { success: false, errors };
|
|
165
|
-
}
|
|
212
|
+
errors.push(`@opencode-ai/plugin SDK not available after global install`);
|
|
213
|
+
return { success: false, errors };
|
|
166
214
|
}
|
|
167
215
|
writeFileSync(versionFile, pluginVersion, "utf-8");
|
|
168
216
|
patchWindowsWrappers(npmGlobalRoot);
|