openclaw-channel-dmwork 0.5.21 → 0.6.0-dev.1c691678
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/dist/cli/bind.d.ts +12 -0
- package/dist/cli/bind.js +104 -0
- package/dist/cli/bind.js.map +1 -0
- package/dist/cli/doctor.js +48 -30
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +53 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install.d.ts +17 -7
- package/dist/cli/install.js +216 -127
- package/dist/cli/install.js.map +1 -1
- package/dist/cli/openclaw-cli.d.ts +92 -0
- package/dist/cli/openclaw-cli.js +602 -47
- package/dist/cli/openclaw-cli.js.map +1 -1
- package/dist/cli/quickstart.d.ts +17 -0
- package/dist/cli/quickstart.js +251 -0
- package/dist/cli/quickstart.js.map +1 -0
- package/dist/cli/update.d.ts +3 -4
- package/dist/cli/update.js +7 -82
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/utils.d.ts +3 -3
- package/dist/cli/utils.js +4 -5
- package/dist/cli/utils.js.map +1 -1
- package/dist/package.json +4 -3
- package/dist/src/api-fetch.d.ts +6 -1
- package/dist/src/api-fetch.js +37 -3
- package/dist/src/api-fetch.js.map +1 -1
- package/dist/src/channel.js +30 -17
- package/dist/src/channel.js.map +1 -1
- package/dist/src/inbound.js +12 -67
- package/dist/src/inbound.js.map +1 -1
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +3 -0
- package/dist/src/version.js.map +1 -0
- package/openclaw.plugin.json +3 -0
- package/package.json +4 -3
- package/skills/dmwork-bot-api/SKILL.md +982 -0
package/dist/cli/openclaw-cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* argument arrays to avoid shell-quoting issues.
|
|
5
5
|
*/
|
|
6
6
|
import { execFileSync, execSync } from "node:child_process";
|
|
7
|
-
import { readFileSync, writeFileSync, copyFileSync, existsSync } from "node:fs";
|
|
7
|
+
import { readFileSync, writeFileSync, copyFileSync, existsSync, rmSync, readdirSync, statSync, renameSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
10
|
/**
|
|
@@ -15,6 +15,7 @@ import { resolve } from "node:path";
|
|
|
15
15
|
* causes version incompatibility crashes on older OpenClaw servers.
|
|
16
16
|
*/
|
|
17
17
|
function findGlobalOpenclaw() {
|
|
18
|
+
const isWindows = process.platform === "win32";
|
|
18
19
|
// Strategy 1: use "which -a" (Unix) or "where" (Windows) to find all openclaw paths
|
|
19
20
|
// Skip: _npx (npx cache), npx-cache, node_modules (project-local devDependency)
|
|
20
21
|
for (const cmd of ["which -a openclaw", "where openclaw"]) {
|
|
@@ -37,7 +38,26 @@ function findGlobalOpenclaw() {
|
|
|
37
38
|
// command not available on this platform
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
// Strategy 2:
|
|
41
|
+
// Strategy 2 (Windows): npm global prefix + openclaw.cmd
|
|
42
|
+
// npm i -g openclaw 在 Windows 上生成 .ps1 和 .cmd,但 npx 子进程的 PATH
|
|
43
|
+
// 可能不包含 npm 全局目录,导致 where 找不到。直接通过 npm prefix 定位。
|
|
44
|
+
if (isWindows) {
|
|
45
|
+
try {
|
|
46
|
+
const prefix = execSync("npm config get prefix", {
|
|
47
|
+
encoding: "utf-8",
|
|
48
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
49
|
+
}).trim();
|
|
50
|
+
if (prefix) {
|
|
51
|
+
const cmdPath = resolve(prefix, "openclaw.cmd");
|
|
52
|
+
if (existsSync(cmdPath))
|
|
53
|
+
return cmdPath;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// npm not available
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Strategy 3: check common global install paths
|
|
41
61
|
const candidates = [
|
|
42
62
|
"/opt/homebrew/bin/openclaw",
|
|
43
63
|
"/usr/local/bin/openclaw",
|
|
@@ -52,6 +72,24 @@ function findGlobalOpenclaw() {
|
|
|
52
72
|
return "openclaw";
|
|
53
73
|
}
|
|
54
74
|
const OPENCLAW = findGlobalOpenclaw();
|
|
75
|
+
const NEEDS_SHELL = process.platform === "win32" && /\.cmd$/i.test(OPENCLAW);
|
|
76
|
+
/**
|
|
77
|
+
* Execute openclaw CLI command. On Windows, .cmd shims require shell: true.
|
|
78
|
+
* All openclaw invocations in this module MUST go through this function.
|
|
79
|
+
*
|
|
80
|
+
* 使用 execFileSync + shell:true 而非手动拼字符串,
|
|
81
|
+
* 让 Node.js 自动处理 cmd.exe 参数转义(包括 JSON 内的引号)。
|
|
82
|
+
*/
|
|
83
|
+
function runOpenclaw(args, opts = {}) {
|
|
84
|
+
const shellOpt = NEEDS_SHELL ? { shell: true } : {};
|
|
85
|
+
return execFileSync(OPENCLAW, args, { encoding: "utf-8", ...shellOpt, ...opts });
|
|
86
|
+
}
|
|
87
|
+
/** Get the resolved openclaw binary path */
|
|
88
|
+
export function getOpenClawBin() {
|
|
89
|
+
return OPENCLAW;
|
|
90
|
+
}
|
|
91
|
+
/** Execute openclaw command (exported for quickstart.ts etc.) */
|
|
92
|
+
export { runOpenclaw };
|
|
55
93
|
/** Expand ~ to home directory */
|
|
56
94
|
function expandHome(p) {
|
|
57
95
|
if (p.startsWith("~/"))
|
|
@@ -62,14 +100,44 @@ function expandHome(p) {
|
|
|
62
100
|
// Config helpers
|
|
63
101
|
// ---------------------------------------------------------------------------
|
|
64
102
|
export function getConfigFilePath() {
|
|
65
|
-
|
|
103
|
+
const out = runOpenclaw(["config", "file"]);
|
|
104
|
+
// openclaw may prepend warnings/box-drawing to stdout; extract the actual path
|
|
105
|
+
// The path is typically the last non-empty line containing openclaw.json
|
|
106
|
+
const lines = out.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
107
|
+
const pathLine = lines.find((l) => l.endsWith("openclaw.json")) ?? lines[lines.length - 1];
|
|
108
|
+
return pathLine ?? out.trim();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Strip OpenClaw stdout noise (banner, plugin log lines, timestamps).
|
|
112
|
+
* Old OpenClaw versions mix these into stdout alongside the actual value.
|
|
113
|
+
*/
|
|
114
|
+
function stripStdoutNoise(raw) {
|
|
115
|
+
return raw
|
|
116
|
+
.split("\n")
|
|
117
|
+
.filter((line) => {
|
|
118
|
+
const t = line.trim();
|
|
119
|
+
if (!t)
|
|
120
|
+
return false;
|
|
121
|
+
// Banner: 🦞 OpenClaw ...
|
|
122
|
+
if (/^[\u{1F980}\u{1F600}-\u{1FAFF}]/u.test(t))
|
|
123
|
+
return false;
|
|
124
|
+
// Plugin log: [plugins] ..., [dmwork] ...
|
|
125
|
+
if (/^\[[\w-]+\]/.test(t))
|
|
126
|
+
return false;
|
|
127
|
+
// Timestamped log: 17:37:26 [plugins] ...
|
|
128
|
+
if (/^\d{1,2}:\d{2}(:\d{2})?\s*\[/.test(t))
|
|
129
|
+
return false;
|
|
130
|
+
return true;
|
|
131
|
+
})
|
|
132
|
+
.join("\n")
|
|
133
|
+
.trim();
|
|
66
134
|
}
|
|
67
135
|
export function configGet(path) {
|
|
68
136
|
try {
|
|
69
|
-
const
|
|
70
|
-
encoding: "utf-8",
|
|
137
|
+
const raw = runOpenclaw(["config", "get", path], {
|
|
71
138
|
stdio: ["pipe", "pipe", "pipe"],
|
|
72
|
-
})
|
|
139
|
+
});
|
|
140
|
+
const val = stripStdoutNoise(raw);
|
|
73
141
|
return val === "" ? null : val;
|
|
74
142
|
}
|
|
75
143
|
catch {
|
|
@@ -78,8 +146,7 @@ export function configGet(path) {
|
|
|
78
146
|
}
|
|
79
147
|
export function configGetJson(path) {
|
|
80
148
|
try {
|
|
81
|
-
const out =
|
|
82
|
-
encoding: "utf-8",
|
|
149
|
+
const out = runOpenclaw(["config", "get", path, "--json"], {
|
|
83
150
|
stdio: ["pipe", "pipe", "pipe"],
|
|
84
151
|
});
|
|
85
152
|
const jsonStart = out.indexOf("{");
|
|
@@ -89,88 +156,264 @@ export function configGetJson(path) {
|
|
|
89
156
|
: Math.max(jsonStart, arrStart);
|
|
90
157
|
if (start < 0)
|
|
91
158
|
return null;
|
|
92
|
-
|
|
159
|
+
// Find matching end bracket to avoid trailing log noise breaking JSON.parse
|
|
160
|
+
const openChar = out[start];
|
|
161
|
+
const closeChar = openChar === "{" ? "}" : "]";
|
|
162
|
+
let depth = 0;
|
|
163
|
+
let end = -1;
|
|
164
|
+
for (let i = start; i < out.length; i++) {
|
|
165
|
+
if (out[i] === openChar)
|
|
166
|
+
depth++;
|
|
167
|
+
else if (out[i] === closeChar) {
|
|
168
|
+
depth--;
|
|
169
|
+
if (depth === 0) {
|
|
170
|
+
end = i;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (end < 0)
|
|
176
|
+
return null;
|
|
177
|
+
return JSON.parse(out.slice(start, end + 1));
|
|
93
178
|
}
|
|
94
179
|
catch {
|
|
95
180
|
return null;
|
|
96
181
|
}
|
|
97
182
|
}
|
|
98
183
|
export function configSet(path, value) {
|
|
99
|
-
|
|
184
|
+
runOpenclaw(["config", "set", path, value], {
|
|
100
185
|
stdio: ["pipe", "pipe", "pipe"],
|
|
101
186
|
});
|
|
102
187
|
}
|
|
103
188
|
export function configSetBatch(operations) {
|
|
104
189
|
const batchJson = JSON.stringify(operations.map((op) => ({ path: op.path, value: op.value })));
|
|
105
|
-
|
|
190
|
+
runOpenclaw(["config", "set", "--batch-json", batchJson], {
|
|
106
191
|
stdio: ["pipe", "pipe", "pipe"],
|
|
107
192
|
});
|
|
108
193
|
}
|
|
109
194
|
export function configSetJson(path, value) {
|
|
110
|
-
|
|
195
|
+
runOpenclaw(["config", "set", path, JSON.stringify(value), "--strict-json"], {
|
|
111
196
|
stdio: ["pipe", "pipe", "pipe"],
|
|
112
197
|
});
|
|
113
198
|
}
|
|
114
199
|
export function configUnset(path) {
|
|
115
|
-
|
|
200
|
+
runOpenclaw(["config", "unset", path], {
|
|
116
201
|
stdio: ["pipe", "pipe", "pipe"],
|
|
117
202
|
});
|
|
118
203
|
}
|
|
119
204
|
// ---------------------------------------------------------------------------
|
|
120
205
|
// Plugin helpers
|
|
121
206
|
// ---------------------------------------------------------------------------
|
|
207
|
+
/**
|
|
208
|
+
* Check if an error indicates an unsupported CLI option.
|
|
209
|
+
* Checks stderr/stdout/message across different Node versions and shells.
|
|
210
|
+
*/
|
|
211
|
+
function isUnsupportedOptionError(err) {
|
|
212
|
+
const sources = [
|
|
213
|
+
err?.stderr?.toString?.(),
|
|
214
|
+
err?.stdout?.toString?.(),
|
|
215
|
+
err?.message,
|
|
216
|
+
String(err),
|
|
217
|
+
];
|
|
218
|
+
return sources.some((s) => s && (/unknown option|unrecognized option/i.test(s)));
|
|
219
|
+
}
|
|
220
|
+
function isPluginNotInstalledError(err) {
|
|
221
|
+
const sources = [
|
|
222
|
+
err?.stderr?.toString?.(),
|
|
223
|
+
err?.stdout?.toString?.(),
|
|
224
|
+
err?.message,
|
|
225
|
+
String(err),
|
|
226
|
+
];
|
|
227
|
+
return sources.some((s) => s && (/not installed|no such plugin|plugin not found/i.test(s)));
|
|
228
|
+
}
|
|
122
229
|
export function pluginsInstall(spec, quiet, force) {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
230
|
+
const baseArgs = ["plugins", "install", spec];
|
|
231
|
+
// 3-layer degradation for old openclaw versions:
|
|
232
|
+
// 1. --force --dangerously-force-unsafe-install (newest openclaw)
|
|
233
|
+
// 2. --force (mid-age openclaw)
|
|
234
|
+
// 3. bare install (oldest openclaw)
|
|
235
|
+
const attempts = force
|
|
236
|
+
? [
|
|
237
|
+
[...baseArgs, "--force", "--dangerously-force-unsafe-install"],
|
|
238
|
+
[...baseArgs, "--force"],
|
|
239
|
+
baseArgs,
|
|
240
|
+
]
|
|
241
|
+
: [
|
|
242
|
+
[...baseArgs, "--dangerously-force-unsafe-install"],
|
|
243
|
+
baseArgs,
|
|
244
|
+
];
|
|
245
|
+
// Always pipe to capture stderr for degradation detection.
|
|
246
|
+
// stdio: "inherit" causes Node to omit stderr from the error object,
|
|
247
|
+
// making isUnsupportedOptionError() unable to detect "unknown option".
|
|
248
|
+
for (let i = 0; i < attempts.length; i++) {
|
|
249
|
+
try {
|
|
250
|
+
const result = runOpenclaw(attempts[i], {
|
|
251
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
252
|
+
encoding: "utf-8",
|
|
253
|
+
});
|
|
254
|
+
if (!quiet && result)
|
|
255
|
+
process.stdout.write(result);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
if (isUnsupportedOptionError(err) && i < attempts.length - 1) {
|
|
260
|
+
continue; // try next degradation level
|
|
261
|
+
}
|
|
262
|
+
// Final attempt failed: replay captured output, then throw
|
|
263
|
+
if (!quiet) {
|
|
264
|
+
const stdout = err?.stdout?.toString?.();
|
|
265
|
+
const stderr = err?.stderr?.toString?.();
|
|
266
|
+
if (stdout)
|
|
267
|
+
process.stdout.write(stdout);
|
|
268
|
+
if (stderr)
|
|
269
|
+
process.stderr.write(stderr);
|
|
270
|
+
}
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
129
274
|
}
|
|
130
275
|
export function pluginsUpdate(id, quiet) {
|
|
131
|
-
|
|
132
|
-
stdio:
|
|
276
|
+
const result = runOpenclaw(["plugins", "update", id], {
|
|
277
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
278
|
+
encoding: "utf-8",
|
|
133
279
|
});
|
|
280
|
+
if (!quiet && result)
|
|
281
|
+
process.stdout.write(result);
|
|
134
282
|
}
|
|
135
283
|
export function pluginsUninstall(id, yes) {
|
|
136
284
|
const args = ["plugins", "uninstall", id];
|
|
137
285
|
if (yes)
|
|
138
286
|
args.push("--force");
|
|
139
|
-
|
|
287
|
+
runOpenclaw(args, { stdio: "inherit" });
|
|
140
288
|
}
|
|
141
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Inspect a plugin. Returns structured outcome distinguishing:
|
|
291
|
+
* - ok + data: inspect succeeded
|
|
292
|
+
* - unsupported: old OpenClaw without `plugins inspect`
|
|
293
|
+
* - not_found: plugin genuinely not found
|
|
294
|
+
* - error: other failure (config corruption, plugin load crash, etc.)
|
|
295
|
+
*/
|
|
296
|
+
export function pluginsInspectDetailed(id) {
|
|
142
297
|
try {
|
|
143
|
-
const out =
|
|
298
|
+
const out = runOpenclaw(["plugins", "inspect", id, "--json"], {
|
|
144
299
|
encoding: "utf-8",
|
|
145
300
|
stdio: ["pipe", "pipe", "pipe"],
|
|
146
301
|
});
|
|
147
|
-
// stdout may contain plugin log noise before JSON — find the JSON object
|
|
148
302
|
const jsonStart = out.indexOf("{");
|
|
149
303
|
if (jsonStart < 0)
|
|
150
|
-
return null;
|
|
151
|
-
|
|
304
|
+
return { ok: false, data: null, failReason: "error" };
|
|
305
|
+
const data = JSON.parse(out.slice(jsonStart));
|
|
306
|
+
return { ok: true, data, failReason: null };
|
|
152
307
|
}
|
|
153
|
-
catch {
|
|
154
|
-
|
|
308
|
+
catch (err) {
|
|
309
|
+
const sources = [
|
|
310
|
+
err?.stderr?.toString?.(),
|
|
311
|
+
err?.stdout?.toString?.(),
|
|
312
|
+
err?.message,
|
|
313
|
+
String(err),
|
|
314
|
+
];
|
|
315
|
+
const text = sources.filter(Boolean).join(" ");
|
|
316
|
+
if (/unknown command|unrecognized command/i.test(text)) {
|
|
317
|
+
return { ok: false, data: null, failReason: "unsupported" };
|
|
318
|
+
}
|
|
319
|
+
if (/not found|not installed|no such plugin/i.test(text)) {
|
|
320
|
+
return { ok: false, data: null, failReason: "not_found" };
|
|
321
|
+
}
|
|
322
|
+
return { ok: false, data: null, failReason: "error" };
|
|
155
323
|
}
|
|
156
324
|
}
|
|
325
|
+
/** Backward-compatible wrapper: returns data or null. */
|
|
326
|
+
export function pluginsInspect(id) {
|
|
327
|
+
const outcome = pluginsInspectDetailed(id);
|
|
328
|
+
return outcome.ok ? outcome.data : null;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Resolve plugin install state. Uses `plugins inspect` when available,
|
|
332
|
+
* falls back to config entries + directory + package.json for old OpenClaw
|
|
333
|
+
* versions that don't support `plugins inspect`.
|
|
334
|
+
*
|
|
335
|
+
* Fallback installed = all 3 artifacts present (entries + installs + dir),
|
|
336
|
+
* matching detectScenario()'s healthy definition. Partial presence is NOT
|
|
337
|
+
* considered installed — that's a broken state for doctor --fix to handle.
|
|
338
|
+
*/
|
|
339
|
+
export function resolvePluginState(id) {
|
|
340
|
+
// Try inspect first
|
|
341
|
+
const outcome = pluginsInspectDetailed(id);
|
|
342
|
+
if (outcome.ok && outcome.data?.plugin) {
|
|
343
|
+
return {
|
|
344
|
+
installed: true,
|
|
345
|
+
enabled: outcome.data.plugin.enabled,
|
|
346
|
+
version: outcome.data.plugin.version,
|
|
347
|
+
installPath: outcome.data.install?.installPath ?? null,
|
|
348
|
+
source: "inspect",
|
|
349
|
+
inspectFailReason: null,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
// Fallback: check config + filesystem
|
|
353
|
+
const cfg = readConfigFromFile();
|
|
354
|
+
const extDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
355
|
+
const pluginDir = resolve(extDir, id);
|
|
356
|
+
const hasDir = existsSync(pluginDir);
|
|
357
|
+
const entries = cfg?.plugins?.entries?.[id];
|
|
358
|
+
const installs = cfg?.plugins?.installs?.[id];
|
|
359
|
+
const hasEntry = Boolean(entries);
|
|
360
|
+
const hasInstall = Boolean(installs);
|
|
361
|
+
// Healthy install requires all 3 artifacts, same as detectScenario().
|
|
362
|
+
// Partial presence (e.g. dir exists but no entries/installs) is broken, not installed.
|
|
363
|
+
const installed = hasDir && hasEntry && hasInstall;
|
|
364
|
+
if (!installed) {
|
|
365
|
+
return {
|
|
366
|
+
installed: false, enabled: null, version: null, installPath: null,
|
|
367
|
+
source: "fallback", inspectFailReason: outcome.failReason,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// Resolve version: installs record > package.json on disk
|
|
371
|
+
let version = installs?.version ?? null;
|
|
372
|
+
if (!version && hasDir) {
|
|
373
|
+
try {
|
|
374
|
+
const pkg = JSON.parse(readFileSync(resolve(pluginDir, "package.json"), "utf-8"));
|
|
375
|
+
version = pkg.version ?? null;
|
|
376
|
+
}
|
|
377
|
+
catch { /* no package.json */ }
|
|
378
|
+
}
|
|
379
|
+
const enabled = entries?.enabled ?? null;
|
|
380
|
+
const installPath = installs?.installPath ?? (hasDir ? `~/.openclaw/extensions/${id}` : null);
|
|
381
|
+
return { installed, enabled, version, installPath, source: "fallback", inspectFailReason: outcome.failReason };
|
|
382
|
+
}
|
|
157
383
|
// ---------------------------------------------------------------------------
|
|
158
384
|
// Gateway helpers
|
|
159
385
|
// ---------------------------------------------------------------------------
|
|
160
386
|
export function gatewayStatus() {
|
|
161
387
|
try {
|
|
162
|
-
const out =
|
|
388
|
+
const out = runOpenclaw(["gateway", "status", "--json"], {
|
|
163
389
|
encoding: "utf-8",
|
|
164
390
|
stdio: ["pipe", "pipe", "pipe"],
|
|
165
391
|
});
|
|
166
392
|
const jsonStart = out.indexOf("{");
|
|
167
393
|
if (jsonStart < 0)
|
|
168
394
|
return { running: false };
|
|
169
|
-
|
|
170
|
-
|
|
395
|
+
// Find matching } to avoid trailing log noise
|
|
396
|
+
let depth = 0;
|
|
397
|
+
let end = -1;
|
|
398
|
+
for (let i = jsonStart; i < out.length; i++) {
|
|
399
|
+
if (out[i] === "{")
|
|
400
|
+
depth++;
|
|
401
|
+
else if (out[i] === "}") {
|
|
402
|
+
depth--;
|
|
403
|
+
if (depth === 0) {
|
|
404
|
+
end = i;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (end < 0)
|
|
410
|
+
return { running: false };
|
|
411
|
+
const data = JSON.parse(out.slice(jsonStart, end + 1));
|
|
171
412
|
const runtimeRunning = data.service?.runtime?.status === "running";
|
|
172
413
|
const healthy = data.health?.healthy === true;
|
|
173
|
-
|
|
414
|
+
// Fallback: port is busy with an openclaw-gateway process = gateway is running
|
|
415
|
+
const portBusy = data.port?.status === "busy";
|
|
416
|
+
return { running: runtimeRunning || healthy || portBusy };
|
|
174
417
|
}
|
|
175
418
|
catch {
|
|
176
419
|
return { running: false };
|
|
@@ -178,7 +421,7 @@ export function gatewayStatus() {
|
|
|
178
421
|
}
|
|
179
422
|
export function gatewayRestart(quiet) {
|
|
180
423
|
try {
|
|
181
|
-
|
|
424
|
+
runOpenclaw(["gateway", "restart"], {
|
|
182
425
|
stdio: quiet ? ["pipe", "pipe", "pipe"] : "inherit",
|
|
183
426
|
});
|
|
184
427
|
return true;
|
|
@@ -192,7 +435,7 @@ export function gatewayRestart(quiet) {
|
|
|
192
435
|
// ---------------------------------------------------------------------------
|
|
193
436
|
export function getOpenClawVersion() {
|
|
194
437
|
try {
|
|
195
|
-
const out =
|
|
438
|
+
const out = runOpenclaw(["--version"], {
|
|
196
439
|
encoding: "utf-8",
|
|
197
440
|
stdio: ["pipe", "pipe", "pipe"],
|
|
198
441
|
});
|
|
@@ -218,7 +461,7 @@ export function getOpenClawVersion() {
|
|
|
218
461
|
*/
|
|
219
462
|
export function saveChannelConfigFromFile() {
|
|
220
463
|
try {
|
|
221
|
-
const configPath =
|
|
464
|
+
const configPath = getConfigFilePathSafe();
|
|
222
465
|
const raw = readFileSync(configPath, "utf-8");
|
|
223
466
|
const cfg = JSON.parse(raw);
|
|
224
467
|
return cfg?.channels?.dmwork ?? null;
|
|
@@ -233,7 +476,7 @@ export function saveChannelConfigFromFile() {
|
|
|
233
476
|
* Creates a .bak backup before writing.
|
|
234
477
|
*/
|
|
235
478
|
export function restoreChannelConfigToFile(dmworkConfig) {
|
|
236
|
-
const configPath =
|
|
479
|
+
const configPath = getConfigFilePathSafe();
|
|
237
480
|
// Backup
|
|
238
481
|
copyFileSync(configPath, configPath + ".bak");
|
|
239
482
|
// Read, merge, write
|
|
@@ -242,7 +485,7 @@ export function restoreChannelConfigToFile(dmworkConfig) {
|
|
|
242
485
|
if (!cfg.channels)
|
|
243
486
|
cfg.channels = {};
|
|
244
487
|
cfg.channels.dmwork = dmworkConfig;
|
|
245
|
-
|
|
488
|
+
writeConfigAtomic(cfg);
|
|
246
489
|
}
|
|
247
490
|
/**
|
|
248
491
|
* Remove channels.dmwork directly from the JSON file.
|
|
@@ -254,7 +497,7 @@ export function restoreChannelConfigToFile(dmworkConfig) {
|
|
|
254
497
|
* Falls back to the standard default when CLI is unavailable
|
|
255
498
|
* (e.g. during uninstall when config validation fails).
|
|
256
499
|
*/
|
|
257
|
-
function getConfigFilePathSafe() {
|
|
500
|
+
export function getConfigFilePathSafe() {
|
|
258
501
|
try {
|
|
259
502
|
return expandHome(getConfigFilePath());
|
|
260
503
|
}
|
|
@@ -266,11 +509,10 @@ export function removeChannelConfigFromFile() {
|
|
|
266
509
|
try {
|
|
267
510
|
const configPath = getConfigFilePathSafe();
|
|
268
511
|
copyFileSync(configPath, configPath + ".bak");
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
if (cfg.channels?.dmwork) {
|
|
512
|
+
const cfg = readConfigFromFile();
|
|
513
|
+
if (cfg?.channels?.dmwork) {
|
|
272
514
|
delete cfg.channels.dmwork;
|
|
273
|
-
|
|
515
|
+
writeConfigAtomic(cfg);
|
|
274
516
|
}
|
|
275
517
|
}
|
|
276
518
|
catch {
|
|
@@ -311,7 +553,7 @@ export function removeOrphanedBindingsFromFile(channel, validAccountIds) {
|
|
|
311
553
|
// Keep only if accountId is in valid list (or no accountId specified)
|
|
312
554
|
return !b.match.accountId || validAccountIds.includes(b.match.accountId);
|
|
313
555
|
});
|
|
314
|
-
|
|
556
|
+
writeConfigAtomic(cfg);
|
|
315
557
|
}
|
|
316
558
|
catch {
|
|
317
559
|
// best effort
|
|
@@ -338,7 +580,7 @@ export function cleanupLegacyPlugin() {
|
|
|
338
580
|
if (existsSync(legacyDir)) {
|
|
339
581
|
// Try to uninstall via openclaw CLI first (removes entries/installs/allow)
|
|
340
582
|
try {
|
|
341
|
-
|
|
583
|
+
runOpenclaw(["plugins", "uninstall", LEGACY_PLUGIN_ID, "--force", "--keep-files"], {
|
|
342
584
|
stdio: ["pipe", "pipe", "pipe"],
|
|
343
585
|
});
|
|
344
586
|
actions.push(`Unregistered legacy plugin "${LEGACY_PLUGIN_ID}"`);
|
|
@@ -348,7 +590,6 @@ export function cleanupLegacyPlugin() {
|
|
|
348
590
|
}
|
|
349
591
|
// Remove legacy directory
|
|
350
592
|
try {
|
|
351
|
-
const { rmSync } = require("node:fs");
|
|
352
593
|
rmSync(legacyDir, { recursive: true, force: true });
|
|
353
594
|
actions.push(`Removed legacy directory: ${legacyDir}`);
|
|
354
595
|
}
|
|
@@ -370,7 +611,7 @@ export function cleanupLegacyPlugin() {
|
|
|
370
611
|
if (Array.isArray(cfg.plugins?.allow)) {
|
|
371
612
|
cfg.plugins.allow = cfg.plugins.allow.filter((id) => id !== LEGACY_PLUGIN_ID);
|
|
372
613
|
}
|
|
373
|
-
|
|
614
|
+
writeConfigAtomic(cfg);
|
|
374
615
|
actions.push(`Cleaned legacy entries from openclaw.json`);
|
|
375
616
|
}
|
|
376
617
|
}
|
|
@@ -379,4 +620,318 @@ export function cleanupLegacyPlugin() {
|
|
|
379
620
|
}
|
|
380
621
|
return actions;
|
|
381
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Clean up stale openclaw-channel-dmwork directory that is not registered
|
|
625
|
+
* in plugins.installs (orphaned from a failed previous install).
|
|
626
|
+
*
|
|
627
|
+
* Only removes the directory if ALL of these are true:
|
|
628
|
+
* 1. The directory exists
|
|
629
|
+
* 2. pluginsInspect returns null (openclaw doesn't recognize it)
|
|
630
|
+
* 3. plugins.installs has no record for openclaw-channel-dmwork
|
|
631
|
+
*/
|
|
632
|
+
export function cleanupStalePluginDir() {
|
|
633
|
+
const actions = [];
|
|
634
|
+
const extensionsDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
635
|
+
const pluginDir = resolve(extensionsDir, "openclaw-channel-dmwork");
|
|
636
|
+
if (!existsSync(pluginDir))
|
|
637
|
+
return actions;
|
|
638
|
+
// Check if openclaw recognizes it
|
|
639
|
+
const inspect = pluginsInspect("openclaw-channel-dmwork");
|
|
640
|
+
if (inspect?.plugin)
|
|
641
|
+
return actions; // recognized, don't touch
|
|
642
|
+
// Check if it's in installs registry
|
|
643
|
+
try {
|
|
644
|
+
const cfg = readConfigFromFile();
|
|
645
|
+
if (cfg?.plugins?.installs?.["openclaw-channel-dmwork"]) {
|
|
646
|
+
return actions; // has install record, might just be inspect anomaly
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
catch { /* proceed with cleanup */ }
|
|
650
|
+
// All three conditions met: exists + not recognized + not in registry → stale
|
|
651
|
+
try {
|
|
652
|
+
rmSync(pluginDir, { recursive: true, force: true });
|
|
653
|
+
actions.push(`Removed stale plugin directory: ${pluginDir}`);
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
actions.push(`Warning: could not remove stale directory: ${pluginDir}`);
|
|
657
|
+
}
|
|
658
|
+
return actions;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Clean up stale openclaw-install-stage directories that belong to DMWork.
|
|
662
|
+
* Only removes directories that:
|
|
663
|
+
* 1. Match .openclaw-install-stage-* pattern
|
|
664
|
+
* 2. Are older than 10 minutes (not a current installation)
|
|
665
|
+
* 3. Contain a package.json with name "openclaw-channel-dmwork"
|
|
666
|
+
*/
|
|
667
|
+
export function cleanupStaleStageDirectories() {
|
|
668
|
+
const actions = [];
|
|
669
|
+
const extensionsDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
670
|
+
try {
|
|
671
|
+
const entries = readdirSync(extensionsDir);
|
|
672
|
+
const now = Date.now();
|
|
673
|
+
const TEN_MINUTES = 10 * 60 * 1000;
|
|
674
|
+
for (const entry of entries) {
|
|
675
|
+
if (!entry.startsWith(".openclaw-install-stage-"))
|
|
676
|
+
continue;
|
|
677
|
+
const stagePath = resolve(extensionsDir, entry);
|
|
678
|
+
try {
|
|
679
|
+
const stat = statSync(stagePath);
|
|
680
|
+
if (!stat.isDirectory())
|
|
681
|
+
continue;
|
|
682
|
+
if (now - stat.mtimeMs < TEN_MINUTES)
|
|
683
|
+
continue; // too recent, skip
|
|
684
|
+
// Check if it's DMWork's stage directory
|
|
685
|
+
const pkgPath = resolve(stagePath, "package", "package.json");
|
|
686
|
+
const altPkgPath = resolve(stagePath, "package.json");
|
|
687
|
+
let isDmwork = false;
|
|
688
|
+
for (const p of [pkgPath, altPkgPath]) {
|
|
689
|
+
try {
|
|
690
|
+
const pkg = JSON.parse(readFileSync(p, "utf-8"));
|
|
691
|
+
if (pkg.name === "openclaw-channel-dmwork") {
|
|
692
|
+
isDmwork = true;
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
catch { /* try next */ }
|
|
697
|
+
}
|
|
698
|
+
if (!isDmwork)
|
|
699
|
+
continue; // not ours, don't touch
|
|
700
|
+
rmSync(stagePath, { recursive: true, force: true });
|
|
701
|
+
actions.push(`Removed stale stage directory: ${entry}`);
|
|
702
|
+
}
|
|
703
|
+
catch { /* skip this entry */ }
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
catch { /* best effort */ }
|
|
707
|
+
return actions;
|
|
708
|
+
}
|
|
709
|
+
// ---------------------------------------------------------------------------
|
|
710
|
+
// Atomic config write
|
|
711
|
+
// ---------------------------------------------------------------------------
|
|
712
|
+
/**
|
|
713
|
+
* Write openclaw.json atomically: write to .tmp then rename.
|
|
714
|
+
* Prevents gateway watcher from reading half-written/truncated JSON.
|
|
715
|
+
*/
|
|
716
|
+
export function writeConfigAtomic(cfg) {
|
|
717
|
+
const configPath = getConfigFilePathSafe();
|
|
718
|
+
const tmpPath = configPath + ".tmp";
|
|
719
|
+
writeFileSync(tmpPath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
720
|
+
renameSync(tmpPath, configPath);
|
|
721
|
+
}
|
|
722
|
+
export function detectScenario() {
|
|
723
|
+
const cfg = readConfigFromFile();
|
|
724
|
+
const extDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
725
|
+
const hasLegacyDir = existsSync(resolve(extDir, "dmwork"));
|
|
726
|
+
const hasLegacyEntries = Boolean(cfg?.plugins?.entries?.["dmwork"]);
|
|
727
|
+
const hasLegacyInstalls = Boolean(cfg?.plugins?.installs?.["dmwork"]);
|
|
728
|
+
const hasLegacy = hasLegacyDir || hasLegacyEntries || hasLegacyInstalls;
|
|
729
|
+
const hasNewDir = existsSync(resolve(extDir, "openclaw-channel-dmwork"));
|
|
730
|
+
const hasNewEntries = Boolean(cfg?.plugins?.entries?.["openclaw-channel-dmwork"]);
|
|
731
|
+
const hasNewInstalls = Boolean(cfg?.plugins?.installs?.["openclaw-channel-dmwork"]);
|
|
732
|
+
const inspectOk = Boolean(pluginsInspect("openclaw-channel-dmwork")?.plugin);
|
|
733
|
+
const isHealthy = inspectOk || (hasNewDir && hasNewEntries && hasNewInstalls);
|
|
734
|
+
const hasNewPartial = (hasNewDir || hasNewEntries || hasNewInstalls) && !isHealthy;
|
|
735
|
+
const hasDmworkChannel = Boolean(cfg?.channels?.dmwork);
|
|
736
|
+
if (hasLegacy)
|
|
737
|
+
return "legacy";
|
|
738
|
+
if (isHealthy)
|
|
739
|
+
return "update";
|
|
740
|
+
if (hasNewPartial)
|
|
741
|
+
return "broken";
|
|
742
|
+
if (hasDmworkChannel)
|
|
743
|
+
return "deadlock";
|
|
744
|
+
return "fresh";
|
|
745
|
+
}
|
|
746
|
+
export function isHealthyInstall() {
|
|
747
|
+
const cfg = readConfigFromFile();
|
|
748
|
+
const extDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
749
|
+
const hasNewDir = existsSync(resolve(extDir, "openclaw-channel-dmwork"));
|
|
750
|
+
const hasNewEntries = Boolean(cfg?.plugins?.entries?.["openclaw-channel-dmwork"]);
|
|
751
|
+
const hasNewInstalls = Boolean(cfg?.plugins?.installs?.["openclaw-channel-dmwork"]);
|
|
752
|
+
const inspectOk = Boolean(pluginsInspect("openclaw-channel-dmwork")?.plugin);
|
|
753
|
+
return inspectOk || (hasNewDir && hasNewEntries && hasNewInstalls);
|
|
754
|
+
}
|
|
755
|
+
export function ensurePluginsAllow() {
|
|
756
|
+
try {
|
|
757
|
+
const cfg = readConfigFromFile();
|
|
758
|
+
if (!cfg?.plugins?.allow || !Array.isArray(cfg.plugins.allow))
|
|
759
|
+
return;
|
|
760
|
+
if (cfg.plugins.allow.includes("openclaw-channel-dmwork"))
|
|
761
|
+
return;
|
|
762
|
+
cfg.plugins.allow.push("openclaw-channel-dmwork");
|
|
763
|
+
writeConfigAtomic(cfg);
|
|
764
|
+
}
|
|
765
|
+
catch { /* best effort */ }
|
|
766
|
+
}
|
|
767
|
+
// ---------------------------------------------------------------------------
|
|
768
|
+
// pluginsUpdateCompat
|
|
769
|
+
// ---------------------------------------------------------------------------
|
|
770
|
+
export function pluginsUpdateCompat(id, tag, quiet) {
|
|
771
|
+
try {
|
|
772
|
+
const result = runOpenclaw(["plugins", "update", id], {
|
|
773
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
774
|
+
encoding: "utf-8",
|
|
775
|
+
});
|
|
776
|
+
if (!quiet && result)
|
|
777
|
+
process.stdout.write(result);
|
|
778
|
+
}
|
|
779
|
+
catch (err) {
|
|
780
|
+
// Only fallback to install when update is unsupported or plugin not installed.
|
|
781
|
+
// Other errors (network, permissions, etc.) should propagate.
|
|
782
|
+
if (isUnsupportedOptionError(err) || isPluginNotInstalledError(err)) {
|
|
783
|
+
pluginsInstall(`${id}@${tag}`, quiet, true);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (!quiet) {
|
|
787
|
+
const stdout = err?.stdout?.toString?.();
|
|
788
|
+
const stderr = err?.stderr?.toString?.();
|
|
789
|
+
if (stdout)
|
|
790
|
+
process.stdout.write(stdout);
|
|
791
|
+
if (stderr)
|
|
792
|
+
process.stderr.write(stderr);
|
|
793
|
+
}
|
|
794
|
+
throw err;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// ---------------------------------------------------------------------------
|
|
798
|
+
// Legacy migration helpers
|
|
799
|
+
// ---------------------------------------------------------------------------
|
|
800
|
+
export function renameLegacyDir() {
|
|
801
|
+
const extDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
802
|
+
const legacyDir = resolve(extDir, "dmwork");
|
|
803
|
+
const backupDir = resolve(extDir, ".dmwork-backup");
|
|
804
|
+
if (!existsSync(legacyDir))
|
|
805
|
+
return false;
|
|
806
|
+
try {
|
|
807
|
+
if (existsSync(backupDir))
|
|
808
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
809
|
+
renameSync(legacyDir, backupDir);
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
export function restoreLegacyDir() {
|
|
817
|
+
const extDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
818
|
+
const legacyDir = resolve(extDir, "dmwork");
|
|
819
|
+
const backupDir = resolve(extDir, ".dmwork-backup");
|
|
820
|
+
if (!existsSync(backupDir))
|
|
821
|
+
return;
|
|
822
|
+
try {
|
|
823
|
+
if (existsSync(legacyDir))
|
|
824
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
825
|
+
renameSync(backupDir, legacyDir);
|
|
826
|
+
}
|
|
827
|
+
catch { /* best effort */ }
|
|
828
|
+
}
|
|
829
|
+
export function deleteLegacyBackup() {
|
|
830
|
+
const extDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
831
|
+
const backupDir = resolve(extDir, ".dmwork-backup");
|
|
832
|
+
if (existsSync(backupDir)) {
|
|
833
|
+
try {
|
|
834
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
835
|
+
}
|
|
836
|
+
catch { /* best effort */ }
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
export function removeLegacyFromConfig() {
|
|
840
|
+
try {
|
|
841
|
+
const cfg = readConfigFromFile();
|
|
842
|
+
if (!cfg)
|
|
843
|
+
return;
|
|
844
|
+
if (cfg.plugins?.entries?.["dmwork"])
|
|
845
|
+
delete cfg.plugins.entries["dmwork"];
|
|
846
|
+
if (cfg.plugins?.installs?.["dmwork"])
|
|
847
|
+
delete cfg.plugins.installs["dmwork"];
|
|
848
|
+
if (Array.isArray(cfg.plugins?.allow)) {
|
|
849
|
+
cfg.plugins.allow = cfg.plugins.allow.filter((id) => id !== "dmwork");
|
|
850
|
+
}
|
|
851
|
+
if (cfg.channels?.dmwork)
|
|
852
|
+
delete cfg.channels.dmwork;
|
|
853
|
+
writeConfigAtomic(cfg);
|
|
854
|
+
}
|
|
855
|
+
catch { /* best effort */ }
|
|
856
|
+
}
|
|
857
|
+
export function saveChannelConfigToDisk() {
|
|
858
|
+
try {
|
|
859
|
+
const backupPath = getConfigFilePathSafe().replace(/openclaw\.json$/, "channels-dmwork-backup.json");
|
|
860
|
+
const cfg = readConfigFromFile();
|
|
861
|
+
const dmwork = cfg?.channels?.dmwork;
|
|
862
|
+
if (dmwork) {
|
|
863
|
+
writeFileSync(backupPath, JSON.stringify(dmwork, null, 2), "utf-8");
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
// No channels.dmwork — remove stale backup to prevent wrong restore
|
|
867
|
+
if (existsSync(backupPath))
|
|
868
|
+
rmSync(backupPath, { force: true });
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
catch { /* best effort */ }
|
|
872
|
+
}
|
|
873
|
+
export function restoreChannelConfigFromDisk() {
|
|
874
|
+
try {
|
|
875
|
+
const backupPath = getConfigFilePathSafe().replace(/openclaw\.json$/, "channels-dmwork-backup.json");
|
|
876
|
+
if (!existsSync(backupPath))
|
|
877
|
+
return;
|
|
878
|
+
let dmwork = JSON.parse(readFileSync(backupPath, "utf-8"));
|
|
879
|
+
// Migrate flat config → accounts.default
|
|
880
|
+
if (dmwork.botToken && !dmwork.accounts) {
|
|
881
|
+
dmwork = {
|
|
882
|
+
...dmwork,
|
|
883
|
+
accounts: { default: { botToken: dmwork.botToken, apiUrl: dmwork.apiUrl } },
|
|
884
|
+
};
|
|
885
|
+
delete dmwork.botToken;
|
|
886
|
+
}
|
|
887
|
+
const cfg = readConfigFromFile();
|
|
888
|
+
if (!cfg)
|
|
889
|
+
return;
|
|
890
|
+
if (!cfg.channels)
|
|
891
|
+
cfg.channels = {};
|
|
892
|
+
cfg.channels.dmwork = dmwork;
|
|
893
|
+
writeConfigAtomic(cfg);
|
|
894
|
+
rmSync(backupPath, { force: true });
|
|
895
|
+
}
|
|
896
|
+
catch { /* best effort */ }
|
|
897
|
+
}
|
|
898
|
+
export function cleanupBrokenInstall() {
|
|
899
|
+
const actions = [];
|
|
900
|
+
const cfg = readConfigFromFile();
|
|
901
|
+
const extDir = getConfigFilePathSafe().replace(/openclaw\.json$/, "extensions");
|
|
902
|
+
const pluginDir = resolve(extDir, "openclaw-channel-dmwork");
|
|
903
|
+
const hasDir = existsSync(pluginDir);
|
|
904
|
+
const hasEntries = Boolean(cfg?.plugins?.entries?.["openclaw-channel-dmwork"]);
|
|
905
|
+
const hasInstalls = Boolean(cfg?.plugins?.installs?.["openclaw-channel-dmwork"]);
|
|
906
|
+
// Use same healthy definition as detectScenario(): inspect OK OR all 3 artifacts present
|
|
907
|
+
const inspectOk = Boolean(pluginsInspect("openclaw-channel-dmwork")?.plugin);
|
|
908
|
+
const isHealthy = inspectOk || (hasDir && hasEntries && hasInstalls);
|
|
909
|
+
if (isHealthy)
|
|
910
|
+
return actions; // Actually healthy, nothing to clean
|
|
911
|
+
// Remove directory if it exists (orphan or partial)
|
|
912
|
+
if (hasDir) {
|
|
913
|
+
try {
|
|
914
|
+
rmSync(pluginDir, { recursive: true, force: true });
|
|
915
|
+
actions.push("Removed broken/orphan plugin directory");
|
|
916
|
+
}
|
|
917
|
+
catch { /* best effort */ }
|
|
918
|
+
}
|
|
919
|
+
// Remove stale config entries
|
|
920
|
+
if (cfg && (hasEntries || hasInstalls)) {
|
|
921
|
+
let changed = false;
|
|
922
|
+
if (hasEntries) {
|
|
923
|
+
delete cfg.plugins.entries["openclaw-channel-dmwork"];
|
|
924
|
+
changed = true;
|
|
925
|
+
}
|
|
926
|
+
if (hasInstalls) {
|
|
927
|
+
delete cfg.plugins.installs["openclaw-channel-dmwork"];
|
|
928
|
+
changed = true;
|
|
929
|
+
}
|
|
930
|
+
if (changed) {
|
|
931
|
+
writeConfigAtomic(cfg);
|
|
932
|
+
actions.push("Cleaned stale config entries");
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return actions;
|
|
936
|
+
}
|
|
382
937
|
//# sourceMappingURL=openclaw-cli.js.map
|