Skip to content

Commit 0b02bc2

Browse files
committed
Fix offline plugin cache handling
1 parent ca53bb4 commit 0b02bc2

7 files changed

Lines changed: 108 additions & 75 deletions

File tree

packages/cli/src/adapters/opencode.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { dirname } from "node:path";
33
import { parse as parseJsonc, stringify as stringifyJsonc } from "comment-json";
44
import { writeFileAtomic } from "../lib/atomic-write";
55
import { isOpenCodeInstalledOnSystem } from "../lib/opencode-install";
6+
import {
7+
getOpenCodePluginPackageJsonPaths,
8+
OPENCODE_PLUGIN_ENTRY_WITH_VERSION as PLUGIN_ENTRY,
9+
OPENCODE_PLUGIN_NAME as PLUGIN_NAME,
10+
} from "../lib/opencode-plugin-cache";
611
import {
712
detectConfigPaths,
813
dirSizeBytes,
@@ -16,9 +21,6 @@ import type {
1621
PluginEntryResult,
1722
} from "./types";
1823

19-
const PLUGIN_NAME = "@cortexkit/opencode-magic-context";
20-
const PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
21-
2224
export class OpenCodeAdapter implements HarnessAdapter {
2325
readonly kind = "opencode" as const;
2426
readonly displayName = "OpenCode";
@@ -214,12 +216,7 @@ export class OpenCodeAdapter implements HarnessAdapter {
214216

215217
getInstalledPluginVersion(): string | null {
216218
// Look in OpenCode's plugin cache for the installed package version.
217-
const cacheDir = getOpenCodePluginCacheDir();
218-
const candidates = [
219-
`${cacheDir}/${PLUGIN_NAME}@latest/node_modules/${PLUGIN_NAME}/package.json`,
220-
`${cacheDir}/${PLUGIN_NAME}/node_modules/${PLUGIN_NAME}/package.json`,
221-
];
222-
for (const candidate of candidates) {
219+
for (const candidate of getOpenCodePluginPackageJsonPaths()) {
223220
if (!existsSync(candidate)) continue;
224221
try {
225222
const raw = readFileSync(candidate, "utf-8");

packages/cli/src/commands/doctor-opencode-cache.ts

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,21 @@
11
import { existsSync, readFileSync, rmSync } from "node:fs";
2-
import { join } from "node:path";
3-
import { getOpenCodeCacheDir } from "@magic-context/core/shared/data-path";
4-
5-
export const OPENCODE_PLUGIN_NAME = "@cortexkit/opencode-magic-context";
6-
export const OPENCODE_PLUGIN_ENTRY_WITH_VERSION = `${OPENCODE_PLUGIN_NAME}@latest`;
2+
import {
3+
getOpenCodePluginCacheRoots,
4+
getOpenCodePluginPackageJsonPath,
5+
} from "../lib/opencode-plugin-cache";
76

87
export interface PluginCacheResult {
9-
action: "cleared" | "up_to_date" | "not_found" | "error";
8+
action: "cleared" | "up_to_date" | "not_found" | "check_unavailable" | "error";
109
path: string;
1110
paths?: string[];
1211
cached?: string;
1312
latest?: string;
1413
error?: string;
1514
}
1615

17-
export function getOpenCodePluginCacheRoots(): string[] {
18-
const cacheDir = getOpenCodeCacheDir();
19-
return [
20-
join(cacheDir, "packages", OPENCODE_PLUGIN_ENTRY_WITH_VERSION),
21-
join(cacheDir, "packages", OPENCODE_PLUGIN_NAME),
22-
];
23-
}
24-
25-
function cachedPluginPackagePath(pluginCacheDir: string): string {
26-
return join(
27-
pluginCacheDir,
28-
"node_modules",
29-
"@cortexkit",
30-
"opencode-magic-context",
31-
"package.json",
32-
);
33-
}
34-
3516
function readCachedPluginVersion(pluginCacheDir: string): string | undefined {
3617
try {
37-
const installedPkgPath = cachedPluginPackagePath(pluginCacheDir);
18+
const installedPkgPath = getOpenCodePluginPackageJsonPath(pluginCacheDir);
3819
if (!existsSync(installedPkgPath)) return undefined;
3920
const pkg = JSON.parse(readFileSync(installedPkgPath, "utf-8")) as { version?: unknown };
4021
return typeof pkg.version === "string" ? pkg.version : undefined;
@@ -58,12 +39,20 @@ export async function clearPluginCache(
5839
path,
5940
cached: readCachedPluginVersion(path),
6041
}));
42+
43+
if (options.force !== true && latestVersion === undefined) {
44+
const firstEntry = cacheEntries[0];
45+
return {
46+
action: "check_unavailable",
47+
path: firstEntry?.path ?? pluginCacheRoots[0] ?? "",
48+
paths: cacheEntries.map((entry) => entry.path),
49+
cached: firstEntry?.cached,
50+
};
51+
}
52+
6153
const clearTargets = cacheEntries.filter(
6254
(entry) =>
63-
options.force === true ||
64-
latestVersion === undefined ||
65-
entry.cached === undefined ||
66-
entry.cached !== latestVersion,
55+
options.force === true || entry.cached === undefined || entry.cached !== latestVersion,
6756
);
6857

6958
if (clearTargets.length === 0) {

packages/cli/src/commands/doctor-opencode.test.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import {
99
import { computeLegacyRustDirIdentity } from "@magic-context/core/features/magic-context/v22-deferred-backfill";
1010
import { Database } from "@magic-context/core/shared/sqlite";
1111
import { parse as parseJsonc, stringify as stringifyJsonc } from "comment-json";
12-
import { runV22BackfillCommands } from "../lib/v22-backfill-commands";
13-
import { migrateLegacyAgentEnabledConfigForDoctor } from "./doctor-opencode";
1412
import {
15-
clearPluginCache,
1613
OPENCODE_PLUGIN_ENTRY_WITH_VERSION,
1714
OPENCODE_PLUGIN_NAME,
18-
} from "./doctor-opencode-cache";
15+
} from "../lib/opencode-plugin-cache";
16+
import { runV22BackfillCommands } from "../lib/v22-backfill-commands";
17+
import { migrateLegacyAgentEnabledConfigForDoctor } from "./doctor-opencode";
18+
import { clearPluginCache } from "./doctor-opencode-cache";
1919

2020
function migrate(input: Record<string, unknown>) {
2121
const logs: Array<{ level: "success" | "warn"; message: string }> = [];
@@ -218,14 +218,32 @@ describe("doctor OpenCode plugin cache", () => {
218218
expect(existsSync(versionlessCachePath)).toBe(false);
219219
});
220220

221-
it("clears existing cache when plugin npm latest is unavailable", async () => {
221+
it("preserves existing cache when plugin npm latest is unavailable", async () => {
222222
const cacheRoot = makeTempDir("mc-opencode-cache-");
223223
originalXdgCacheHome = process.env.XDG_CACHE_HOME;
224224
process.env.XDG_CACHE_HOME = cacheRoot;
225225
const pluginCachePath = createCachedOpenCodePlugin(cacheRoot, "0.29.1");
226226

227227
const result = await clearPluginCache({ latestVersion: null });
228228

229+
expect(result).toMatchObject({
230+
action: "check_unavailable",
231+
cached: "0.29.1",
232+
path: pluginCachePath,
233+
paths: [pluginCachePath],
234+
});
235+
expect(result.latest).toBeUndefined();
236+
expect(existsSync(pluginCachePath)).toBe(true);
237+
});
238+
239+
it("force-clears existing cache even when plugin npm latest is unavailable", async () => {
240+
const cacheRoot = makeTempDir("mc-opencode-cache-");
241+
originalXdgCacheHome = process.env.XDG_CACHE_HOME;
242+
process.env.XDG_CACHE_HOME = cacheRoot;
243+
const pluginCachePath = createCachedOpenCodePlugin(cacheRoot, "0.29.1");
244+
245+
const result = await clearPluginCache({ force: true, latestVersion: null });
246+
229247
expect(result).toMatchObject({
230248
action: "cleared",
231249
cached: "0.29.1",

packages/cli/src/commands/doctor-opencode.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ import { bundleIssueReport } from "../lib/logs-opencode";
2626
import { migrateDreamerV2ForDoctor } from "../lib/migrate-dreamer-v2-doctor";
2727
import { migrateExperimentalPinKeyFilesForDoctor } from "../lib/migrate-experimental-doctor";
2828
import { isOpenCodeInstalled } from "../lib/opencode-helpers";
29-
import { detectConfigPaths, getMagicContextLogPath } from "../lib/paths";
30-
import { confirm, intro, log, outro, selectOne, spinner, text } from "../lib/prompts";
31-
import { runV22BackfillCommands, type V22BackfillCommandArgs } from "../lib/v22-backfill-commands";
3229
import {
33-
clearPluginCache,
3430
getOpenCodePluginCacheRoots,
3531
OPENCODE_PLUGIN_ENTRY_WITH_VERSION as PLUGIN_ENTRY_WITH_VERSION,
3632
OPENCODE_PLUGIN_NAME as PLUGIN_NAME,
37-
} from "./doctor-opencode-cache";
33+
} from "../lib/opencode-plugin-cache";
34+
import { detectConfigPaths, getMagicContextLogPath } from "../lib/paths";
35+
import { confirm, intro, log, outro, selectOne, spinner, text } from "../lib/prompts";
36+
import { runV22BackfillCommands, type V22BackfillCommandArgs } from "../lib/v22-backfill-commands";
37+
import { clearPluginCache } from "./doctor-opencode-cache";
3838

3939
const CLI_PACKAGE_NAME = "@cortexkit/magic-context";
4040

@@ -1106,6 +1106,10 @@ export async function runDoctor(
11061106
fixed++;
11071107
} else if (cacheResult.action === "up_to_date") {
11081108
pass(`Plugin cache up to date (v${cacheResult.cached})`);
1109+
} else if (cacheResult.action === "check_unavailable") {
1110+
warn(
1111+
`Plugin cache version check unavailable; preserving cached plugin${cacheResult.cached ? ` (cached: ${cacheResult.cached})` : ""}. Use doctor --force to reinstall it.`,
1112+
);
11091113
} else if (cacheResult.action === "error") {
11101114
warn(`Could not clear plugin cache: ${cacheResult.error}`);
11111115
log.info(` Manually delete: ${cacheResult.path}`);

packages/cli/src/commands/setup-opencode.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ import {
1616
getOpenCodeVersion,
1717
isOpenCodeInstalled,
1818
} from "../lib/opencode-helpers";
19+
import {
20+
OPENCODE_PLUGIN_ENTRY_WITH_VERSION as PLUGIN_ENTRY,
21+
OPENCODE_PLUGIN_NAME as PLUGIN_NAME,
22+
} from "../lib/opencode-plugin-cache";
1923
import { detectConfigPaths } from "../lib/paths";
2024
import { confirm, intro, log, note, outro, promptIO, spinner } from "../lib/prompts";
2125

22-
const PLUGIN_NAME = "@cortexkit/opencode-magic-context";
23-
const PLUGIN_ENTRY = "@cortexkit/opencode-magic-context@latest";
2426
const DCP_PLUGIN_NAME = "@tarquinen/opencode-dcp";
2527

2628
// ─── Helpers ──────────────────────────────────────────────

packages/cli/src/lib/diagnostics-opencode.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ import { join } from "node:path";
1414

1515
import { parseCompartmentOutput } from "@magic-context/core/hooks/magic-context/compartment-parser";
1616
import { detectConflicts } from "@magic-context/core/shared/conflict-detector";
17-
import {
18-
getOpenCodeCacheDir,
19-
getProjectMagicContextHistorianDir,
20-
} from "@magic-context/core/shared/data-path";
17+
import { getProjectMagicContextHistorianDir } from "@magic-context/core/shared/data-path";
2118
import { parse as parseJsonc } from "comment-json";
2219
import { getOpenCodeVersion, isOpenCodeInstalled } from "./opencode-helpers";
20+
import {
21+
getOpenCodePluginCacheRoots,
22+
getOpenCodePluginPackageJsonPaths,
23+
OPENCODE_PLUGIN_ENTRY_WITH_VERSION,
24+
OPENCODE_PLUGIN_NAME,
25+
} from "./opencode-plugin-cache";
2326
import {
2427
type ConfigPaths,
2528
detectConfigPaths,
@@ -28,9 +31,6 @@ import {
2831
} from "./paths";
2932
import { sanitizeConfigValue, sanitizeDiagnosticText, sanitizePathString } from "./redaction";
3033

31-
const PLUGIN_NAME = "@cortexkit/opencode-magic-context";
32-
const PLUGIN_ENTRY_WITH_VERSION = `${PLUGIN_NAME}@latest`;
33-
3434
export interface DiagnosticReport {
3535
timestamp: string;
3636
platform: string;
@@ -216,24 +216,20 @@ function getSelfVersion(): string {
216216
}
217217

218218
function getPluginCacheInfo(): { path: string; cached?: string; latest?: string } {
219-
const path = join(getOpenCodeCacheDir(), "packages", PLUGIN_ENTRY_WITH_VERSION);
219+
const [path = ""] = getOpenCodePluginCacheRoots();
220220
let cached: string | undefined;
221-
try {
222-
const installedPkgPath = join(
223-
path,
224-
"node_modules",
225-
"@cortexkit",
226-
"opencode-magic-context",
227-
"package.json",
228-
);
229-
if (existsSync(installedPkgPath)) {
230-
const pkg = JSON.parse(readFileSync(installedPkgPath, "utf-8")) as {
231-
version?: unknown;
232-
};
233-
cached = typeof pkg.version === "string" ? pkg.version : undefined;
221+
for (const installedPkgPath of getOpenCodePluginPackageJsonPaths()) {
222+
try {
223+
if (existsSync(installedPkgPath)) {
224+
const pkg = JSON.parse(readFileSync(installedPkgPath, "utf-8")) as {
225+
version?: unknown;
226+
};
227+
cached = typeof pkg.version === "string" ? pkg.version : undefined;
228+
if (cached) break;
229+
}
230+
} catch {
231+
cached = undefined;
234232
}
235-
} catch {
236-
cached = undefined;
237233
}
238234
return { path, cached, latest: getSelfVersion() };
239235
}
@@ -283,8 +279,9 @@ function configHasPluginEntry(config: Record<string, unknown> | null): boolean {
283279
const plugins = Array.isArray(config?.plugin) ? config.plugin : [];
284280
return plugins.some((entry) => {
285281
if (typeof entry !== "string") return false;
286-
if (entry === PLUGIN_NAME) return true;
287-
if (entry.startsWith(`${PLUGIN_NAME}@`)) return true;
282+
if (entry === OPENCODE_PLUGIN_NAME) return true;
283+
if (entry === OPENCODE_PLUGIN_ENTRY_WITH_VERSION) return true;
284+
if (entry.startsWith(`${OPENCODE_PLUGIN_NAME}@`)) return true;
288285
// Local dev paths
289286
if (entry.includes("opencode-magic-context")) return true;
290287
return false;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { join } from "node:path";
2+
import { getOpenCodePluginCacheDir } from "./paths";
3+
4+
export const OPENCODE_PLUGIN_NAME = "@cortexkit/opencode-magic-context";
5+
export const OPENCODE_PLUGIN_ENTRY_WITH_VERSION = `${OPENCODE_PLUGIN_NAME}@latest`;
6+
7+
export function getOpenCodePluginCacheRoots(): string[] {
8+
const cacheDir = getOpenCodePluginCacheDir();
9+
return [
10+
join(cacheDir, OPENCODE_PLUGIN_ENTRY_WITH_VERSION),
11+
join(cacheDir, OPENCODE_PLUGIN_NAME),
12+
];
13+
}
14+
15+
export function getOpenCodePluginPackageJsonPath(pluginCacheRoot: string): string {
16+
return join(
17+
pluginCacheRoot,
18+
"node_modules",
19+
...OPENCODE_PLUGIN_NAME.split("/"),
20+
"package.json",
21+
);
22+
}
23+
24+
export function getOpenCodePluginPackageJsonPaths(): string[] {
25+
return getOpenCodePluginCacheRoots().map(getOpenCodePluginPackageJsonPath);
26+
}

0 commit comments

Comments
 (0)