





















@@ -12,6 +12,29 @@ function readJson(file) {
1212return JSON.parse(fs.readFileSync(file, "utf8"));
1313}
141415+function resolveHomePath(value) {
16+if (value === "~") {
17+return process.env.HOME;
18+}
19+if (value?.startsWith("~/") || value?.startsWith("~\\")) {
20+return path.join(process.env.HOME ?? "", value.slice(2));
21+}
22+return value;
23+}
24+25+function comparablePath(value) {
26+const resolved = path.resolve(resolveHomePath(value));
27+try {
28+return fs.realpathSync.native(resolved);
29+} catch {
30+return resolved;
31+}
32+}
33+34+function pathsEqual(left, right) {
35+return comparablePath(left) === comparablePath(right);
36+}
37+1538function configPath() {
1639return (
1740process.env.OPENCLAW_CONFIG_PATH ??
@@ -29,6 +52,12 @@ function writeConfig(cfg) {
2952fs.writeFileSync(configPath(), `${JSON.stringify(cfg, null, 2)}\n`);
3053}
315455+function installRecords() {
56+const recordsPath = path.join(process.env.HOME ?? "", ".openclaw", "plugins", "installs.json");
57+const records = fs.existsSync(recordsPath) ? readJson(recordsPath) : {};
58+return records.installRecords ?? records.records ?? {};
59+}
60+3261function assertOnboard() {
3362const home = process.argv[3];
3463const stateDir = path.join(home, ".openclaw");
@@ -65,16 +94,63 @@ function assertFileContains() {
6594assert(raw.includes(needle), `${file} did not contain ${needle}. Output: ${raw}`);
6695}
679697+function rememberPluginInstallPath() {
98+const pluginId = process.argv[3];
99+const installPathFile = process.argv[4];
100+const sourcePathFile = process.argv[5];
101+const expectedSourcePath = process.argv[6];
102+assert(pluginId, "missing plugin id");
103+assert(installPathFile, "missing install path file");
104+const record = installRecords()[pluginId];
105+assert(record, `missing install record for ${pluginId}`);
106+const installPath = resolveHomePath(record.installPath);
107+assert(installPath, `install path missing for ${pluginId}`);
108+assert(fs.existsSync(installPath), `install path missing on disk for ${pluginId}: ${installPath}`);
109+if (expectedSourcePath && record.sourcePath) {
110+assert(
111+pathsEqual(record.sourcePath, expectedSourcePath),
112+`unexpected source path for ${pluginId}: ${record.sourcePath}, expected ${expectedSourcePath}`,
113+);
114+}
115+fs.writeFileSync(installPathFile, installPath, "utf8");
116+if (sourcePathFile && (expectedSourcePath || record.sourcePath)) {
117+fs.writeFileSync(
118+sourcePathFile,
119+expectedSourcePath || resolveHomePath(record.sourcePath),
120+"utf8",
121+);
122+}
123+}
124+68125function assertPluginUninstalled() {
69126const pluginId = process.argv[3];
127+const installPathFile = process.argv[4];
128+const sourcePathFile = process.argv[5];
70129const cfg = readJson(configPath());
71-const recordsPath = path.join(process.env.HOME ?? "", ".openclaw", "plugins", "installs.json");
72-const records = fs.existsSync(recordsPath) ? readJson(recordsPath) : {};
73-const installRecords = records.installRecords ?? records.records ?? {};
74-assert(!installRecords[pluginId], `install record still present for ${pluginId}`);
130+const records = installRecords();
131+assert(!records[pluginId], `install record still present for ${pluginId}`);
75132assert(!cfg.plugins?.entries?.[pluginId], `plugin config entry still present for ${pluginId}`);
76133assert(!(cfg.plugins?.allow ?? []).includes(pluginId), `allowlist still contains ${pluginId}`);
77134assert(!(cfg.plugins?.deny ?? []).includes(pluginId), `denylist still contains ${pluginId}`);
135+if (!installPathFile) {
136+return;
137+}
138+const installPath = fs.readFileSync(installPathFile, "utf8").trim();
139+const sourcePath =
140+sourcePathFile && fs.existsSync(sourcePathFile)
141+ ? fs.readFileSync(sourcePathFile, "utf8").trim()
142+ : "";
143+if (sourcePath) {
144+assert(
145+fs.existsSync(sourcePath),
146+`source path was deleted during uninstall for ${pluginId}: ${sourcePath}`,
147+);
148+}
149+const installPathIsSourcePath = sourcePath ? pathsEqual(installPath, sourcePath) : false;
150+assert(
151+installPathIsSourcePath || !fs.existsSync(installPath),
152+`managed plugin directory still present: ${installPath}`,
153+);
78154}
7915580156function configureClickClack() {
@@ -183,6 +259,7 @@ async function waitClickClackReply() {
183259184260const commands = {
185261"assert-onboard": assertOnboard,
262+"remember-plugin-install-path": rememberPluginInstallPath,
186263"configure-mock-model": configureMockModel,
187264"assert-agent-turn": assertAgentTurn,
188265"assert-file-contains": assertFileContains,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。