Check cached embedded servers before reuse, isolate concurrent cancellation, and serialize request MCP registration and cleanup. Preserve native controls across session switches and recover usable partial inventories. Share the bundled offline OpenCode configuration with desktop and Agent launch paths, including model bridge environment forwarding. Validation: focused tests and typecheck passed; lint passed before the final fixes. The full test run exceeded 120 seconds and reported a DS stdio MCP failure. Current-source Agent host and real suspend/resume validation remain pending. Release note: 修复切换会话后 OpenCode 原生控件消失及并发取消相互影响的问题,并完善内嵌服务复用检查和桌面、Agent 的离线依赖配置。
150 lines
3.9 KiB
JavaScript
150 lines
3.9 KiB
JavaScript
const { existsSync } = require("node:fs");
|
|
const {
|
|
mkdirSync,
|
|
readFileSync,
|
|
renameSync,
|
|
rmSync,
|
|
statSync,
|
|
writeFileSync,
|
|
} = require("node:fs");
|
|
const { join } = require("node:path");
|
|
const { spawnSync } = require("node:child_process");
|
|
|
|
const opencodeVersion = "1.18.29";
|
|
const opencodePluginPackage = "@opencode-ai/plugin";
|
|
|
|
function npmInvocation() {
|
|
const npmCli = process.env.npm_execpath;
|
|
if (npmCli) {
|
|
return { command: process.execPath, prefixArgs: [npmCli] };
|
|
}
|
|
if (process.platform === "win32") {
|
|
throw new Error("npm_execpath is required to prepare OpenCode config");
|
|
}
|
|
return { command: "npm", prefixArgs: [] };
|
|
}
|
|
|
|
function pluginIntegrity(projectDir) {
|
|
const lock = JSON.parse(
|
|
readFileSync(join(projectDir, "package-lock.json"), "utf8"),
|
|
);
|
|
const entry = lock.packages?.[`node_modules/${opencodePluginPackage}`];
|
|
if (
|
|
entry?.version !== opencodeVersion ||
|
|
typeof entry.integrity !== "string"
|
|
) {
|
|
throw new Error(
|
|
`Missing locked ${opencodePluginPackage}@${opencodeVersion} integrity`,
|
|
);
|
|
}
|
|
return entry.integrity;
|
|
}
|
|
|
|
function prepareBundledOpenCodeConfig(projectDir) {
|
|
const targetDirectory = join(
|
|
projectDir,
|
|
".runtime-resources",
|
|
"opencode-config",
|
|
);
|
|
const identity = {
|
|
packageName: opencodePluginPackage,
|
|
version: opencodeVersion,
|
|
integrity: pluginIntegrity(projectDir),
|
|
};
|
|
const markerPath = join(targetDirectory, ".goodbuddy-ready.json");
|
|
try {
|
|
const ready = JSON.parse(readFileSync(markerPath, "utf8"));
|
|
if (
|
|
ready.packageName === identity.packageName &&
|
|
ready.version === identity.version &&
|
|
ready.integrity === identity.integrity &&
|
|
statSync(
|
|
join(
|
|
targetDirectory,
|
|
"node_modules",
|
|
"@opencode-ai",
|
|
"plugin",
|
|
"package.json",
|
|
),
|
|
).isFile()
|
|
) {
|
|
return targetDirectory;
|
|
}
|
|
} catch {
|
|
// Rebuild an incomplete or stale offline config template.
|
|
}
|
|
|
|
const stagingDirectory = `${targetDirectory}.staging-${process.pid}`;
|
|
rmSync(stagingDirectory, { recursive: true, force: true });
|
|
mkdirSync(stagingDirectory, { recursive: true });
|
|
try {
|
|
writeFileSync(
|
|
join(stagingDirectory, "package.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
private: true,
|
|
dependencies: {
|
|
[opencodePluginPackage]: opencodeVersion,
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
"utf8",
|
|
);
|
|
writeFileSync(join(stagingDirectory, ".gitignore"), "node_modules\n");
|
|
const npm = npmInvocation();
|
|
const environment = { ...process.env };
|
|
delete environment.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
const result = spawnSync(
|
|
npm.command,
|
|
[
|
|
...npm.prefixArgs,
|
|
"install",
|
|
"--ignore-scripts",
|
|
"--omit=dev",
|
|
"--omit=optional",
|
|
"--no-audit",
|
|
"--no-fund",
|
|
"--save-exact",
|
|
],
|
|
{
|
|
cwd: stagingDirectory,
|
|
encoding: "utf8",
|
|
env: environment,
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
shell: false,
|
|
windowsHide: true,
|
|
},
|
|
);
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
`Unable to prepare bundled OpenCode config: ${
|
|
result.error?.message ||
|
|
result.stderr ||
|
|
result.stdout ||
|
|
`npm exited with ${result.status}`
|
|
}`,
|
|
);
|
|
}
|
|
writeFileSync(
|
|
join(stagingDirectory, ".goodbuddy-ready.json"),
|
|
`${JSON.stringify(identity)}\n`,
|
|
"utf8",
|
|
);
|
|
rmSync(targetDirectory, { recursive: true, force: true });
|
|
mkdirSync(join(targetDirectory, ".."), { recursive: true });
|
|
renameSync(stagingDirectory, targetDirectory);
|
|
return targetDirectory;
|
|
} finally {
|
|
if (existsSync(stagingDirectory)) {
|
|
rmSync(stagingDirectory, { recursive: true, force: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
opencodeVersion,
|
|
prepareBundledOpenCodeConfig,
|
|
};
|