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