nextclaw 0.4.8 → 0.4.10
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/index.js +527 -25
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -5
- package/ui-dist/assets/{index-JpepB1WI.js → index-Bt59xHFj.js} +13 -13
- package/ui-dist/index.html +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { APP_NAME as APP_NAME2, APP_TAGLINE } from "nextclaw
|
|
5
|
+
import { APP_NAME as APP_NAME2, APP_TAGLINE } from "@nextclaw/core";
|
|
6
6
|
|
|
7
7
|
// src/cli/runtime.ts
|
|
8
8
|
import {
|
|
@@ -30,8 +30,21 @@ import {
|
|
|
30
30
|
APP_NAME,
|
|
31
31
|
DEFAULT_WORKSPACE_DIR,
|
|
32
32
|
DEFAULT_WORKSPACE_PATH
|
|
33
|
-
} from "nextclaw
|
|
34
|
-
import {
|
|
33
|
+
} from "@nextclaw/core";
|
|
34
|
+
import {
|
|
35
|
+
loadOpenClawPlugins,
|
|
36
|
+
buildPluginStatusReport,
|
|
37
|
+
enablePluginInConfig,
|
|
38
|
+
disablePluginInConfig,
|
|
39
|
+
addPluginLoadPath,
|
|
40
|
+
recordPluginInstall,
|
|
41
|
+
installPluginFromPath,
|
|
42
|
+
installPluginFromNpmSpec,
|
|
43
|
+
uninstallPlugin,
|
|
44
|
+
resolveUninstallDirectoryTarget,
|
|
45
|
+
loadPluginUiMetadata
|
|
46
|
+
} from "@nextclaw/openclaw-compat";
|
|
47
|
+
import { startUiServer } from "@nextclaw/server";
|
|
35
48
|
import {
|
|
36
49
|
closeSync,
|
|
37
50
|
cpSync,
|
|
@@ -57,7 +70,7 @@ import {
|
|
|
57
70
|
buildConfigSchema,
|
|
58
71
|
ConfigSchema,
|
|
59
72
|
redactConfigObject
|
|
60
|
-
} from "nextclaw
|
|
73
|
+
} from "@nextclaw/core";
|
|
61
74
|
|
|
62
75
|
// src/cli/utils.ts
|
|
63
76
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
@@ -65,7 +78,7 @@ import { join, resolve } from "path";
|
|
|
65
78
|
import { spawn } from "child_process";
|
|
66
79
|
import { createServer } from "net";
|
|
67
80
|
import { fileURLToPath } from "url";
|
|
68
|
-
import { getDataDir, getPackageVersion } from "nextclaw
|
|
81
|
+
import { getDataDir, getPackageVersion } from "@nextclaw/core";
|
|
69
82
|
function resolveUiConfig(config, overrides) {
|
|
70
83
|
const base = config.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
|
|
71
84
|
return { ...base, ...overrides ?? {} };
|
|
@@ -331,7 +344,7 @@ function runSelfUpdate(options = {}) {
|
|
|
331
344
|
|
|
332
345
|
// src/cli/gateway/controller.ts
|
|
333
346
|
var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
|
|
334
|
-
var readConfigSnapshot = (getConfigPath2) => {
|
|
347
|
+
var readConfigSnapshot = (getConfigPath2, plugins2) => {
|
|
335
348
|
const path = getConfigPath2();
|
|
336
349
|
let raw = "";
|
|
337
350
|
let parsed = {};
|
|
@@ -355,12 +368,12 @@ var readConfigSnapshot = (getConfigPath2) => {
|
|
|
355
368
|
raw = JSON.stringify(config, null, 2);
|
|
356
369
|
}
|
|
357
370
|
const hash = hashRaw(raw);
|
|
358
|
-
const schema = buildConfigSchema({ version: getPackageVersion() });
|
|
371
|
+
const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
|
|
359
372
|
const redacted = redactConfigObject(config, schema.uiHints);
|
|
360
373
|
return { raw: valid ? JSON.stringify(redacted, null, 2) : null, hash: valid ? hash : null, config, redacted, valid };
|
|
361
374
|
};
|
|
362
|
-
var redactValue = (value) => {
|
|
363
|
-
const schema = buildConfigSchema({ version: getPackageVersion() });
|
|
375
|
+
var redactValue = (value, plugins2) => {
|
|
376
|
+
const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
|
|
364
377
|
return redactConfigObject(value, schema.uiHints);
|
|
365
378
|
};
|
|
366
379
|
var mergeDeep = (base, patch) => {
|
|
@@ -405,7 +418,8 @@ var GatewayControllerImpl = class {
|
|
|
405
418
|
return "Restart scheduled";
|
|
406
419
|
}
|
|
407
420
|
async getConfig() {
|
|
408
|
-
const
|
|
421
|
+
const plugins2 = this.deps.getPluginUiMetadata?.() ?? [];
|
|
422
|
+
const snapshot = readConfigSnapshot(this.deps.getConfigPath, plugins2);
|
|
409
423
|
return {
|
|
410
424
|
raw: snapshot.raw,
|
|
411
425
|
hash: snapshot.hash,
|
|
@@ -417,10 +431,11 @@ var GatewayControllerImpl = class {
|
|
|
417
431
|
};
|
|
418
432
|
}
|
|
419
433
|
async getConfigSchema() {
|
|
420
|
-
return buildConfigSchema({ version: getPackageVersion() });
|
|
434
|
+
return buildConfigSchema({ version: getPackageVersion(), plugins: this.deps.getPluginUiMetadata?.() ?? [] });
|
|
421
435
|
}
|
|
422
436
|
async applyConfig(params) {
|
|
423
|
-
const
|
|
437
|
+
const plugins2 = this.deps.getPluginUiMetadata?.() ?? [];
|
|
438
|
+
const snapshot = readConfigSnapshot(this.deps.getConfigPath, plugins2);
|
|
424
439
|
if (!params.baseHash) {
|
|
425
440
|
return { ok: false, error: "config base hash required; re-run config.get and retry" };
|
|
426
441
|
}
|
|
@@ -449,12 +464,13 @@ var GatewayControllerImpl = class {
|
|
|
449
464
|
ok: true,
|
|
450
465
|
note: params.note ?? null,
|
|
451
466
|
path: this.deps.getConfigPath(),
|
|
452
|
-
config: redactValue(validated),
|
|
467
|
+
config: redactValue(validated, plugins2),
|
|
453
468
|
restart: { scheduled: true, delayMs }
|
|
454
469
|
};
|
|
455
470
|
}
|
|
456
471
|
async patchConfig(params) {
|
|
457
|
-
const
|
|
472
|
+
const plugins2 = this.deps.getPluginUiMetadata?.() ?? [];
|
|
473
|
+
const snapshot = readConfigSnapshot(this.deps.getConfigPath, plugins2);
|
|
458
474
|
if (!params.baseHash) {
|
|
459
475
|
return { ok: false, error: "config base hash required; re-run config.get and retry" };
|
|
460
476
|
}
|
|
@@ -484,7 +500,7 @@ var GatewayControllerImpl = class {
|
|
|
484
500
|
ok: true,
|
|
485
501
|
note: params.note ?? null,
|
|
486
502
|
path: this.deps.getConfigPath(),
|
|
487
|
-
config: redactValue(validated),
|
|
503
|
+
config: redactValue(validated, plugins2),
|
|
488
504
|
restart: { scheduled: true, delayMs }
|
|
489
505
|
};
|
|
490
506
|
}
|
|
@@ -653,7 +669,12 @@ var ConfigReloader = class {
|
|
|
653
669
|
}
|
|
654
670
|
this.reloadTask = (async () => {
|
|
655
671
|
await this.channels.stopAll();
|
|
656
|
-
this.channels = new ChannelManager(
|
|
672
|
+
this.channels = new ChannelManager(
|
|
673
|
+
nextConfig,
|
|
674
|
+
this.options.bus,
|
|
675
|
+
this.options.sessionManager,
|
|
676
|
+
this.options.getExtensionChannels?.() ?? []
|
|
677
|
+
);
|
|
657
678
|
await this.channels.startAll();
|
|
658
679
|
})();
|
|
659
680
|
try {
|
|
@@ -851,17 +872,23 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
851
872
|
}
|
|
852
873
|
async agent(opts) {
|
|
853
874
|
const config = loadConfig();
|
|
875
|
+
const workspace = getWorkspacePath(config.agents.defaults.workspace);
|
|
876
|
+
const pluginRegistry = this.loadPluginRegistry(config, workspace);
|
|
877
|
+
const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
|
|
878
|
+
this.logPluginDiagnostics(pluginRegistry);
|
|
854
879
|
const bus = new MessageBus();
|
|
855
880
|
const provider = this.makeProvider(config);
|
|
856
881
|
const providerManager = new ProviderManager(provider);
|
|
857
882
|
const agentLoop = new AgentLoop({
|
|
858
883
|
bus,
|
|
859
884
|
providerManager,
|
|
860
|
-
workspace
|
|
885
|
+
workspace,
|
|
861
886
|
braveApiKey: config.tools.web.search.apiKey || void 0,
|
|
862
887
|
execConfig: config.tools.exec,
|
|
863
888
|
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
864
|
-
contextConfig: config.agents.context
|
|
889
|
+
contextConfig: config.agents.context,
|
|
890
|
+
config,
|
|
891
|
+
extensionRegistry
|
|
865
892
|
});
|
|
866
893
|
if (opts.message) {
|
|
867
894
|
const response = await agentLoop.processDirect({
|
|
@@ -939,6 +966,356 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
939
966
|
console.log(`Tip: restart ${APP_NAME} to apply the update.`);
|
|
940
967
|
}
|
|
941
968
|
}
|
|
969
|
+
pluginsList(opts = {}) {
|
|
970
|
+
const config = loadConfig();
|
|
971
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
972
|
+
const report = buildPluginStatusReport({
|
|
973
|
+
config,
|
|
974
|
+
workspaceDir,
|
|
975
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
976
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
977
|
+
});
|
|
978
|
+
const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
|
|
979
|
+
if (opts.json) {
|
|
980
|
+
console.log(
|
|
981
|
+
JSON.stringify(
|
|
982
|
+
{
|
|
983
|
+
workspaceDir,
|
|
984
|
+
plugins: list,
|
|
985
|
+
diagnostics: report.diagnostics
|
|
986
|
+
},
|
|
987
|
+
null,
|
|
988
|
+
2
|
|
989
|
+
)
|
|
990
|
+
);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (list.length === 0) {
|
|
994
|
+
console.log("No plugins discovered.");
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
for (const plugin of list) {
|
|
998
|
+
const status = plugin.status === "loaded" ? "loaded" : plugin.status === "disabled" ? "disabled" : "error";
|
|
999
|
+
const title = plugin.name && plugin.name !== plugin.id ? `${plugin.name} (${plugin.id})` : plugin.id;
|
|
1000
|
+
if (!opts.verbose) {
|
|
1001
|
+
const desc = plugin.description ? plugin.description.length > 80 ? `${plugin.description.slice(0, 77)}...` : plugin.description : "(no description)";
|
|
1002
|
+
console.log(`${title} ${status} - ${desc}`);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
console.log(`${title} ${status}`);
|
|
1006
|
+
console.log(` source: ${plugin.source}`);
|
|
1007
|
+
console.log(` origin: ${plugin.origin}`);
|
|
1008
|
+
if (plugin.version) {
|
|
1009
|
+
console.log(` version: ${plugin.version}`);
|
|
1010
|
+
}
|
|
1011
|
+
if (plugin.toolNames.length > 0) {
|
|
1012
|
+
console.log(` tools: ${plugin.toolNames.join(", ")}`);
|
|
1013
|
+
}
|
|
1014
|
+
if (plugin.channelIds.length > 0) {
|
|
1015
|
+
console.log(` channels: ${plugin.channelIds.join(", ")}`);
|
|
1016
|
+
}
|
|
1017
|
+
if (plugin.providerIds.length > 0) {
|
|
1018
|
+
console.log(` providers: ${plugin.providerIds.join(", ")}`);
|
|
1019
|
+
}
|
|
1020
|
+
if (plugin.error) {
|
|
1021
|
+
console.log(` error: ${plugin.error}`);
|
|
1022
|
+
}
|
|
1023
|
+
console.log("");
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
pluginsInfo(id, opts = {}) {
|
|
1027
|
+
const config = loadConfig();
|
|
1028
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1029
|
+
const report = buildPluginStatusReport({
|
|
1030
|
+
config,
|
|
1031
|
+
workspaceDir,
|
|
1032
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1033
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1034
|
+
});
|
|
1035
|
+
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
1036
|
+
if (!plugin) {
|
|
1037
|
+
console.error(`Plugin not found: ${id}`);
|
|
1038
|
+
process.exit(1);
|
|
1039
|
+
}
|
|
1040
|
+
if (opts.json) {
|
|
1041
|
+
console.log(JSON.stringify(plugin, null, 2));
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const install = config.plugins.installs?.[plugin.id];
|
|
1045
|
+
const lines = [];
|
|
1046
|
+
lines.push(plugin.name || plugin.id);
|
|
1047
|
+
if (plugin.name && plugin.name !== plugin.id) {
|
|
1048
|
+
lines.push(`id: ${plugin.id}`);
|
|
1049
|
+
}
|
|
1050
|
+
if (plugin.description) {
|
|
1051
|
+
lines.push(plugin.description);
|
|
1052
|
+
}
|
|
1053
|
+
lines.push("");
|
|
1054
|
+
lines.push(`Status: ${plugin.status}`);
|
|
1055
|
+
lines.push(`Source: ${plugin.source}`);
|
|
1056
|
+
lines.push(`Origin: ${plugin.origin}`);
|
|
1057
|
+
if (plugin.version) {
|
|
1058
|
+
lines.push(`Version: ${plugin.version}`);
|
|
1059
|
+
}
|
|
1060
|
+
if (plugin.toolNames.length > 0) {
|
|
1061
|
+
lines.push(`Tools: ${plugin.toolNames.join(", ")}`);
|
|
1062
|
+
}
|
|
1063
|
+
if (plugin.channelIds.length > 0) {
|
|
1064
|
+
lines.push(`Channels: ${plugin.channelIds.join(", ")}`);
|
|
1065
|
+
}
|
|
1066
|
+
if (plugin.providerIds.length > 0) {
|
|
1067
|
+
lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
|
|
1068
|
+
}
|
|
1069
|
+
if (plugin.error) {
|
|
1070
|
+
lines.push(`Error: ${plugin.error}`);
|
|
1071
|
+
}
|
|
1072
|
+
if (install) {
|
|
1073
|
+
lines.push("");
|
|
1074
|
+
lines.push(`Install: ${install.source}`);
|
|
1075
|
+
if (install.spec) {
|
|
1076
|
+
lines.push(`Spec: ${install.spec}`);
|
|
1077
|
+
}
|
|
1078
|
+
if (install.sourcePath) {
|
|
1079
|
+
lines.push(`Source path: ${install.sourcePath}`);
|
|
1080
|
+
}
|
|
1081
|
+
if (install.installPath) {
|
|
1082
|
+
lines.push(`Install path: ${install.installPath}`);
|
|
1083
|
+
}
|
|
1084
|
+
if (install.version) {
|
|
1085
|
+
lines.push(`Recorded version: ${install.version}`);
|
|
1086
|
+
}
|
|
1087
|
+
if (install.installedAt) {
|
|
1088
|
+
lines.push(`Installed at: ${install.installedAt}`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
console.log(lines.join("\n"));
|
|
1092
|
+
}
|
|
1093
|
+
pluginsEnable(id) {
|
|
1094
|
+
const config = loadConfig();
|
|
1095
|
+
const next = enablePluginInConfig(config, id);
|
|
1096
|
+
saveConfig(next);
|
|
1097
|
+
console.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
|
|
1098
|
+
}
|
|
1099
|
+
pluginsDisable(id) {
|
|
1100
|
+
const config = loadConfig();
|
|
1101
|
+
const next = disablePluginInConfig(config, id);
|
|
1102
|
+
saveConfig(next);
|
|
1103
|
+
console.log(`Disabled plugin "${id}". Restart the gateway to apply.`);
|
|
1104
|
+
}
|
|
1105
|
+
async pluginsUninstall(id, opts = {}) {
|
|
1106
|
+
const config = loadConfig();
|
|
1107
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1108
|
+
const report = buildPluginStatusReport({
|
|
1109
|
+
config,
|
|
1110
|
+
workspaceDir,
|
|
1111
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1112
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1113
|
+
});
|
|
1114
|
+
const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
|
|
1115
|
+
if (opts.keepConfig) {
|
|
1116
|
+
console.log("`--keep-config` is deprecated, use `--keep-files`.");
|
|
1117
|
+
}
|
|
1118
|
+
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
1119
|
+
const pluginId = plugin?.id ?? id;
|
|
1120
|
+
const hasEntry = pluginId in (config.plugins.entries ?? {});
|
|
1121
|
+
const hasInstall = pluginId in (config.plugins.installs ?? {});
|
|
1122
|
+
if (!hasEntry && !hasInstall) {
|
|
1123
|
+
if (plugin) {
|
|
1124
|
+
console.error(
|
|
1125
|
+
`Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`
|
|
1126
|
+
);
|
|
1127
|
+
} else {
|
|
1128
|
+
console.error(`Plugin not found: ${id}`);
|
|
1129
|
+
}
|
|
1130
|
+
process.exit(1);
|
|
1131
|
+
}
|
|
1132
|
+
const install = config.plugins.installs?.[pluginId];
|
|
1133
|
+
const isLinked = install?.source === "path";
|
|
1134
|
+
const preview = [];
|
|
1135
|
+
if (hasEntry) {
|
|
1136
|
+
preview.push("config entry");
|
|
1137
|
+
}
|
|
1138
|
+
if (hasInstall) {
|
|
1139
|
+
preview.push("install record");
|
|
1140
|
+
}
|
|
1141
|
+
if (config.plugins.allow?.includes(pluginId)) {
|
|
1142
|
+
preview.push("allowlist entry");
|
|
1143
|
+
}
|
|
1144
|
+
if (isLinked && install?.sourcePath && config.plugins.load?.paths?.includes(install.sourcePath)) {
|
|
1145
|
+
preview.push("load path");
|
|
1146
|
+
}
|
|
1147
|
+
const deleteTarget = !keepFiles ? resolveUninstallDirectoryTarget({
|
|
1148
|
+
pluginId,
|
|
1149
|
+
hasInstall,
|
|
1150
|
+
installRecord: install
|
|
1151
|
+
}) : null;
|
|
1152
|
+
if (deleteTarget) {
|
|
1153
|
+
preview.push(`directory: ${deleteTarget}`);
|
|
1154
|
+
}
|
|
1155
|
+
const pluginName = plugin?.name || pluginId;
|
|
1156
|
+
const pluginTitle = pluginName !== pluginId ? `${pluginName} (${pluginId})` : pluginName;
|
|
1157
|
+
console.log(`Plugin: ${pluginTitle}`);
|
|
1158
|
+
console.log(`Will remove: ${preview.length > 0 ? preview.join(", ") : "(nothing)"}`);
|
|
1159
|
+
if (opts.dryRun) {
|
|
1160
|
+
console.log("Dry run, no changes made.");
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (!opts.force) {
|
|
1164
|
+
const confirmed = await this.confirmYesNo(`Uninstall plugin "${pluginId}"?`);
|
|
1165
|
+
if (!confirmed) {
|
|
1166
|
+
console.log("Cancelled.");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const result = await uninstallPlugin({
|
|
1171
|
+
config,
|
|
1172
|
+
pluginId,
|
|
1173
|
+
deleteFiles: !keepFiles
|
|
1174
|
+
});
|
|
1175
|
+
if (!result.ok) {
|
|
1176
|
+
console.error(result.error);
|
|
1177
|
+
process.exit(1);
|
|
1178
|
+
}
|
|
1179
|
+
for (const warning of result.warnings) {
|
|
1180
|
+
console.warn(warning);
|
|
1181
|
+
}
|
|
1182
|
+
saveConfig(result.config);
|
|
1183
|
+
const removed = [];
|
|
1184
|
+
if (result.actions.entry) {
|
|
1185
|
+
removed.push("config entry");
|
|
1186
|
+
}
|
|
1187
|
+
if (result.actions.install) {
|
|
1188
|
+
removed.push("install record");
|
|
1189
|
+
}
|
|
1190
|
+
if (result.actions.allowlist) {
|
|
1191
|
+
removed.push("allowlist");
|
|
1192
|
+
}
|
|
1193
|
+
if (result.actions.loadPath) {
|
|
1194
|
+
removed.push("load path");
|
|
1195
|
+
}
|
|
1196
|
+
if (result.actions.directory) {
|
|
1197
|
+
removed.push("directory");
|
|
1198
|
+
}
|
|
1199
|
+
console.log(`Uninstalled plugin "${pluginId}". Removed: ${removed.length > 0 ? removed.join(", ") : "nothing"}.`);
|
|
1200
|
+
console.log("Restart the gateway to apply changes.");
|
|
1201
|
+
}
|
|
1202
|
+
async pluginsInstall(pathOrSpec, opts = {}) {
|
|
1203
|
+
const fileSpec = this.resolveFileNpmSpecToLocalPath(pathOrSpec);
|
|
1204
|
+
if (fileSpec && !fileSpec.ok) {
|
|
1205
|
+
console.error(fileSpec.error);
|
|
1206
|
+
process.exit(1);
|
|
1207
|
+
}
|
|
1208
|
+
const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
|
|
1209
|
+
const resolved = resolve4(expandHome(normalized));
|
|
1210
|
+
const config = loadConfig();
|
|
1211
|
+
if (existsSync4(resolved)) {
|
|
1212
|
+
if (opts.link) {
|
|
1213
|
+
const probe = await installPluginFromPath({ path: resolved, dryRun: true });
|
|
1214
|
+
if (!probe.ok) {
|
|
1215
|
+
console.error(probe.error);
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1218
|
+
let next3 = addPluginLoadPath(config, resolved);
|
|
1219
|
+
next3 = enablePluginInConfig(next3, probe.pluginId);
|
|
1220
|
+
next3 = recordPluginInstall(next3, {
|
|
1221
|
+
pluginId: probe.pluginId,
|
|
1222
|
+
source: "path",
|
|
1223
|
+
sourcePath: resolved,
|
|
1224
|
+
installPath: resolved,
|
|
1225
|
+
version: probe.version
|
|
1226
|
+
});
|
|
1227
|
+
saveConfig(next3);
|
|
1228
|
+
console.log(`Linked plugin path: ${resolved}`);
|
|
1229
|
+
console.log("Restart the gateway to load plugins.");
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const result2 = await installPluginFromPath({
|
|
1233
|
+
path: resolved,
|
|
1234
|
+
logger: {
|
|
1235
|
+
info: (message) => console.log(message),
|
|
1236
|
+
warn: (message) => console.warn(message)
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
if (!result2.ok) {
|
|
1240
|
+
console.error(result2.error);
|
|
1241
|
+
process.exit(1);
|
|
1242
|
+
}
|
|
1243
|
+
let next2 = enablePluginInConfig(config, result2.pluginId);
|
|
1244
|
+
next2 = recordPluginInstall(next2, {
|
|
1245
|
+
pluginId: result2.pluginId,
|
|
1246
|
+
source: this.isArchivePath(resolved) ? "archive" : "path",
|
|
1247
|
+
sourcePath: resolved,
|
|
1248
|
+
installPath: result2.targetDir,
|
|
1249
|
+
version: result2.version
|
|
1250
|
+
});
|
|
1251
|
+
saveConfig(next2);
|
|
1252
|
+
console.log(`Installed plugin: ${result2.pluginId}`);
|
|
1253
|
+
console.log("Restart the gateway to load plugins.");
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (opts.link) {
|
|
1257
|
+
console.error("`--link` requires a local path.");
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
}
|
|
1260
|
+
if (this.looksLikePath(pathOrSpec)) {
|
|
1261
|
+
console.error(`Path not found: ${resolved}`);
|
|
1262
|
+
process.exit(1);
|
|
1263
|
+
}
|
|
1264
|
+
const result = await installPluginFromNpmSpec({
|
|
1265
|
+
spec: pathOrSpec,
|
|
1266
|
+
logger: {
|
|
1267
|
+
info: (message) => console.log(message),
|
|
1268
|
+
warn: (message) => console.warn(message)
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
if (!result.ok) {
|
|
1272
|
+
console.error(result.error);
|
|
1273
|
+
process.exit(1);
|
|
1274
|
+
}
|
|
1275
|
+
let next = enablePluginInConfig(config, result.pluginId);
|
|
1276
|
+
next = recordPluginInstall(next, {
|
|
1277
|
+
pluginId: result.pluginId,
|
|
1278
|
+
source: "npm",
|
|
1279
|
+
spec: pathOrSpec,
|
|
1280
|
+
installPath: result.targetDir,
|
|
1281
|
+
version: result.version
|
|
1282
|
+
});
|
|
1283
|
+
saveConfig(next);
|
|
1284
|
+
console.log(`Installed plugin: ${result.pluginId}`);
|
|
1285
|
+
console.log("Restart the gateway to load plugins.");
|
|
1286
|
+
}
|
|
1287
|
+
pluginsDoctor() {
|
|
1288
|
+
const config = loadConfig();
|
|
1289
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1290
|
+
const report = buildPluginStatusReport({
|
|
1291
|
+
config,
|
|
1292
|
+
workspaceDir,
|
|
1293
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1294
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1295
|
+
});
|
|
1296
|
+
const pluginErrors = report.plugins.filter((plugin) => plugin.status === "error");
|
|
1297
|
+
const diagnostics = report.diagnostics.filter((diag) => diag.level === "error");
|
|
1298
|
+
if (pluginErrors.length === 0 && diagnostics.length === 0) {
|
|
1299
|
+
console.log("No plugin issues detected.");
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
if (pluginErrors.length > 0) {
|
|
1303
|
+
console.log("Plugin errors:");
|
|
1304
|
+
for (const entry of pluginErrors) {
|
|
1305
|
+
console.log(`- ${entry.id}: ${entry.error ?? "failed to load"} (${entry.source})`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (diagnostics.length > 0) {
|
|
1309
|
+
if (pluginErrors.length > 0) {
|
|
1310
|
+
console.log("");
|
|
1311
|
+
}
|
|
1312
|
+
console.log("Diagnostics:");
|
|
1313
|
+
for (const diag of diagnostics) {
|
|
1314
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
1315
|
+
console.log(`- ${prefix}${diag.message}`);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
942
1319
|
async skillsInstall(options) {
|
|
943
1320
|
const workdir = options.workdir ? expandHome(options.workdir) : getWorkspacePath();
|
|
944
1321
|
const result = await installClawHubSkill({
|
|
@@ -1071,14 +1448,85 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1071
1448
|
}
|
|
1072
1449
|
}
|
|
1073
1450
|
}
|
|
1451
|
+
loadPluginRegistry(config, workspaceDir) {
|
|
1452
|
+
return loadOpenClawPlugins({
|
|
1453
|
+
config,
|
|
1454
|
+
workspaceDir,
|
|
1455
|
+
reservedToolNames: [
|
|
1456
|
+
"read_file",
|
|
1457
|
+
"write_file",
|
|
1458
|
+
"edit_file",
|
|
1459
|
+
"list_dir",
|
|
1460
|
+
"exec",
|
|
1461
|
+
"web_search",
|
|
1462
|
+
"web_fetch",
|
|
1463
|
+
"message",
|
|
1464
|
+
"spawn",
|
|
1465
|
+
"sessions_list",
|
|
1466
|
+
"sessions_history",
|
|
1467
|
+
"sessions_send",
|
|
1468
|
+
"memory_search",
|
|
1469
|
+
"memory_get",
|
|
1470
|
+
"subagents",
|
|
1471
|
+
"gateway",
|
|
1472
|
+
"cron"
|
|
1473
|
+
],
|
|
1474
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1475
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name),
|
|
1476
|
+
logger: {
|
|
1477
|
+
info: (message) => console.log(message),
|
|
1478
|
+
warn: (message) => console.warn(message),
|
|
1479
|
+
error: (message) => console.error(message),
|
|
1480
|
+
debug: (message) => console.debug(message)
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
toExtensionRegistry(pluginRegistry) {
|
|
1485
|
+
return {
|
|
1486
|
+
tools: pluginRegistry.tools.map((tool) => ({
|
|
1487
|
+
extensionId: tool.pluginId,
|
|
1488
|
+
factory: tool.factory,
|
|
1489
|
+
names: tool.names,
|
|
1490
|
+
optional: tool.optional,
|
|
1491
|
+
source: tool.source
|
|
1492
|
+
})),
|
|
1493
|
+
channels: pluginRegistry.channels.map((channel) => ({
|
|
1494
|
+
extensionId: channel.pluginId,
|
|
1495
|
+
channel: channel.channel,
|
|
1496
|
+
source: channel.source
|
|
1497
|
+
})),
|
|
1498
|
+
diagnostics: pluginRegistry.diagnostics.map((diag) => ({
|
|
1499
|
+
level: diag.level,
|
|
1500
|
+
message: diag.message,
|
|
1501
|
+
extensionId: diag.pluginId,
|
|
1502
|
+
source: diag.source
|
|
1503
|
+
}))
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
logPluginDiagnostics(registry) {
|
|
1507
|
+
for (const diag of registry.diagnostics) {
|
|
1508
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
1509
|
+
const text = `${prefix}${diag.message}`;
|
|
1510
|
+
if (diag.level === "error") {
|
|
1511
|
+
console.error(`[plugins] ${text}`);
|
|
1512
|
+
} else {
|
|
1513
|
+
console.warn(`[plugins] ${text}`);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1074
1517
|
async startGateway(options = {}) {
|
|
1075
1518
|
const config = loadConfig();
|
|
1519
|
+
const workspace = getWorkspacePath(config.agents.defaults.workspace);
|
|
1520
|
+
const pluginRegistry = this.loadPluginRegistry(config, workspace);
|
|
1521
|
+
const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
|
|
1522
|
+
this.logPluginDiagnostics(pluginRegistry);
|
|
1076
1523
|
const bus = new MessageBus();
|
|
1077
1524
|
const provider = options.allowMissingProvider === true ? this.makeProvider(config, { allowMissing: true }) : this.makeProvider(config);
|
|
1078
1525
|
const providerManager = provider ? new ProviderManager(provider) : null;
|
|
1079
|
-
const sessionManager = new SessionManager(
|
|
1526
|
+
const sessionManager = new SessionManager(workspace);
|
|
1080
1527
|
const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
1081
1528
|
const cron2 = new CronService(cronStorePath);
|
|
1529
|
+
const pluginUiMetadata = loadPluginUiMetadata({ config, workspaceDir: workspace });
|
|
1082
1530
|
const uiConfig = resolveUiConfig(config, options.uiOverrides);
|
|
1083
1531
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
1084
1532
|
if (!provider) {
|
|
@@ -1088,7 +1536,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1088
1536
|
});
|
|
1089
1537
|
return;
|
|
1090
1538
|
}
|
|
1091
|
-
const channels2 = new ChannelManager(config, bus, sessionManager);
|
|
1539
|
+
const channels2 = new ChannelManager(config, bus, sessionManager, extensionRegistry.channels);
|
|
1092
1540
|
const reloader = new ConfigReloader({
|
|
1093
1541
|
initialConfig: config,
|
|
1094
1542
|
channels: channels2,
|
|
@@ -1097,6 +1545,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1097
1545
|
providerManager,
|
|
1098
1546
|
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }),
|
|
1099
1547
|
loadConfig,
|
|
1548
|
+
getExtensionChannels: () => extensionRegistry.channels,
|
|
1100
1549
|
onRestartRequired: (paths) => {
|
|
1101
1550
|
console.warn(`Config changes require restart: ${paths.join(", ")}`);
|
|
1102
1551
|
}
|
|
@@ -1105,12 +1554,13 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1105
1554
|
reloader,
|
|
1106
1555
|
cron: cron2,
|
|
1107
1556
|
getConfigPath,
|
|
1108
|
-
saveConfig
|
|
1557
|
+
saveConfig,
|
|
1558
|
+
getPluginUiMetadata: () => pluginUiMetadata
|
|
1109
1559
|
});
|
|
1110
1560
|
const agent = new AgentLoop({
|
|
1111
1561
|
bus,
|
|
1112
1562
|
providerManager: providerManager ?? new ProviderManager(provider),
|
|
1113
|
-
workspace
|
|
1563
|
+
workspace,
|
|
1114
1564
|
model: config.agents.defaults.model,
|
|
1115
1565
|
maxIterations: config.agents.defaults.maxToolIterations,
|
|
1116
1566
|
braveApiKey: config.tools.web.search.apiKey || void 0,
|
|
@@ -1119,7 +1569,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1119
1569
|
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
1120
1570
|
sessionManager,
|
|
1121
1571
|
contextConfig: config.agents.context,
|
|
1122
|
-
gatewayController
|
|
1572
|
+
gatewayController,
|
|
1573
|
+
config,
|
|
1574
|
+
extensionRegistry
|
|
1123
1575
|
});
|
|
1124
1576
|
cron2.onJob = async (job) => {
|
|
1125
1577
|
const response = await agent.processDirect({
|
|
@@ -1140,7 +1592,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1140
1592
|
return response;
|
|
1141
1593
|
};
|
|
1142
1594
|
const heartbeat = new HeartbeatService(
|
|
1143
|
-
|
|
1595
|
+
workspace,
|
|
1144
1596
|
async (promptText) => agent.processDirect({ content: promptText, sessionKey: "heartbeat" }),
|
|
1145
1597
|
30 * 60,
|
|
1146
1598
|
true
|
|
@@ -1310,6 +1762,18 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1310
1762
|
clearServiceState();
|
|
1311
1763
|
console.log(`\u2713 ${APP_NAME} stopped`);
|
|
1312
1764
|
}
|
|
1765
|
+
async confirmYesNo(question) {
|
|
1766
|
+
const rl = createInterface({
|
|
1767
|
+
input: process.stdin,
|
|
1768
|
+
output: process.stdout
|
|
1769
|
+
});
|
|
1770
|
+
const answer = await new Promise((resolve5) => {
|
|
1771
|
+
rl.question(`${question} [y/N] `, (line) => resolve5(line));
|
|
1772
|
+
});
|
|
1773
|
+
rl.close();
|
|
1774
|
+
const normalized = answer.trim().toLowerCase();
|
|
1775
|
+
return normalized === "y" || normalized === "yes";
|
|
1776
|
+
}
|
|
1313
1777
|
makeProvider(config, options) {
|
|
1314
1778
|
const provider = getProvider(config);
|
|
1315
1779
|
const model = config.agents.defaults.model;
|
|
@@ -1330,6 +1794,36 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1330
1794
|
wireApi: provider?.wireApi ?? null
|
|
1331
1795
|
});
|
|
1332
1796
|
}
|
|
1797
|
+
resolveFileNpmSpecToLocalPath(raw) {
|
|
1798
|
+
const trimmed = raw.trim();
|
|
1799
|
+
if (!trimmed.toLowerCase().startsWith("file:")) {
|
|
1800
|
+
return null;
|
|
1801
|
+
}
|
|
1802
|
+
const rest = trimmed.slice("file:".length);
|
|
1803
|
+
if (!rest) {
|
|
1804
|
+
return { ok: false, error: "unsupported file: spec: missing path" };
|
|
1805
|
+
}
|
|
1806
|
+
if (rest.startsWith("///")) {
|
|
1807
|
+
return { ok: true, path: rest.slice(2) };
|
|
1808
|
+
}
|
|
1809
|
+
if (rest.startsWith("//localhost/")) {
|
|
1810
|
+
return { ok: true, path: rest.slice("//localhost".length) };
|
|
1811
|
+
}
|
|
1812
|
+
if (rest.startsWith("//")) {
|
|
1813
|
+
return {
|
|
1814
|
+
ok: false,
|
|
1815
|
+
error: 'unsupported file: URL host (expected "file:<path>" or "file:///abs/path")'
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
return { ok: true, path: rest };
|
|
1819
|
+
}
|
|
1820
|
+
looksLikePath(raw) {
|
|
1821
|
+
return raw.startsWith(".") || raw.startsWith("~") || raw.startsWith("/") || raw.endsWith(".ts") || raw.endsWith(".js") || raw.endsWith(".mjs") || raw.endsWith(".cjs") || raw.endsWith(".tgz") || raw.endsWith(".tar.gz") || raw.endsWith(".tar") || raw.endsWith(".zip");
|
|
1822
|
+
}
|
|
1823
|
+
isArchivePath(filePath) {
|
|
1824
|
+
const lower = filePath.toLowerCase();
|
|
1825
|
+
return lower.endsWith(".zip") || lower.endsWith(".tgz") || lower.endsWith(".tar.gz") || lower.endsWith(".tar");
|
|
1826
|
+
}
|
|
1333
1827
|
createWorkspaceTemplates(workspace, options = {}) {
|
|
1334
1828
|
const created = [];
|
|
1335
1829
|
const force = Boolean(options.force);
|
|
@@ -1413,7 +1907,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1413
1907
|
resolveBuiltinSkillsDir() {
|
|
1414
1908
|
try {
|
|
1415
1909
|
const require2 = createRequire(import.meta.url);
|
|
1416
|
-
const entry = require2.resolve("nextclaw
|
|
1910
|
+
const entry = require2.resolve("@nextclaw/core");
|
|
1417
1911
|
const pkgRoot = resolve4(dirname(entry), "..");
|
|
1418
1912
|
const distSkills = join3(pkgRoot, "dist", "skills");
|
|
1419
1913
|
if (existsSync4(distSkills)) {
|
|
@@ -1516,6 +2010,14 @@ var skills = program.command("skills").description("Manage skills");
|
|
|
1516
2010
|
registerClawHubInstall(skills);
|
|
1517
2011
|
var clawhub = program.command("clawhub").description("Install skills from ClawHub");
|
|
1518
2012
|
registerClawHubInstall(clawhub);
|
|
2013
|
+
var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
|
|
2014
|
+
plugins.command("list").description("List discovered plugins").option("--json", "Print JSON").option("--enabled", "Only show enabled plugins", false).option("--verbose", "Show detailed entries", false).action((opts) => runtime.pluginsList(opts));
|
|
2015
|
+
plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));
|
|
2016
|
+
plugins.command("enable <id>").description("Enable a plugin in config").action((id) => runtime.pluginsEnable(id));
|
|
2017
|
+
plugins.command("disable <id>").description("Disable a plugin in config").action((id) => runtime.pluginsDisable(id));
|
|
2018
|
+
plugins.command("uninstall <id>").description("Uninstall a plugin").option("--keep-files", "Keep installed files on disk", false).option("--keep-config", "Deprecated alias for --keep-files", false).option("--force", "Skip confirmation prompt", false).option("--dry-run", "Show what would be removed without making changes", false).action(async (id, opts) => runtime.pluginsUninstall(id, opts));
|
|
2019
|
+
plugins.command("install <path-or-spec>").description("Install a plugin (path, archive, or npm spec)").option("-l, --link", "Link a local path instead of copying", false).action(async (pathOrSpec, opts) => runtime.pluginsInstall(pathOrSpec, opts));
|
|
2020
|
+
plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
|
|
1519
2021
|
var channels = program.command("channels").description("Manage channels");
|
|
1520
2022
|
channels.command("status").description("Show channel status").action(() => runtime.channelsStatus());
|
|
1521
2023
|
channels.command("login").description("Link device via QR code").action(() => runtime.channelsLogin());
|