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
|
@@ -1,9 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Version check and self-update functionality. Checks GitHub
|
|
3
|
+
* releases for new versions and installs the newest version via npm, then
|
|
4
|
+
* re-syncs the OpenCode runtime junctions.
|
|
5
|
+
*/
|
|
6
|
+
import { setupRuntime } from "./setup-runtime.js";
|
|
7
|
+
/** Information about an available update. */
|
|
1
8
|
export interface UpdateInfo {
|
|
9
|
+
/** The currently installed version string. */
|
|
2
10
|
currentVersion: string;
|
|
11
|
+
/** The latest available version on GitHub. */
|
|
3
12
|
latestVersion: string;
|
|
13
|
+
/** Whether a newer version than the current one exists. */
|
|
4
14
|
updateAvailable: boolean;
|
|
15
|
+
/** URL to the GitHub release page. */
|
|
5
16
|
releaseUrl: string;
|
|
17
|
+
/** ISO date string of when the release was published. */
|
|
6
18
|
publishedAt: string;
|
|
7
19
|
}
|
|
20
|
+
/** Result of an install-update attempt. */
|
|
21
|
+
export interface InstallUpdateResult {
|
|
22
|
+
/** Whether the install completed successfully. */
|
|
23
|
+
success: boolean;
|
|
24
|
+
/** Human-readable status message. */
|
|
25
|
+
message: string;
|
|
26
|
+
/** Version before the update. */
|
|
27
|
+
fromVersion: string;
|
|
28
|
+
/** Version after the update (undefined if the install failed). */
|
|
29
|
+
toVersion?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Callable shape for the npm runner (a subset of execSync's signature). */
|
|
32
|
+
type NpmRunner = (command: string, options: {
|
|
33
|
+
stdio: "inherit" | "pipe";
|
|
34
|
+
timeout: number;
|
|
35
|
+
}) => unknown;
|
|
36
|
+
/**
|
|
37
|
+
* Read the current version from the package.json sitting next to this module.
|
|
38
|
+
*
|
|
39
|
+
* Resolves the package root via `import.meta.url` so it works both from the
|
|
40
|
+
* source tree and from the compiled `dist/` output. Returns `"0.0.0"` if the
|
|
41
|
+
* file cannot be read or parsed (best-effort — never throws).
|
|
42
|
+
*
|
|
43
|
+
* @returns The current package version string.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getCurrentVersion(): string;
|
|
46
|
+
/**
|
|
47
|
+
* Compare two semver-ish strings.
|
|
48
|
+
* @returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
49
|
+
*/
|
|
8
50
|
export declare function compareVersions(a: string, b: string): number;
|
|
51
|
+
/**
|
|
52
|
+
* Check the GitHub releases API for a newer version of OpenCodeRAG.
|
|
53
|
+
*
|
|
54
|
+
* Uses a 5-second timeout; failures (network errors, non-OK responses, missing
|
|
55
|
+
* `tag_name`) are silently caught and reported as "no update available" so the
|
|
56
|
+
* caller never has to handle a rejection.
|
|
57
|
+
*
|
|
58
|
+
* @param currentVersion - The version string to compare against.
|
|
59
|
+
* @returns UpdateInfo indicating whether an update is available.
|
|
60
|
+
*/
|
|
9
61
|
export declare function checkForUpdate(currentVersion: string): Promise<UpdateInfo>;
|
|
62
|
+
/**
|
|
63
|
+
* Install the newest published version of OpenCodeRAG.
|
|
64
|
+
*
|
|
65
|
+
* Runs `npm install -g <package>@latest` to refresh the global install, then
|
|
66
|
+
* calls {@link setupRuntime} with `force: true` to re-create the
|
|
67
|
+
* `~/.opencode/node_modules/` junctions so OpenCode picks up the new build on
|
|
68
|
+
* the next restart.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Optional verbosity flag. When `verbose` is true, npm output
|
|
71
|
+
* is streamed to the console; otherwise it is captured silently. The
|
|
72
|
+
* `_execSync` and `_setupRuntime` seams are for testing only.
|
|
73
|
+
* @returns An {@link InstallUpdateResult} with success status, message, and the
|
|
74
|
+
* from/to versions.
|
|
75
|
+
*/
|
|
76
|
+
export declare function installLatestUpdate(options?: {
|
|
77
|
+
verbose?: boolean;
|
|
78
|
+
/** Test seam: override the npm runner. */
|
|
79
|
+
_execSync?: NpmRunner;
|
|
80
|
+
/** Test seam: override the runtime sync. */
|
|
81
|
+
_setupRuntime?: typeof setupRuntime;
|
|
82
|
+
/** Test seam: override the version reader for the "to" version. */
|
|
83
|
+
_getCurrentVersion?: typeof getCurrentVersion;
|
|
84
|
+
}): Promise<InstallUpdateResult>;
|
|
85
|
+
export {};
|
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Version check and self-update functionality. Checks GitHub
|
|
3
|
+
* releases for new versions and installs the newest version via npm, then
|
|
4
|
+
* re-syncs the OpenCode runtime junctions.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { setupRuntime } from "./setup-runtime.js";
|
|
11
|
+
/** npm package name (must match the `name` field in package.json). */
|
|
12
|
+
const PACKAGE_NAME = "opencode-rag-plugin";
|
|
13
|
+
/** GitHub repo used for release checks (owner/repo). */
|
|
14
|
+
const GITHUB_REPO = "MrDoe/OpenCodeRAG";
|
|
15
|
+
const GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
|
|
16
|
+
/**
|
|
17
|
+
* Read the current version from the package.json sitting next to this module.
|
|
18
|
+
*
|
|
19
|
+
* Resolves the package root via `import.meta.url` so it works both from the
|
|
20
|
+
* source tree and from the compiled `dist/` output. Returns `"0.0.0"` if the
|
|
21
|
+
* file cannot be read or parsed (best-effort — never throws).
|
|
22
|
+
*
|
|
23
|
+
* @returns The current package version string.
|
|
24
|
+
*/
|
|
25
|
+
export function getCurrentVersion() {
|
|
26
|
+
try {
|
|
27
|
+
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
28
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
29
|
+
return pkg.version ?? "0.0.0";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "0.0.0";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Strip a leading 'v' or 'V' prefix from a version tag. */
|
|
36
|
+
function normalizeVersion(tag) {
|
|
37
|
+
return tag.replace(/^v/i, "");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Compare two semver-ish strings.
|
|
41
|
+
* @returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
42
|
+
*/
|
|
1
43
|
export function compareVersions(a, b) {
|
|
2
44
|
const pa = a.split(".").map((s) => parseInt(s, 10));
|
|
3
45
|
const pb = b.split(".").map((s) => parseInt(s, 10));
|
|
@@ -11,11 +53,21 @@ export function compareVersions(a, b) {
|
|
|
11
53
|
}
|
|
12
54
|
return 0;
|
|
13
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Check the GitHub releases API for a newer version of OpenCodeRAG.
|
|
58
|
+
*
|
|
59
|
+
* Uses a 5-second timeout; failures (network errors, non-OK responses, missing
|
|
60
|
+
* `tag_name`) are silently caught and reported as "no update available" so the
|
|
61
|
+
* caller never has to handle a rejection.
|
|
62
|
+
*
|
|
63
|
+
* @param currentVersion - The version string to compare against.
|
|
64
|
+
* @returns UpdateInfo indicating whether an update is available.
|
|
65
|
+
*/
|
|
14
66
|
export async function checkForUpdate(currentVersion) {
|
|
15
67
|
const controller = new AbortController();
|
|
16
68
|
const timeout = setTimeout(() => controller.abort(), 5_000);
|
|
17
69
|
try {
|
|
18
|
-
const response = await fetch(
|
|
70
|
+
const response = await fetch(GITHUB_API_URL, {
|
|
19
71
|
headers: {
|
|
20
72
|
Accept: "application/vnd.github+json",
|
|
21
73
|
"User-Agent": "opencode-rag-updater",
|
|
@@ -30,7 +82,7 @@ export async function checkForUpdate(currentVersion) {
|
|
|
30
82
|
if (!tagName) {
|
|
31
83
|
return { currentVersion, latestVersion: currentVersion, updateAvailable: false, releaseUrl: "", publishedAt: "" };
|
|
32
84
|
}
|
|
33
|
-
const latestVersion = tagName
|
|
85
|
+
const latestVersion = normalizeVersion(tagName);
|
|
34
86
|
return {
|
|
35
87
|
currentVersion,
|
|
36
88
|
latestVersion,
|
|
@@ -46,4 +98,63 @@ export async function checkForUpdate(currentVersion) {
|
|
|
46
98
|
clearTimeout(timeout);
|
|
47
99
|
}
|
|
48
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Install the newest published version of OpenCodeRAG.
|
|
103
|
+
*
|
|
104
|
+
* Runs `npm install -g <package>@latest` to refresh the global install, then
|
|
105
|
+
* calls {@link setupRuntime} with `force: true` to re-create the
|
|
106
|
+
* `~/.opencode/node_modules/` junctions so OpenCode picks up the new build on
|
|
107
|
+
* the next restart.
|
|
108
|
+
*
|
|
109
|
+
* @param options - Optional verbosity flag. When `verbose` is true, npm output
|
|
110
|
+
* is streamed to the console; otherwise it is captured silently. The
|
|
111
|
+
* `_execSync` and `_setupRuntime` seams are for testing only.
|
|
112
|
+
* @returns An {@link InstallUpdateResult} with success status, message, and the
|
|
113
|
+
* from/to versions.
|
|
114
|
+
*/
|
|
115
|
+
export async function installLatestUpdate(options) {
|
|
116
|
+
const verbose = options?.verbose ?? false;
|
|
117
|
+
const stdio = verbose ? "inherit" : "pipe";
|
|
118
|
+
const run = options?._execSync ?? execSync;
|
|
119
|
+
const syncRuntime = options?._setupRuntime ?? setupRuntime;
|
|
120
|
+
const readVersion = options?._getCurrentVersion ?? getCurrentVersion;
|
|
121
|
+
const fromVersion = readVersion();
|
|
122
|
+
try {
|
|
123
|
+
run(`npm install -g ${PACKAGE_NAME}@latest --no-fund --no-audit`, {
|
|
124
|
+
stdio,
|
|
125
|
+
timeout: 120_000,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
message: `npm install failed: ${err.message}`,
|
|
132
|
+
fromVersion,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const toVersion = readVersion();
|
|
136
|
+
const result = await syncRuntime({ force: true, silent: !verbose, version: toVersion });
|
|
137
|
+
if (!result.success) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
message: `installed v${toVersion} but runtime sync failed: ${result.errors.join("; ")}. Run \`opencode-rag setup\` to retry.`,
|
|
141
|
+
fromVersion,
|
|
142
|
+
toVersion,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (compareVersions(toVersion, fromVersion) <= 0) {
|
|
146
|
+
return {
|
|
147
|
+
success: true,
|
|
148
|
+
message: `Already up-to-date (v${toVersion}). Runtime re-synced.`,
|
|
149
|
+
fromVersion,
|
|
150
|
+
toVersion,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
message: `Updated v${fromVersion} → v${toVersion}. Restart OpenCode to load the new version.`,
|
|
156
|
+
fromVersion,
|
|
157
|
+
toVersion,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
49
160
|
//# sourceMappingURL=version-check.js.map
|
|
@@ -9,7 +9,7 @@ import type { ProxyConfig } from "../core/config.js";
|
|
|
9
9
|
* @param baseUrl - Cohere API base URL
|
|
10
10
|
* @param model - Model name to use for embedding
|
|
11
11
|
* @param apiKey - API key for authentication
|
|
12
|
-
* @param timeoutMs - Request timeout in milliseconds (default
|
|
12
|
+
* @param timeoutMs - Request timeout in milliseconds (default 120000)
|
|
13
13
|
* @param proxy - Optional proxy configuration
|
|
14
14
|
*/
|
|
15
15
|
export declare class CohereProvider implements EmbeddingProvider {
|
package/dist/embedder/cohere.js
CHANGED
|
@@ -5,7 +5,7 @@ import { postJson } from "./http.js";
|
|
|
5
5
|
* @param baseUrl - Cohere API base URL
|
|
6
6
|
* @param model - Model name to use for embedding
|
|
7
7
|
* @param apiKey - API key for authentication
|
|
8
|
-
* @param timeoutMs - Request timeout in milliseconds (default
|
|
8
|
+
* @param timeoutMs - Request timeout in milliseconds (default 120000)
|
|
9
9
|
* @param proxy - Optional proxy configuration
|
|
10
10
|
*/
|
|
11
11
|
export class CohereProvider {
|
|
@@ -15,7 +15,7 @@ export class CohereProvider {
|
|
|
15
15
|
apiKey;
|
|
16
16
|
timeoutMs;
|
|
17
17
|
proxy;
|
|
18
|
-
constructor(baseUrl, model, apiKey, timeoutMs =
|
|
18
|
+
constructor(baseUrl, model, apiKey, timeoutMs = 120000, proxy) {
|
|
19
19
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
20
20
|
this.model = model;
|
|
21
21
|
this.apiKey = apiKey;
|
package/dist/embedder/factory.js
CHANGED
|
@@ -16,7 +16,7 @@ import pLimit from "p-limit";
|
|
|
16
16
|
*/
|
|
17
17
|
export function createEmbedder(config) {
|
|
18
18
|
const { provider, baseUrl, model, apiKey, proxy, timeoutMs } = config.embedding;
|
|
19
|
-
const effectiveTimeoutMs = timeoutMs ??
|
|
19
|
+
const effectiveTimeoutMs = timeoutMs ?? 120000;
|
|
20
20
|
if (provider === "ollama") {
|
|
21
21
|
return new OllamaProvider(baseUrl, model, apiKey, effectiveTimeoutMs, proxy, config.logging.level);
|
|
22
22
|
}
|
package/dist/embedder/health.js
CHANGED
|
@@ -4,7 +4,7 @@ import { postJson } from "./http.js";
|
|
|
4
4
|
* Returns one result per configured model (embedding + description + image_description if enabled).
|
|
5
5
|
*/
|
|
6
6
|
export async function checkProviderHealth(config) {
|
|
7
|
-
const timeoutMs = config.embedding.timeoutMs ??
|
|
7
|
+
const timeoutMs = config.embedding.timeoutMs ?? 120000;
|
|
8
8
|
const checks = [
|
|
9
9
|
checkEmbeddingModel(config, timeoutMs),
|
|
10
10
|
];
|
|
@@ -9,7 +9,7 @@ import type { ProxyConfig } from "../core/config.js";
|
|
|
9
9
|
* @param baseUrl - Ollama server base URL (e.g. http://localhost:11434)
|
|
10
10
|
* @param model - Model name to use for embedding
|
|
11
11
|
* @param apiKey - Optional API key for authenticated endpoints
|
|
12
|
-
|
|
12
|
+
* @param timeoutMs - Request timeout in milliseconds (default 120000)
|
|
13
13
|
* @param proxy - Optional proxy configuration
|
|
14
14
|
* @param logLevel - Optional logging level for debug output
|
|
15
15
|
*/
|
package/dist/embedder/ollama.js
CHANGED
|
@@ -7,7 +7,7 @@ import { appendDebugLog } from "../core/fileLogger.js";
|
|
|
7
7
|
* @param baseUrl - Ollama server base URL (e.g. http://localhost:11434)
|
|
8
8
|
* @param model - Model name to use for embedding
|
|
9
9
|
* @param apiKey - Optional API key for authenticated endpoints
|
|
10
|
-
|
|
10
|
+
* @param timeoutMs - Request timeout in milliseconds (default 120000)
|
|
11
11
|
* @param proxy - Optional proxy configuration
|
|
12
12
|
* @param logLevel - Optional logging level for debug output
|
|
13
13
|
*/
|
|
@@ -19,7 +19,7 @@ export class OllamaProvider {
|
|
|
19
19
|
timeoutMs;
|
|
20
20
|
proxy;
|
|
21
21
|
logLevel;
|
|
22
|
-
constructor(baseUrl, model, apiKey, timeoutMs =
|
|
22
|
+
constructor(baseUrl, model, apiKey, timeoutMs = 120000, proxy, logLevel) {
|
|
23
23
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
24
24
|
this.model = model;
|
|
25
25
|
this.apiKey = apiKey;
|
|
@@ -9,7 +9,7 @@ import type { ProxyConfig } from "../core/config.js";
|
|
|
9
9
|
* @param baseUrl - API base URL (e.g. https://api.openai.com/v1)
|
|
10
10
|
* @param model - Model name to use for embedding
|
|
11
11
|
* @param apiKey - API key for authentication
|
|
12
|
-
* @param timeoutMs - Request timeout in milliseconds (default
|
|
12
|
+
* @param timeoutMs - Request timeout in milliseconds (default 120000)
|
|
13
13
|
* @param proxy - Optional proxy configuration
|
|
14
14
|
*/
|
|
15
15
|
export declare class OpenAIProvider implements EmbeddingProvider {
|
package/dist/embedder/openai.js
CHANGED
|
@@ -16,7 +16,7 @@ function inferProviderName(baseUrl) {
|
|
|
16
16
|
* @param baseUrl - API base URL (e.g. https://api.openai.com/v1)
|
|
17
17
|
* @param model - Model name to use for embedding
|
|
18
18
|
* @param apiKey - API key for authentication
|
|
19
|
-
* @param timeoutMs - Request timeout in milliseconds (default
|
|
19
|
+
* @param timeoutMs - Request timeout in milliseconds (default 120000)
|
|
20
20
|
* @param proxy - Optional proxy configuration
|
|
21
21
|
*/
|
|
22
22
|
export class OpenAIProvider {
|
|
@@ -27,7 +27,7 @@ export class OpenAIProvider {
|
|
|
27
27
|
timeoutMs;
|
|
28
28
|
proxy;
|
|
29
29
|
provider;
|
|
30
|
-
constructor(baseUrl, model, apiKey, timeoutMs =
|
|
30
|
+
constructor(baseUrl, model, apiKey, timeoutMs = 120000, proxy) {
|
|
31
31
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
32
32
|
this.model = model;
|
|
33
33
|
this.apiKey = apiKey;
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ export type { ContextOptimizationConfig, ContextOptimizationOptions } from "./re
|
|
|
25
25
|
*/
|
|
26
26
|
export { search, indexWorkspace, getContext, validateConfig, scanWorkspace, getIndexStatusSummary } from "./api.js";
|
|
27
27
|
export type { SearchOptions, IndexOptions, ContextResult, ConfigValidationResult, WorkspaceFile, IndexRunStats } from "./api.js";
|
|
28
|
+
/** Version check and self-update API. */
|
|
29
|
+
export { checkForUpdate, getCurrentVersion, installLatestUpdate, compareVersions } from "./core/version-check.js";
|
|
30
|
+
export type { UpdateInfo, InstallUpdateResult } from "./core/version-check.js";
|
|
28
31
|
/** The plugin server configuration object, used to register OpenCodeRAG as an OpenCode plugin. */
|
|
29
32
|
export declare const server: import("@opencode-ai/plugin").Plugin;
|
|
30
33
|
/** Unique identifier for the OpenCodeRAG plugin. */
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@ export { DescriptionCache } from "./core/desc-cache.js";
|
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
23
|
export { search, indexWorkspace, getContext, validateConfig, scanWorkspace, getIndexStatusSummary } from "./api.js";
|
|
24
|
+
/** Version check and self-update API. */
|
|
25
|
+
export { checkForUpdate, getCurrentVersion, installLatestUpdate, compareVersions } from "./core/version-check.js";
|
|
24
26
|
/** Plugin entry — only importable inside OpenCode's runtime. */
|
|
25
27
|
import { ragPlugin } from "./plugin.js";
|
|
26
28
|
/** The plugin server configuration object, used to register OpenCodeRAG as an OpenCode plugin. */
|
package/dist/indexer/pipeline.js
CHANGED
|
@@ -210,10 +210,17 @@ async function runIndexPassInner(options, logger) {
|
|
|
210
210
|
effectiveStore = createVectorStore(options.config, tempStorePath, options.dimension);
|
|
211
211
|
logger.debug(`Rebuilding index in temporary store at ${tempStorePath}`);
|
|
212
212
|
}
|
|
213
|
+
else if (existingCount > 0) {
|
|
214
|
+
// NEVER destroy existing data when we can't do an atomic rebuild.
|
|
215
|
+
// Abort and ask the user to run 'opencode-rag index --force' manually.
|
|
216
|
+
logger.warn("Cannot rebuild safely without embedding dimension — aborting to protect existing data. " +
|
|
217
|
+
"Run 'opencode-rag index --force' manually to rebuild.");
|
|
218
|
+
// Restore manifest entries we just deleted so the next pass can retry incrementally
|
|
219
|
+
return createIndexStats(workspaceFiles.length, manifestStatus);
|
|
220
|
+
}
|
|
213
221
|
else {
|
|
214
|
-
//
|
|
215
|
-
logger.
|
|
216
|
-
await options.store.clear();
|
|
222
|
+
// No existing data — safe to proceed with in-place indexing (no clear needed)
|
|
223
|
+
logger.debug("No existing data; indexing from scratch.");
|
|
217
224
|
}
|
|
218
225
|
}
|
|
219
226
|
const stats = createIndexStats(workspaceFiles.length, manifestStatus);
|
|
@@ -518,7 +525,7 @@ async function runIndexPassInner(options, logger) {
|
|
|
518
525
|
}
|
|
519
526
|
// ── Phase 2: Embed all texts in a single batched call ──────────────────
|
|
520
527
|
const batchSize = isOllama
|
|
521
|
-
? Math.min(options.config.indexing.ollamaMaxBatchSize ??
|
|
528
|
+
? Math.min(options.config.indexing.ollamaMaxBatchSize ?? 500, defaultBatchSize)
|
|
522
529
|
: defaultBatchSize;
|
|
523
530
|
let embeddedDone = 0;
|
|
524
531
|
const allTexts = embedQueue.map(item => item.text);
|
|
@@ -556,6 +563,8 @@ async function runIndexPassInner(options, logger) {
|
|
|
556
563
|
}
|
|
557
564
|
}
|
|
558
565
|
// ── Phase 3: Store + manifest update per file (parallel) ──────────────
|
|
566
|
+
const filesToStore = prepared.filter((p) => !earlyWorkerResults.has(prepared.indexOf(p)) && p.chunks && (p.textToEmbed?.length ?? 0) > 0).length;
|
|
567
|
+
let storedFiles = 0;
|
|
559
568
|
const storeLimit = pLimit(options.config.indexing.concurrency);
|
|
560
569
|
const storeResults = await Promise.all(prepared.map((prep, fi) => storeLimit(async () => {
|
|
561
570
|
if (aborted()) {
|
|
@@ -618,6 +627,8 @@ async function runIndexPassInner(options, logger) {
|
|
|
618
627
|
enqueueManifestSave();
|
|
619
628
|
}
|
|
620
629
|
options.progress?.finishFile(prep.fileLabel);
|
|
630
|
+
storedFiles++;
|
|
631
|
+
logChunkProgress("Storing", prep.fileLabel, storedFiles, filesToStore, storedFiles, filesToStore);
|
|
621
632
|
return result;
|
|
622
633
|
})));
|
|
623
634
|
const workerResults = storeResults;
|
package/dist/plugin.js
CHANGED
|
@@ -22,9 +22,10 @@ import { loadDocProgress, markSubdirectoryDocumented } from "./core/doc-progress
|
|
|
22
22
|
import { loadManifest } from "./core/manifest.js";
|
|
23
23
|
import { createSessionLogger } from "./eval/session-logger.js";
|
|
24
24
|
import { countTokens } from "./eval/token-counter.js";
|
|
25
|
-
import { checkForUpdate } from "./core/version-check.js";
|
|
25
|
+
import { checkForUpdate, getCurrentVersion, installLatestUpdate } from "./core/version-check.js";
|
|
26
|
+
import { loadAutoUpdateState, saveAutoUpdateState, shouldAttemptInstall } from "./core/auto-update-state.js";
|
|
26
27
|
import { destroyAllPooledConnections } from "./embedder/http.js";
|
|
27
|
-
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
28
|
+
import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
28
29
|
import path from "node:path";
|
|
29
30
|
import { fileURLToPath } from "node:url";
|
|
30
31
|
import { spawn, execSync } from "node:child_process";
|
|
@@ -39,6 +40,10 @@ const backgroundIndexers = new Map();
|
|
|
39
40
|
const mcpServers = new Map();
|
|
40
41
|
/** Pending update notifications keyed by workspace directory. */
|
|
41
42
|
const pendingUpdateInfo = new Map();
|
|
43
|
+
/** Workspace directories that have already been prompted about an update this session. */
|
|
44
|
+
const notifiedUpdateDirs = new Set();
|
|
45
|
+
/** Pending "restart needed" notice after a background auto-install. */
|
|
46
|
+
const pendingRestartNotice = new Map();
|
|
42
47
|
/** Guard flag to prevent re-entrant shutdown. */
|
|
43
48
|
let shutdownRegistered = false;
|
|
44
49
|
/** Close all active background indexers and MCP servers, then destroy idle sockets. */
|
|
@@ -59,6 +64,8 @@ async function shutdownPluginResources() {
|
|
|
59
64
|
}
|
|
60
65
|
configCache.clear();
|
|
61
66
|
pendingUpdateInfo.clear();
|
|
67
|
+
pendingRestartNotice.clear();
|
|
68
|
+
notifiedUpdateDirs.clear();
|
|
62
69
|
destroyAllPooledConnections();
|
|
63
70
|
}
|
|
64
71
|
/** Register a process.beforeExit handler so that resources are cleaned up
|
|
@@ -685,17 +692,37 @@ export function createRagHooks(options) {
|
|
|
685
692
|
];
|
|
686
693
|
output.system.unshift(guidance.join("\n"));
|
|
687
694
|
}
|
|
688
|
-
// Inject update
|
|
695
|
+
// Inject a one-time update prompt so the agent asks the user to install.
|
|
696
|
+
// Fires only once per workspace per session to avoid nagging on every turn.
|
|
689
697
|
const updateInfo = pendingUpdateInfo.get(options.worktree);
|
|
690
|
-
if (updateInfo) {
|
|
691
|
-
|
|
692
|
-
|
|
698
|
+
if (updateInfo && !notifiedUpdateDirs.has(options.worktree)) {
|
|
699
|
+
notifiedUpdateDirs.add(options.worktree);
|
|
700
|
+
pendingUpdateInfo.delete(options.worktree);
|
|
701
|
+
output.system.unshift(`A newer OpenCodeRAG version is available: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}. ` +
|
|
702
|
+
`At the very start of your next response, briefly tell the user a new version is available and ask whether ` +
|
|
703
|
+
`they would like you to install it. If they agree, run \`opencode-rag update\` via the bash tool, then tell ` +
|
|
704
|
+
`them to restart OpenCode to load the new version. Do not mention this notice again.`);
|
|
705
|
+
}
|
|
706
|
+
// Inject a one-time "restart needed" notice after a background auto-install.
|
|
707
|
+
// Fires only once per workspace per session, mirroring the notify-only pattern above.
|
|
708
|
+
const restartInfo = pendingRestartNotice.get(options.worktree);
|
|
709
|
+
if (restartInfo && !notifiedUpdateDirs.has(options.worktree + ":restart")) {
|
|
710
|
+
notifiedUpdateDirs.add(options.worktree + ":restart");
|
|
711
|
+
pendingRestartNotice.delete(options.worktree);
|
|
712
|
+
output.system.unshift(`OpenCodeRAG was automatically updated from v${restartInfo.fromVersion} to v${restartInfo.toVersion} ` +
|
|
713
|
+
`in the background. At the very start of your next response, tell the user the update was installed ` +
|
|
714
|
+
`and ask them to restart OpenCode to load the new version. Do not mention this notice again.`);
|
|
693
715
|
}
|
|
694
716
|
// Inject documentation mode system prompt if enabled
|
|
695
717
|
const docMode = getEffectiveCfg().documentationMode;
|
|
696
718
|
if (docMode?.enabled && docMode.systemPrompt) {
|
|
697
719
|
output.system.unshift(docMode.systemPrompt);
|
|
698
720
|
}
|
|
721
|
+
// Inject wiki mode system prompt if enabled
|
|
722
|
+
const wikiMode = getEffectiveCfg().wikiMode;
|
|
723
|
+
if (wikiMode?.enabled && wikiMode.systemPrompt) {
|
|
724
|
+
output.system.unshift(wikiMode.systemPrompt);
|
|
725
|
+
}
|
|
699
726
|
},
|
|
700
727
|
async "chat.message"(input, output) {
|
|
701
728
|
try {
|
|
@@ -797,6 +824,64 @@ export function createRagHooks(options) {
|
|
|
797
824
|
}
|
|
798
825
|
return;
|
|
799
826
|
}
|
|
827
|
+
// Handle /wiki slash command
|
|
828
|
+
if (text.startsWith("/wiki")) {
|
|
829
|
+
const wikiMode = getEffectiveCfg().wikiMode;
|
|
830
|
+
if (!wikiMode?.enabled) {
|
|
831
|
+
const parts = output?.parts ?? output?.message?.parts;
|
|
832
|
+
if (Array.isArray(parts) && parts.length > 0) {
|
|
833
|
+
const first = parts[0];
|
|
834
|
+
if (typeof first.text === "string") {
|
|
835
|
+
parts[0] = { ...first, text: "Wiki mode is not enabled. Set `wikiMode.enabled` to `true` in opencode-rag.json." };
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const arg = text.slice(5).trim().toLowerCase();
|
|
841
|
+
const wikiDir = path.join(options.worktree, ".opencode", "wiki");
|
|
842
|
+
const lines = [];
|
|
843
|
+
if (arg === "lint") {
|
|
844
|
+
lines.push("## Wiki Lint", "", "Run a health check on the wiki. Check for:", "- Orphan pages with no inbound [[links]]", "- Stale pages whose sourceRefs files changed since lastReviewed", "- Contradictions between pages", "- Important concepts mentioned but lacking their own page", "", "Append findings to .opencode/wiki/log.md and fix any issues found.");
|
|
845
|
+
}
|
|
846
|
+
else if (arg === "seed") {
|
|
847
|
+
lines.push("## Wiki Seed", "", "Generate the initial wiki from existing sources:", "1. Read README and top-level docs for project overview → create overview page", "2. Scan file structure with get_file_skeleton for major modules → create entity pages", "3. Identify common conventions from code patterns → create concept pages", "4. Create .opencode/wiki/index.md listing all pages", "5. Create .opencode/wiki/log.md with initial entry", "6. Keep pages focused — one concept per page, short prose, cross-referenced with [[links]]");
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
if (existsSync(wikiDir)) {
|
|
851
|
+
const entries = readdirSync(wikiDir);
|
|
852
|
+
const pages = entries.filter((e) => e.endsWith(".md") && e !== "log.md").length;
|
|
853
|
+
const hasIndex = entries.includes("index.md");
|
|
854
|
+
const hasLog = entries.includes("log.md");
|
|
855
|
+
const dirs = entries.filter((e) => !e.includes(".")).length;
|
|
856
|
+
let lastLogEntry = "";
|
|
857
|
+
const logPath = path.join(wikiDir, "log.md");
|
|
858
|
+
if (hasLog && existsSync(logPath)) {
|
|
859
|
+
const logContent = readFileSync(logPath, "utf-8");
|
|
860
|
+
const logLines = logContent.trim().split("\n").filter((l) => l.startsWith("## ["));
|
|
861
|
+
if (logLines.length > 0) {
|
|
862
|
+
lastLogEntry = logLines[logLines.length - 1] ?? "";
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
lines.push("## Wiki Status", "", `Wiki: \`.opencode/wiki/\``, `Pages: ~${pages} (.md files)`, `Subdirectories: \`${dirs}\``, `Index: ${hasIndex ? "yes" : "no"} | Log: ${hasLog ? "yes" : "no"}`);
|
|
866
|
+
if (lastLogEntry) {
|
|
867
|
+
lines.push(`Latest log: \`${lastLogEntry}\``);
|
|
868
|
+
}
|
|
869
|
+
lines.push("", "Commands:", "- `/wiki` — show this status", "- `/wiki lint` — health-check the wiki", "- `/wiki seed` — generate initial wiki pages from the codebase");
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
lines.push("## Wiki Status", "", "No wiki yet at `.opencode/wiki/`.", "Use `/wiki seed` to generate the initial wiki from existing sources.");
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
const wikiMsg = lines.join("\n");
|
|
876
|
+
const parts = output?.parts ?? output?.message?.parts;
|
|
877
|
+
if (Array.isArray(parts) && parts.length > 0) {
|
|
878
|
+
const first = parts[0];
|
|
879
|
+
if (typeof first.text === "string") {
|
|
880
|
+
parts[0] = { ...first, text: wikiMsg };
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
800
885
|
// Handle hotkey-triggered RAG injection (Ctrl+Enter / Ctrl+Alt+Enter)
|
|
801
886
|
const pendingInjection = consumePendingRagInjection(options.storePath);
|
|
802
887
|
if (pendingInjection) {
|
|
@@ -1090,6 +1175,8 @@ export const ragPlugin = async (input, _options) => {
|
|
|
1090
1175
|
// Clean up stale config cache and pending update info for this directory
|
|
1091
1176
|
configCache.delete(input.directory);
|
|
1092
1177
|
pendingUpdateInfo.delete(input.directory);
|
|
1178
|
+
pendingRestartNotice.delete(input.directory);
|
|
1179
|
+
notifiedUpdateDirs.delete(input.directory);
|
|
1093
1180
|
// Clean up idle HTTP sockets from previous provider connections
|
|
1094
1181
|
destroyAllPooledConnections();
|
|
1095
1182
|
appendDebugLog(logFilePath, {
|
|
@@ -1165,6 +1252,7 @@ export const ragPlugin = async (input, _options) => {
|
|
|
1165
1252
|
logLevel,
|
|
1166
1253
|
keywordIndex,
|
|
1167
1254
|
descriptionProvider,
|
|
1255
|
+
dimension: vectorDimension,
|
|
1168
1256
|
});
|
|
1169
1257
|
backgroundIndexers.set(input.directory, indexer);
|
|
1170
1258
|
}
|
|
@@ -1180,28 +1268,87 @@ export const ragPlugin = async (input, _options) => {
|
|
|
1180
1268
|
catch { /* ignore */ }
|
|
1181
1269
|
}
|
|
1182
1270
|
}
|
|
1183
|
-
// Auto-start MCP server if enabled (
|
|
1184
|
-
|
|
1271
|
+
// Auto-start standalone MCP server if enabled (default: off). Agent tools
|
|
1272
|
+
// (search_semantic, get_file_skeleton, find_usages, describe_image) and the
|
|
1273
|
+
// chat.message hook run in-process regardless. Enable only when an external
|
|
1274
|
+
// MCP client connects to `opencode-rag mcp`.
|
|
1275
|
+
const mcpCfg = effectiveCfg.mcp ?? { enabled: false };
|
|
1185
1276
|
const isTempDir = path.resolve(input.directory).startsWith(tmpdir());
|
|
1186
1277
|
if (mcpCfg.enabled && !isTempDir) {
|
|
1187
1278
|
const mcpInstance = startMcpServerProcess(input.directory, logFilePath, logLevel);
|
|
1188
1279
|
mcpServers.set(input.directory, mcpInstance);
|
|
1189
1280
|
}
|
|
1190
|
-
// Auto-update check (non-blocking, best-effort)
|
|
1281
|
+
// Auto-update check (non-blocking, best-effort). On by default; can be
|
|
1282
|
+
// disabled via `autoUpdate.enabled: false` in opencode-rag.json. When a newer
|
|
1283
|
+
// release is found it is either surfaced as a one-time install prompt via
|
|
1284
|
+
// the system transform hook (default) or automatically installed in the
|
|
1285
|
+
// background when `autoUpdate.autoInstall: true` is configured.
|
|
1191
1286
|
const autoUpdateCfg = effectiveCfg.autoUpdate;
|
|
1192
1287
|
if (autoUpdateCfg?.enabled) {
|
|
1193
|
-
const currentVersion = (
|
|
1194
|
-
try {
|
|
1195
|
-
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
1196
|
-
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
1197
|
-
}
|
|
1198
|
-
catch {
|
|
1199
|
-
return "0.0.0";
|
|
1200
|
-
}
|
|
1201
|
-
})();
|
|
1288
|
+
const currentVersion = getCurrentVersion();
|
|
1202
1289
|
checkForUpdate(currentVersion)
|
|
1203
1290
|
.then((info) => {
|
|
1204
|
-
if (info.updateAvailable)
|
|
1291
|
+
if (!info.updateAvailable)
|
|
1292
|
+
return;
|
|
1293
|
+
if (autoUpdateCfg?.autoInstall) {
|
|
1294
|
+
// Background auto-install branch (opt-in). Persist a cooldown file
|
|
1295
|
+
// so we don't re-install on every plugin reload or after failures.
|
|
1296
|
+
const cooldownMs = autoUpdateCfg.cooldownMs ?? 3_600_000;
|
|
1297
|
+
const maxFailures = autoUpdateCfg.maxConsecutiveFailures ?? 3;
|
|
1298
|
+
const state = loadAutoUpdateState(storePath);
|
|
1299
|
+
const { attempt, reason } = shouldAttemptInstall(state, info.latestVersion, cooldownMs, maxFailures);
|
|
1300
|
+
if (!attempt) {
|
|
1301
|
+
appendDebugLog(logFilePath, {
|
|
1302
|
+
scope: "updater",
|
|
1303
|
+
message: `Auto-install skipped: ${reason}`,
|
|
1304
|
+
}, logLevel);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
saveAutoUpdateState(storePath, {
|
|
1308
|
+
lastAttemptAt: Date.now(),
|
|
1309
|
+
lastAttemptedVersion: info.latestVersion,
|
|
1310
|
+
lastResult: "failure",
|
|
1311
|
+
consecutiveFailures: 1,
|
|
1312
|
+
});
|
|
1313
|
+
installLatestUpdate({ verbose: false })
|
|
1314
|
+
.then((result) => {
|
|
1315
|
+
if (result.success && result.toVersion && result.toVersion !== currentVersion) {
|
|
1316
|
+
pendingRestartNotice.set(input.directory, {
|
|
1317
|
+
fromVersion: currentVersion,
|
|
1318
|
+
toVersion: result.toVersion,
|
|
1319
|
+
});
|
|
1320
|
+
saveAutoUpdateState(storePath, {
|
|
1321
|
+
lastAttemptAt: Date.now(),
|
|
1322
|
+
lastAttemptedVersion: info.latestVersion,
|
|
1323
|
+
lastResult: "success",
|
|
1324
|
+
consecutiveFailures: 0,
|
|
1325
|
+
});
|
|
1326
|
+
appendDebugLog(logFilePath, {
|
|
1327
|
+
scope: "updater",
|
|
1328
|
+
message: `Auto-installed v${currentVersion} → v${result.toVersion}`,
|
|
1329
|
+
}, logLevel);
|
|
1330
|
+
}
|
|
1331
|
+
else {
|
|
1332
|
+
saveAutoUpdateState(storePath, {
|
|
1333
|
+
lastAttemptAt: Date.now(),
|
|
1334
|
+
lastAttemptedVersion: info.latestVersion,
|
|
1335
|
+
lastResult: "success",
|
|
1336
|
+
consecutiveFailures: 0,
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
})
|
|
1340
|
+
.catch(() => {
|
|
1341
|
+
const existing = loadAutoUpdateState(storePath);
|
|
1342
|
+
saveAutoUpdateState(storePath, {
|
|
1343
|
+
lastAttemptAt: Date.now(),
|
|
1344
|
+
lastAttemptedVersion: info.latestVersion,
|
|
1345
|
+
lastResult: "failure",
|
|
1346
|
+
consecutiveFailures: (existing?.consecutiveFailures ?? 0) + 1,
|
|
1347
|
+
});
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
else {
|
|
1351
|
+
// Notify-only: surface as a one-time agent prompt
|
|
1205
1352
|
pendingUpdateInfo.set(input.directory, info);
|
|
1206
1353
|
appendDebugLog(logFilePath, {
|
|
1207
1354
|
scope: "updater",
|