nextclaw 0.4.9 → 0.4.11
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 +714 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -5
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,26 @@ 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
|
+
setPluginRuntimeBridge,
|
|
46
|
+
getPluginChannelBindings,
|
|
47
|
+
getPluginUiMetadataFromRegistry,
|
|
48
|
+
resolvePluginChannelMessageToolHints,
|
|
49
|
+
startPluginChannelGateways,
|
|
50
|
+
stopPluginChannelGateways
|
|
51
|
+
} from "@nextclaw/openclaw-compat";
|
|
52
|
+
import { startUiServer } from "@nextclaw/server";
|
|
35
53
|
import {
|
|
36
54
|
closeSync,
|
|
37
55
|
cpSync,
|
|
@@ -57,7 +75,7 @@ import {
|
|
|
57
75
|
buildConfigSchema,
|
|
58
76
|
ConfigSchema,
|
|
59
77
|
redactConfigObject
|
|
60
|
-
} from "nextclaw
|
|
78
|
+
} from "@nextclaw/core";
|
|
61
79
|
|
|
62
80
|
// src/cli/utils.ts
|
|
63
81
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
@@ -65,7 +83,7 @@ import { join, resolve } from "path";
|
|
|
65
83
|
import { spawn } from "child_process";
|
|
66
84
|
import { createServer } from "net";
|
|
67
85
|
import { fileURLToPath } from "url";
|
|
68
|
-
import { getDataDir, getPackageVersion } from "nextclaw
|
|
86
|
+
import { getDataDir, getPackageVersion } from "@nextclaw/core";
|
|
69
87
|
function resolveUiConfig(config, overrides) {
|
|
70
88
|
const base = config.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
|
|
71
89
|
return { ...base, ...overrides ?? {} };
|
|
@@ -331,7 +349,7 @@ function runSelfUpdate(options = {}) {
|
|
|
331
349
|
|
|
332
350
|
// src/cli/gateway/controller.ts
|
|
333
351
|
var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
|
|
334
|
-
var readConfigSnapshot = (getConfigPath2) => {
|
|
352
|
+
var readConfigSnapshot = (getConfigPath2, plugins2) => {
|
|
335
353
|
const path = getConfigPath2();
|
|
336
354
|
let raw = "";
|
|
337
355
|
let parsed = {};
|
|
@@ -355,12 +373,12 @@ var readConfigSnapshot = (getConfigPath2) => {
|
|
|
355
373
|
raw = JSON.stringify(config, null, 2);
|
|
356
374
|
}
|
|
357
375
|
const hash = hashRaw(raw);
|
|
358
|
-
const schema = buildConfigSchema({ version: getPackageVersion() });
|
|
376
|
+
const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
|
|
359
377
|
const redacted = redactConfigObject(config, schema.uiHints);
|
|
360
378
|
return { raw: valid ? JSON.stringify(redacted, null, 2) : null, hash: valid ? hash : null, config, redacted, valid };
|
|
361
379
|
};
|
|
362
|
-
var redactValue = (value) => {
|
|
363
|
-
const schema = buildConfigSchema({ version: getPackageVersion() });
|
|
380
|
+
var redactValue = (value, plugins2) => {
|
|
381
|
+
const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
|
|
364
382
|
return redactConfigObject(value, schema.uiHints);
|
|
365
383
|
};
|
|
366
384
|
var mergeDeep = (base, patch) => {
|
|
@@ -405,7 +423,8 @@ var GatewayControllerImpl = class {
|
|
|
405
423
|
return "Restart scheduled";
|
|
406
424
|
}
|
|
407
425
|
async getConfig() {
|
|
408
|
-
const
|
|
426
|
+
const plugins2 = this.deps.getPluginUiMetadata?.() ?? [];
|
|
427
|
+
const snapshot = readConfigSnapshot(this.deps.getConfigPath, plugins2);
|
|
409
428
|
return {
|
|
410
429
|
raw: snapshot.raw,
|
|
411
430
|
hash: snapshot.hash,
|
|
@@ -417,10 +436,11 @@ var GatewayControllerImpl = class {
|
|
|
417
436
|
};
|
|
418
437
|
}
|
|
419
438
|
async getConfigSchema() {
|
|
420
|
-
return buildConfigSchema({ version: getPackageVersion() });
|
|
439
|
+
return buildConfigSchema({ version: getPackageVersion(), plugins: this.deps.getPluginUiMetadata?.() ?? [] });
|
|
421
440
|
}
|
|
422
441
|
async applyConfig(params) {
|
|
423
|
-
const
|
|
442
|
+
const plugins2 = this.deps.getPluginUiMetadata?.() ?? [];
|
|
443
|
+
const snapshot = readConfigSnapshot(this.deps.getConfigPath, plugins2);
|
|
424
444
|
if (!params.baseHash) {
|
|
425
445
|
return { ok: false, error: "config base hash required; re-run config.get and retry" };
|
|
426
446
|
}
|
|
@@ -449,12 +469,13 @@ var GatewayControllerImpl = class {
|
|
|
449
469
|
ok: true,
|
|
450
470
|
note: params.note ?? null,
|
|
451
471
|
path: this.deps.getConfigPath(),
|
|
452
|
-
config: redactValue(validated),
|
|
472
|
+
config: redactValue(validated, plugins2),
|
|
453
473
|
restart: { scheduled: true, delayMs }
|
|
454
474
|
};
|
|
455
475
|
}
|
|
456
476
|
async patchConfig(params) {
|
|
457
|
-
const
|
|
477
|
+
const plugins2 = this.deps.getPluginUiMetadata?.() ?? [];
|
|
478
|
+
const snapshot = readConfigSnapshot(this.deps.getConfigPath, plugins2);
|
|
458
479
|
if (!params.baseHash) {
|
|
459
480
|
return { ok: false, error: "config base hash required; re-run config.get and retry" };
|
|
460
481
|
}
|
|
@@ -484,7 +505,7 @@ var GatewayControllerImpl = class {
|
|
|
484
505
|
ok: true,
|
|
485
506
|
note: params.note ?? null,
|
|
486
507
|
path: this.deps.getConfigPath(),
|
|
487
|
-
config: redactValue(validated),
|
|
508
|
+
config: redactValue(validated, plugins2),
|
|
488
509
|
restart: { scheduled: true, delayMs }
|
|
489
510
|
};
|
|
490
511
|
}
|
|
@@ -653,7 +674,12 @@ var ConfigReloader = class {
|
|
|
653
674
|
}
|
|
654
675
|
this.reloadTask = (async () => {
|
|
655
676
|
await this.channels.stopAll();
|
|
656
|
-
this.channels = new ChannelManager(
|
|
677
|
+
this.channels = new ChannelManager(
|
|
678
|
+
nextConfig,
|
|
679
|
+
this.options.bus,
|
|
680
|
+
this.options.sessionManager,
|
|
681
|
+
this.options.getExtensionChannels?.() ?? []
|
|
682
|
+
);
|
|
657
683
|
await this.channels.startAll();
|
|
658
684
|
})();
|
|
659
685
|
try {
|
|
@@ -851,17 +877,29 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
851
877
|
}
|
|
852
878
|
async agent(opts) {
|
|
853
879
|
const config = loadConfig();
|
|
880
|
+
const workspace = getWorkspacePath(config.agents.defaults.workspace);
|
|
881
|
+
const pluginRegistry = this.loadPluginRegistry(config, workspace);
|
|
882
|
+
const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
|
|
883
|
+
this.logPluginDiagnostics(pluginRegistry);
|
|
854
884
|
const bus = new MessageBus();
|
|
855
885
|
const provider = this.makeProvider(config);
|
|
856
886
|
const providerManager = new ProviderManager(provider);
|
|
857
887
|
const agentLoop = new AgentLoop({
|
|
858
888
|
bus,
|
|
859
889
|
providerManager,
|
|
860
|
-
workspace
|
|
890
|
+
workspace,
|
|
861
891
|
braveApiKey: config.tools.web.search.apiKey || void 0,
|
|
862
892
|
execConfig: config.tools.exec,
|
|
863
893
|
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
864
|
-
contextConfig: config.agents.context
|
|
894
|
+
contextConfig: config.agents.context,
|
|
895
|
+
config,
|
|
896
|
+
extensionRegistry,
|
|
897
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
898
|
+
registry: pluginRegistry,
|
|
899
|
+
channel,
|
|
900
|
+
cfg: loadConfig(),
|
|
901
|
+
accountId
|
|
902
|
+
})
|
|
865
903
|
});
|
|
866
904
|
if (opts.message) {
|
|
867
905
|
const response = await agentLoop.processDirect({
|
|
@@ -939,6 +977,356 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
939
977
|
console.log(`Tip: restart ${APP_NAME} to apply the update.`);
|
|
940
978
|
}
|
|
941
979
|
}
|
|
980
|
+
pluginsList(opts = {}) {
|
|
981
|
+
const config = loadConfig();
|
|
982
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
983
|
+
const report = buildPluginStatusReport({
|
|
984
|
+
config,
|
|
985
|
+
workspaceDir,
|
|
986
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
987
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
988
|
+
});
|
|
989
|
+
const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
|
|
990
|
+
if (opts.json) {
|
|
991
|
+
console.log(
|
|
992
|
+
JSON.stringify(
|
|
993
|
+
{
|
|
994
|
+
workspaceDir,
|
|
995
|
+
plugins: list,
|
|
996
|
+
diagnostics: report.diagnostics
|
|
997
|
+
},
|
|
998
|
+
null,
|
|
999
|
+
2
|
|
1000
|
+
)
|
|
1001
|
+
);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (list.length === 0) {
|
|
1005
|
+
console.log("No plugins discovered.");
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
for (const plugin of list) {
|
|
1009
|
+
const status = plugin.status === "loaded" ? "loaded" : plugin.status === "disabled" ? "disabled" : "error";
|
|
1010
|
+
const title = plugin.name && plugin.name !== plugin.id ? `${plugin.name} (${plugin.id})` : plugin.id;
|
|
1011
|
+
if (!opts.verbose) {
|
|
1012
|
+
const desc = plugin.description ? plugin.description.length > 80 ? `${plugin.description.slice(0, 77)}...` : plugin.description : "(no description)";
|
|
1013
|
+
console.log(`${title} ${status} - ${desc}`);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
console.log(`${title} ${status}`);
|
|
1017
|
+
console.log(` source: ${plugin.source}`);
|
|
1018
|
+
console.log(` origin: ${plugin.origin}`);
|
|
1019
|
+
if (plugin.version) {
|
|
1020
|
+
console.log(` version: ${plugin.version}`);
|
|
1021
|
+
}
|
|
1022
|
+
if (plugin.toolNames.length > 0) {
|
|
1023
|
+
console.log(` tools: ${plugin.toolNames.join(", ")}`);
|
|
1024
|
+
}
|
|
1025
|
+
if (plugin.channelIds.length > 0) {
|
|
1026
|
+
console.log(` channels: ${plugin.channelIds.join(", ")}`);
|
|
1027
|
+
}
|
|
1028
|
+
if (plugin.providerIds.length > 0) {
|
|
1029
|
+
console.log(` providers: ${plugin.providerIds.join(", ")}`);
|
|
1030
|
+
}
|
|
1031
|
+
if (plugin.error) {
|
|
1032
|
+
console.log(` error: ${plugin.error}`);
|
|
1033
|
+
}
|
|
1034
|
+
console.log("");
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
pluginsInfo(id, opts = {}) {
|
|
1038
|
+
const config = loadConfig();
|
|
1039
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1040
|
+
const report = buildPluginStatusReport({
|
|
1041
|
+
config,
|
|
1042
|
+
workspaceDir,
|
|
1043
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1044
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1045
|
+
});
|
|
1046
|
+
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
1047
|
+
if (!plugin) {
|
|
1048
|
+
console.error(`Plugin not found: ${id}`);
|
|
1049
|
+
process.exit(1);
|
|
1050
|
+
}
|
|
1051
|
+
if (opts.json) {
|
|
1052
|
+
console.log(JSON.stringify(plugin, null, 2));
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const install = config.plugins.installs?.[plugin.id];
|
|
1056
|
+
const lines = [];
|
|
1057
|
+
lines.push(plugin.name || plugin.id);
|
|
1058
|
+
if (plugin.name && plugin.name !== plugin.id) {
|
|
1059
|
+
lines.push(`id: ${plugin.id}`);
|
|
1060
|
+
}
|
|
1061
|
+
if (plugin.description) {
|
|
1062
|
+
lines.push(plugin.description);
|
|
1063
|
+
}
|
|
1064
|
+
lines.push("");
|
|
1065
|
+
lines.push(`Status: ${plugin.status}`);
|
|
1066
|
+
lines.push(`Source: ${plugin.source}`);
|
|
1067
|
+
lines.push(`Origin: ${plugin.origin}`);
|
|
1068
|
+
if (plugin.version) {
|
|
1069
|
+
lines.push(`Version: ${plugin.version}`);
|
|
1070
|
+
}
|
|
1071
|
+
if (plugin.toolNames.length > 0) {
|
|
1072
|
+
lines.push(`Tools: ${plugin.toolNames.join(", ")}`);
|
|
1073
|
+
}
|
|
1074
|
+
if (plugin.channelIds.length > 0) {
|
|
1075
|
+
lines.push(`Channels: ${plugin.channelIds.join(", ")}`);
|
|
1076
|
+
}
|
|
1077
|
+
if (plugin.providerIds.length > 0) {
|
|
1078
|
+
lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
|
|
1079
|
+
}
|
|
1080
|
+
if (plugin.error) {
|
|
1081
|
+
lines.push(`Error: ${plugin.error}`);
|
|
1082
|
+
}
|
|
1083
|
+
if (install) {
|
|
1084
|
+
lines.push("");
|
|
1085
|
+
lines.push(`Install: ${install.source}`);
|
|
1086
|
+
if (install.spec) {
|
|
1087
|
+
lines.push(`Spec: ${install.spec}`);
|
|
1088
|
+
}
|
|
1089
|
+
if (install.sourcePath) {
|
|
1090
|
+
lines.push(`Source path: ${install.sourcePath}`);
|
|
1091
|
+
}
|
|
1092
|
+
if (install.installPath) {
|
|
1093
|
+
lines.push(`Install path: ${install.installPath}`);
|
|
1094
|
+
}
|
|
1095
|
+
if (install.version) {
|
|
1096
|
+
lines.push(`Recorded version: ${install.version}`);
|
|
1097
|
+
}
|
|
1098
|
+
if (install.installedAt) {
|
|
1099
|
+
lines.push(`Installed at: ${install.installedAt}`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
console.log(lines.join("\n"));
|
|
1103
|
+
}
|
|
1104
|
+
pluginsEnable(id) {
|
|
1105
|
+
const config = loadConfig();
|
|
1106
|
+
const next = enablePluginInConfig(config, id);
|
|
1107
|
+
saveConfig(next);
|
|
1108
|
+
console.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
|
|
1109
|
+
}
|
|
1110
|
+
pluginsDisable(id) {
|
|
1111
|
+
const config = loadConfig();
|
|
1112
|
+
const next = disablePluginInConfig(config, id);
|
|
1113
|
+
saveConfig(next);
|
|
1114
|
+
console.log(`Disabled plugin "${id}". Restart the gateway to apply.`);
|
|
1115
|
+
}
|
|
1116
|
+
async pluginsUninstall(id, opts = {}) {
|
|
1117
|
+
const config = loadConfig();
|
|
1118
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1119
|
+
const report = buildPluginStatusReport({
|
|
1120
|
+
config,
|
|
1121
|
+
workspaceDir,
|
|
1122
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1123
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1124
|
+
});
|
|
1125
|
+
const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
|
|
1126
|
+
if (opts.keepConfig) {
|
|
1127
|
+
console.log("`--keep-config` is deprecated, use `--keep-files`.");
|
|
1128
|
+
}
|
|
1129
|
+
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
1130
|
+
const pluginId = plugin?.id ?? id;
|
|
1131
|
+
const hasEntry = pluginId in (config.plugins.entries ?? {});
|
|
1132
|
+
const hasInstall = pluginId in (config.plugins.installs ?? {});
|
|
1133
|
+
if (!hasEntry && !hasInstall) {
|
|
1134
|
+
if (plugin) {
|
|
1135
|
+
console.error(
|
|
1136
|
+
`Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`
|
|
1137
|
+
);
|
|
1138
|
+
} else {
|
|
1139
|
+
console.error(`Plugin not found: ${id}`);
|
|
1140
|
+
}
|
|
1141
|
+
process.exit(1);
|
|
1142
|
+
}
|
|
1143
|
+
const install = config.plugins.installs?.[pluginId];
|
|
1144
|
+
const isLinked = install?.source === "path";
|
|
1145
|
+
const preview = [];
|
|
1146
|
+
if (hasEntry) {
|
|
1147
|
+
preview.push("config entry");
|
|
1148
|
+
}
|
|
1149
|
+
if (hasInstall) {
|
|
1150
|
+
preview.push("install record");
|
|
1151
|
+
}
|
|
1152
|
+
if (config.plugins.allow?.includes(pluginId)) {
|
|
1153
|
+
preview.push("allowlist entry");
|
|
1154
|
+
}
|
|
1155
|
+
if (isLinked && install?.sourcePath && config.plugins.load?.paths?.includes(install.sourcePath)) {
|
|
1156
|
+
preview.push("load path");
|
|
1157
|
+
}
|
|
1158
|
+
const deleteTarget = !keepFiles ? resolveUninstallDirectoryTarget({
|
|
1159
|
+
pluginId,
|
|
1160
|
+
hasInstall,
|
|
1161
|
+
installRecord: install
|
|
1162
|
+
}) : null;
|
|
1163
|
+
if (deleteTarget) {
|
|
1164
|
+
preview.push(`directory: ${deleteTarget}`);
|
|
1165
|
+
}
|
|
1166
|
+
const pluginName = plugin?.name || pluginId;
|
|
1167
|
+
const pluginTitle = pluginName !== pluginId ? `${pluginName} (${pluginId})` : pluginName;
|
|
1168
|
+
console.log(`Plugin: ${pluginTitle}`);
|
|
1169
|
+
console.log(`Will remove: ${preview.length > 0 ? preview.join(", ") : "(nothing)"}`);
|
|
1170
|
+
if (opts.dryRun) {
|
|
1171
|
+
console.log("Dry run, no changes made.");
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (!opts.force) {
|
|
1175
|
+
const confirmed = await this.confirmYesNo(`Uninstall plugin "${pluginId}"?`);
|
|
1176
|
+
if (!confirmed) {
|
|
1177
|
+
console.log("Cancelled.");
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const result = await uninstallPlugin({
|
|
1182
|
+
config,
|
|
1183
|
+
pluginId,
|
|
1184
|
+
deleteFiles: !keepFiles
|
|
1185
|
+
});
|
|
1186
|
+
if (!result.ok) {
|
|
1187
|
+
console.error(result.error);
|
|
1188
|
+
process.exit(1);
|
|
1189
|
+
}
|
|
1190
|
+
for (const warning of result.warnings) {
|
|
1191
|
+
console.warn(warning);
|
|
1192
|
+
}
|
|
1193
|
+
saveConfig(result.config);
|
|
1194
|
+
const removed = [];
|
|
1195
|
+
if (result.actions.entry) {
|
|
1196
|
+
removed.push("config entry");
|
|
1197
|
+
}
|
|
1198
|
+
if (result.actions.install) {
|
|
1199
|
+
removed.push("install record");
|
|
1200
|
+
}
|
|
1201
|
+
if (result.actions.allowlist) {
|
|
1202
|
+
removed.push("allowlist");
|
|
1203
|
+
}
|
|
1204
|
+
if (result.actions.loadPath) {
|
|
1205
|
+
removed.push("load path");
|
|
1206
|
+
}
|
|
1207
|
+
if (result.actions.directory) {
|
|
1208
|
+
removed.push("directory");
|
|
1209
|
+
}
|
|
1210
|
+
console.log(`Uninstalled plugin "${pluginId}". Removed: ${removed.length > 0 ? removed.join(", ") : "nothing"}.`);
|
|
1211
|
+
console.log("Restart the gateway to apply changes.");
|
|
1212
|
+
}
|
|
1213
|
+
async pluginsInstall(pathOrSpec, opts = {}) {
|
|
1214
|
+
const fileSpec = this.resolveFileNpmSpecToLocalPath(pathOrSpec);
|
|
1215
|
+
if (fileSpec && !fileSpec.ok) {
|
|
1216
|
+
console.error(fileSpec.error);
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
|
|
1220
|
+
const resolved = resolve4(expandHome(normalized));
|
|
1221
|
+
const config = loadConfig();
|
|
1222
|
+
if (existsSync4(resolved)) {
|
|
1223
|
+
if (opts.link) {
|
|
1224
|
+
const probe = await installPluginFromPath({ path: resolved, dryRun: true });
|
|
1225
|
+
if (!probe.ok) {
|
|
1226
|
+
console.error(probe.error);
|
|
1227
|
+
process.exit(1);
|
|
1228
|
+
}
|
|
1229
|
+
let next3 = addPluginLoadPath(config, resolved);
|
|
1230
|
+
next3 = enablePluginInConfig(next3, probe.pluginId);
|
|
1231
|
+
next3 = recordPluginInstall(next3, {
|
|
1232
|
+
pluginId: probe.pluginId,
|
|
1233
|
+
source: "path",
|
|
1234
|
+
sourcePath: resolved,
|
|
1235
|
+
installPath: resolved,
|
|
1236
|
+
version: probe.version
|
|
1237
|
+
});
|
|
1238
|
+
saveConfig(next3);
|
|
1239
|
+
console.log(`Linked plugin path: ${resolved}`);
|
|
1240
|
+
console.log("Restart the gateway to load plugins.");
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const result2 = await installPluginFromPath({
|
|
1244
|
+
path: resolved,
|
|
1245
|
+
logger: {
|
|
1246
|
+
info: (message) => console.log(message),
|
|
1247
|
+
warn: (message) => console.warn(message)
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
if (!result2.ok) {
|
|
1251
|
+
console.error(result2.error);
|
|
1252
|
+
process.exit(1);
|
|
1253
|
+
}
|
|
1254
|
+
let next2 = enablePluginInConfig(config, result2.pluginId);
|
|
1255
|
+
next2 = recordPluginInstall(next2, {
|
|
1256
|
+
pluginId: result2.pluginId,
|
|
1257
|
+
source: this.isArchivePath(resolved) ? "archive" : "path",
|
|
1258
|
+
sourcePath: resolved,
|
|
1259
|
+
installPath: result2.targetDir,
|
|
1260
|
+
version: result2.version
|
|
1261
|
+
});
|
|
1262
|
+
saveConfig(next2);
|
|
1263
|
+
console.log(`Installed plugin: ${result2.pluginId}`);
|
|
1264
|
+
console.log("Restart the gateway to load plugins.");
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if (opts.link) {
|
|
1268
|
+
console.error("`--link` requires a local path.");
|
|
1269
|
+
process.exit(1);
|
|
1270
|
+
}
|
|
1271
|
+
if (this.looksLikePath(pathOrSpec)) {
|
|
1272
|
+
console.error(`Path not found: ${resolved}`);
|
|
1273
|
+
process.exit(1);
|
|
1274
|
+
}
|
|
1275
|
+
const result = await installPluginFromNpmSpec({
|
|
1276
|
+
spec: pathOrSpec,
|
|
1277
|
+
logger: {
|
|
1278
|
+
info: (message) => console.log(message),
|
|
1279
|
+
warn: (message) => console.warn(message)
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
if (!result.ok) {
|
|
1283
|
+
console.error(result.error);
|
|
1284
|
+
process.exit(1);
|
|
1285
|
+
}
|
|
1286
|
+
let next = enablePluginInConfig(config, result.pluginId);
|
|
1287
|
+
next = recordPluginInstall(next, {
|
|
1288
|
+
pluginId: result.pluginId,
|
|
1289
|
+
source: "npm",
|
|
1290
|
+
spec: pathOrSpec,
|
|
1291
|
+
installPath: result.targetDir,
|
|
1292
|
+
version: result.version
|
|
1293
|
+
});
|
|
1294
|
+
saveConfig(next);
|
|
1295
|
+
console.log(`Installed plugin: ${result.pluginId}`);
|
|
1296
|
+
console.log("Restart the gateway to load plugins.");
|
|
1297
|
+
}
|
|
1298
|
+
pluginsDoctor() {
|
|
1299
|
+
const config = loadConfig();
|
|
1300
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1301
|
+
const report = buildPluginStatusReport({
|
|
1302
|
+
config,
|
|
1303
|
+
workspaceDir,
|
|
1304
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1305
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1306
|
+
});
|
|
1307
|
+
const pluginErrors = report.plugins.filter((plugin) => plugin.status === "error");
|
|
1308
|
+
const diagnostics = report.diagnostics.filter((diag) => diag.level === "error");
|
|
1309
|
+
if (pluginErrors.length === 0 && diagnostics.length === 0) {
|
|
1310
|
+
console.log("No plugin issues detected.");
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if (pluginErrors.length > 0) {
|
|
1314
|
+
console.log("Plugin errors:");
|
|
1315
|
+
for (const entry of pluginErrors) {
|
|
1316
|
+
console.log(`- ${entry.id}: ${entry.error ?? "failed to load"} (${entry.source})`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (diagnostics.length > 0) {
|
|
1320
|
+
if (pluginErrors.length > 0) {
|
|
1321
|
+
console.log("");
|
|
1322
|
+
}
|
|
1323
|
+
console.log("Diagnostics:");
|
|
1324
|
+
for (const diag of diagnostics) {
|
|
1325
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
1326
|
+
console.log(`- ${prefix}${diag.message}`);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
942
1330
|
async skillsInstall(options) {
|
|
943
1331
|
const workdir = options.workdir ? expandHome(options.workdir) : getWorkspacePath();
|
|
944
1332
|
const result = await installClawHubSkill({
|
|
@@ -970,6 +1358,21 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
970
1358
|
console.log(`Telegram: ${config.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
|
|
971
1359
|
console.log(`Slack: ${config.channels.slack.enabled ? "\u2713" : "\u2717"}`);
|
|
972
1360
|
console.log(`QQ: ${config.channels.qq.enabled ? "\u2713" : "\u2717"}`);
|
|
1361
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1362
|
+
const report = buildPluginStatusReport({
|
|
1363
|
+
config,
|
|
1364
|
+
workspaceDir,
|
|
1365
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1366
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1367
|
+
});
|
|
1368
|
+
const pluginChannels = report.plugins.filter((plugin) => plugin.status === "loaded" && plugin.channelIds.length > 0);
|
|
1369
|
+
if (pluginChannels.length > 0) {
|
|
1370
|
+
console.log("Plugin Channels:");
|
|
1371
|
+
for (const plugin of pluginChannels) {
|
|
1372
|
+
const channels2 = plugin.channelIds.join(", ");
|
|
1373
|
+
console.log(`- ${channels2} (plugin: ${plugin.id})`);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
973
1376
|
}
|
|
974
1377
|
channelsLogin() {
|
|
975
1378
|
const bridgeDir = this.getBridgeDir();
|
|
@@ -980,6 +1383,95 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
980
1383
|
console.error(`Bridge failed: ${result.status ?? 1}`);
|
|
981
1384
|
}
|
|
982
1385
|
}
|
|
1386
|
+
channelsAdd(opts) {
|
|
1387
|
+
const channelId = opts.channel?.trim();
|
|
1388
|
+
if (!channelId) {
|
|
1389
|
+
console.error("--channel is required");
|
|
1390
|
+
process.exit(1);
|
|
1391
|
+
}
|
|
1392
|
+
const config = loadConfig();
|
|
1393
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1394
|
+
const pluginRegistry = this.loadPluginRegistry(config, workspaceDir);
|
|
1395
|
+
const bindings = getPluginChannelBindings(pluginRegistry);
|
|
1396
|
+
const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
|
|
1397
|
+
if (!binding) {
|
|
1398
|
+
console.error(`No plugin channel found for: ${channelId}`);
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
const setup = binding.channel.setup;
|
|
1402
|
+
if (!setup?.applyAccountConfig) {
|
|
1403
|
+
console.error(`Channel "${binding.channelId}" does not support setup.`);
|
|
1404
|
+
process.exit(1);
|
|
1405
|
+
}
|
|
1406
|
+
const input = {
|
|
1407
|
+
name: opts.name,
|
|
1408
|
+
token: opts.token,
|
|
1409
|
+
code: opts.code,
|
|
1410
|
+
url: opts.url,
|
|
1411
|
+
httpUrl: opts.httpUrl
|
|
1412
|
+
};
|
|
1413
|
+
const currentView = this.toPluginConfigView(config, bindings);
|
|
1414
|
+
const accountId = binding.channel.config?.defaultAccountId?.(currentView) ?? "default";
|
|
1415
|
+
const validateError = setup.validateInput?.({
|
|
1416
|
+
cfg: currentView,
|
|
1417
|
+
input,
|
|
1418
|
+
accountId
|
|
1419
|
+
});
|
|
1420
|
+
if (validateError) {
|
|
1421
|
+
console.error(`Channel setup validation failed: ${validateError}`);
|
|
1422
|
+
process.exit(1);
|
|
1423
|
+
}
|
|
1424
|
+
const nextView = setup.applyAccountConfig({
|
|
1425
|
+
cfg: currentView,
|
|
1426
|
+
input,
|
|
1427
|
+
accountId
|
|
1428
|
+
});
|
|
1429
|
+
if (!nextView || typeof nextView !== "object" || Array.isArray(nextView)) {
|
|
1430
|
+
console.error("Channel setup returned invalid config payload.");
|
|
1431
|
+
process.exit(1);
|
|
1432
|
+
}
|
|
1433
|
+
let next = this.mergePluginConfigView(config, nextView, bindings);
|
|
1434
|
+
next = enablePluginInConfig(next, binding.pluginId);
|
|
1435
|
+
saveConfig(next);
|
|
1436
|
+
console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
|
|
1437
|
+
console.log("Restart the gateway to apply changes.");
|
|
1438
|
+
}
|
|
1439
|
+
toPluginConfigView(config, bindings) {
|
|
1440
|
+
const view = JSON.parse(JSON.stringify(config));
|
|
1441
|
+
const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
|
|
1442
|
+
for (const binding of bindings) {
|
|
1443
|
+
const pluginConfig = config.plugins.entries?.[binding.pluginId]?.config;
|
|
1444
|
+
if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
channels2[binding.channelId] = JSON.parse(JSON.stringify(pluginConfig));
|
|
1448
|
+
}
|
|
1449
|
+
view.channels = channels2;
|
|
1450
|
+
return view;
|
|
1451
|
+
}
|
|
1452
|
+
mergePluginConfigView(baseConfig, pluginViewConfig, bindings) {
|
|
1453
|
+
const next = JSON.parse(JSON.stringify(baseConfig));
|
|
1454
|
+
const pluginChannels = pluginViewConfig.channels && typeof pluginViewConfig.channels === "object" && !Array.isArray(pluginViewConfig.channels) ? pluginViewConfig.channels : {};
|
|
1455
|
+
const entries = { ...next.plugins.entries ?? {} };
|
|
1456
|
+
for (const binding of bindings) {
|
|
1457
|
+
if (!Object.prototype.hasOwnProperty.call(pluginChannels, binding.channelId)) {
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
const channelConfig = pluginChannels[binding.channelId];
|
|
1461
|
+
if (!channelConfig || typeof channelConfig !== "object" || Array.isArray(channelConfig)) {
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
entries[binding.pluginId] = {
|
|
1465
|
+
...entries[binding.pluginId] ?? {},
|
|
1466
|
+
config: channelConfig
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
next.plugins = {
|
|
1470
|
+
...next.plugins,
|
|
1471
|
+
entries
|
|
1472
|
+
};
|
|
1473
|
+
return next;
|
|
1474
|
+
}
|
|
983
1475
|
cronList(opts) {
|
|
984
1476
|
const storePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
985
1477
|
const service = new CronService(storePath);
|
|
@@ -1071,14 +1563,85 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1071
1563
|
}
|
|
1072
1564
|
}
|
|
1073
1565
|
}
|
|
1566
|
+
loadPluginRegistry(config, workspaceDir) {
|
|
1567
|
+
return loadOpenClawPlugins({
|
|
1568
|
+
config,
|
|
1569
|
+
workspaceDir,
|
|
1570
|
+
reservedToolNames: [
|
|
1571
|
+
"read_file",
|
|
1572
|
+
"write_file",
|
|
1573
|
+
"edit_file",
|
|
1574
|
+
"list_dir",
|
|
1575
|
+
"exec",
|
|
1576
|
+
"web_search",
|
|
1577
|
+
"web_fetch",
|
|
1578
|
+
"message",
|
|
1579
|
+
"spawn",
|
|
1580
|
+
"sessions_list",
|
|
1581
|
+
"sessions_history",
|
|
1582
|
+
"sessions_send",
|
|
1583
|
+
"memory_search",
|
|
1584
|
+
"memory_get",
|
|
1585
|
+
"subagents",
|
|
1586
|
+
"gateway",
|
|
1587
|
+
"cron"
|
|
1588
|
+
],
|
|
1589
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1590
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name),
|
|
1591
|
+
logger: {
|
|
1592
|
+
info: (message) => console.log(message),
|
|
1593
|
+
warn: (message) => console.warn(message),
|
|
1594
|
+
error: (message) => console.error(message),
|
|
1595
|
+
debug: (message) => console.debug(message)
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
toExtensionRegistry(pluginRegistry) {
|
|
1600
|
+
return {
|
|
1601
|
+
tools: pluginRegistry.tools.map((tool) => ({
|
|
1602
|
+
extensionId: tool.pluginId,
|
|
1603
|
+
factory: tool.factory,
|
|
1604
|
+
names: tool.names,
|
|
1605
|
+
optional: tool.optional,
|
|
1606
|
+
source: tool.source
|
|
1607
|
+
})),
|
|
1608
|
+
channels: pluginRegistry.channels.map((channel) => ({
|
|
1609
|
+
extensionId: channel.pluginId,
|
|
1610
|
+
channel: channel.channel,
|
|
1611
|
+
source: channel.source
|
|
1612
|
+
})),
|
|
1613
|
+
diagnostics: pluginRegistry.diagnostics.map((diag) => ({
|
|
1614
|
+
level: diag.level,
|
|
1615
|
+
message: diag.message,
|
|
1616
|
+
extensionId: diag.pluginId,
|
|
1617
|
+
source: diag.source
|
|
1618
|
+
}))
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
logPluginDiagnostics(registry) {
|
|
1622
|
+
for (const diag of registry.diagnostics) {
|
|
1623
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
1624
|
+
const text = `${prefix}${diag.message}`;
|
|
1625
|
+
if (diag.level === "error") {
|
|
1626
|
+
console.error(`[plugins] ${text}`);
|
|
1627
|
+
} else {
|
|
1628
|
+
console.warn(`[plugins] ${text}`);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1074
1632
|
async startGateway(options = {}) {
|
|
1075
1633
|
const config = loadConfig();
|
|
1634
|
+
const workspace = getWorkspacePath(config.agents.defaults.workspace);
|
|
1635
|
+
const pluginRegistry = this.loadPluginRegistry(config, workspace);
|
|
1636
|
+
const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
|
|
1637
|
+
this.logPluginDiagnostics(pluginRegistry);
|
|
1076
1638
|
const bus = new MessageBus();
|
|
1077
1639
|
const provider = options.allowMissingProvider === true ? this.makeProvider(config, { allowMissing: true }) : this.makeProvider(config);
|
|
1078
1640
|
const providerManager = provider ? new ProviderManager(provider) : null;
|
|
1079
|
-
const sessionManager = new SessionManager(
|
|
1641
|
+
const sessionManager = new SessionManager(workspace);
|
|
1080
1642
|
const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
1081
1643
|
const cron2 = new CronService(cronStorePath);
|
|
1644
|
+
const pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
|
|
1082
1645
|
const uiConfig = resolveUiConfig(config, options.uiOverrides);
|
|
1083
1646
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
1084
1647
|
if (!provider) {
|
|
@@ -1088,7 +1651,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1088
1651
|
});
|
|
1089
1652
|
return;
|
|
1090
1653
|
}
|
|
1091
|
-
const channels2 = new ChannelManager(config, bus, sessionManager);
|
|
1654
|
+
const channels2 = new ChannelManager(config, bus, sessionManager, extensionRegistry.channels);
|
|
1092
1655
|
const reloader = new ConfigReloader({
|
|
1093
1656
|
initialConfig: config,
|
|
1094
1657
|
channels: channels2,
|
|
@@ -1097,6 +1660,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1097
1660
|
providerManager,
|
|
1098
1661
|
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }),
|
|
1099
1662
|
loadConfig,
|
|
1663
|
+
getExtensionChannels: () => extensionRegistry.channels,
|
|
1100
1664
|
onRestartRequired: (paths) => {
|
|
1101
1665
|
console.warn(`Config changes require restart: ${paths.join(", ")}`);
|
|
1102
1666
|
}
|
|
@@ -1105,12 +1669,13 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1105
1669
|
reloader,
|
|
1106
1670
|
cron: cron2,
|
|
1107
1671
|
getConfigPath,
|
|
1108
|
-
saveConfig
|
|
1672
|
+
saveConfig,
|
|
1673
|
+
getPluginUiMetadata: () => pluginUiMetadata
|
|
1109
1674
|
});
|
|
1110
1675
|
const agent = new AgentLoop({
|
|
1111
1676
|
bus,
|
|
1112
1677
|
providerManager: providerManager ?? new ProviderManager(provider),
|
|
1113
|
-
workspace
|
|
1678
|
+
workspace,
|
|
1114
1679
|
model: config.agents.defaults.model,
|
|
1115
1680
|
maxIterations: config.agents.defaults.maxToolIterations,
|
|
1116
1681
|
braveApiKey: config.tools.web.search.apiKey || void 0,
|
|
@@ -1119,7 +1684,54 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1119
1684
|
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
1120
1685
|
sessionManager,
|
|
1121
1686
|
contextConfig: config.agents.context,
|
|
1122
|
-
gatewayController
|
|
1687
|
+
gatewayController,
|
|
1688
|
+
config,
|
|
1689
|
+
extensionRegistry,
|
|
1690
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
1691
|
+
registry: pluginRegistry,
|
|
1692
|
+
channel,
|
|
1693
|
+
cfg: loadConfig(),
|
|
1694
|
+
accountId
|
|
1695
|
+
})
|
|
1696
|
+
});
|
|
1697
|
+
const pluginChannelBindings = getPluginChannelBindings(pluginRegistry);
|
|
1698
|
+
setPluginRuntimeBridge({
|
|
1699
|
+
loadConfig: () => this.toPluginConfigView(loadConfig(), pluginChannelBindings),
|
|
1700
|
+
writeConfigFile: async (nextConfigView) => {
|
|
1701
|
+
if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
|
|
1702
|
+
throw new Error("plugin runtime writeConfigFile expects an object config");
|
|
1703
|
+
}
|
|
1704
|
+
const current = loadConfig();
|
|
1705
|
+
const next = this.mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
|
|
1706
|
+
saveConfig(next);
|
|
1707
|
+
},
|
|
1708
|
+
dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
|
|
1709
|
+
const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
|
|
1710
|
+
const body = typeof ctx.Body === "string" ? ctx.Body : "";
|
|
1711
|
+
const content = (bodyForAgent || body).trim();
|
|
1712
|
+
if (!content) {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
const sessionKey = typeof ctx.SessionKey === "string" && ctx.SessionKey.trim().length > 0 ? ctx.SessionKey : `plugin:${typeof ctx.OriginatingChannel === "string" ? ctx.OriginatingChannel : "channel"}:${typeof ctx.SenderId === "string" ? ctx.SenderId : "unknown"}`;
|
|
1716
|
+
const channel = typeof ctx.OriginatingChannel === "string" && ctx.OriginatingChannel.trim().length > 0 ? ctx.OriginatingChannel : "cli";
|
|
1717
|
+
const chatId = typeof ctx.OriginatingTo === "string" && ctx.OriginatingTo.trim().length > 0 ? ctx.OriginatingTo : typeof ctx.SenderId === "string" && ctx.SenderId.trim().length > 0 ? ctx.SenderId : "direct";
|
|
1718
|
+
try {
|
|
1719
|
+
const response = await agent.processDirect({
|
|
1720
|
+
content,
|
|
1721
|
+
sessionKey,
|
|
1722
|
+
channel,
|
|
1723
|
+
chatId,
|
|
1724
|
+
metadata: typeof ctx.AccountId === "string" && ctx.AccountId.trim().length > 0 ? { account_id: ctx.AccountId } : {}
|
|
1725
|
+
});
|
|
1726
|
+
const replyText = typeof response === "string" ? response : String(response ?? "");
|
|
1727
|
+
if (replyText.trim()) {
|
|
1728
|
+
await dispatcherOptions.deliver({ text: replyText }, { kind: "final" });
|
|
1729
|
+
}
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
dispatcherOptions.onError?.(error);
|
|
1732
|
+
throw error;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1123
1735
|
});
|
|
1124
1736
|
cron2.onJob = async (job) => {
|
|
1125
1737
|
const response = await agent.processDirect({
|
|
@@ -1140,7 +1752,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1140
1752
|
return response;
|
|
1141
1753
|
};
|
|
1142
1754
|
const heartbeat = new HeartbeatService(
|
|
1143
|
-
|
|
1755
|
+
workspace,
|
|
1144
1756
|
async (promptText) => agent.processDirect({ content: promptText, sessionKey: "heartbeat" }),
|
|
1145
1757
|
30 * 60,
|
|
1146
1758
|
true
|
|
@@ -1166,7 +1778,32 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1166
1778
|
watcher.on("unlink", () => reloader.scheduleReload("config unlink"));
|
|
1167
1779
|
await cron2.start();
|
|
1168
1780
|
await heartbeat.start();
|
|
1169
|
-
|
|
1781
|
+
let pluginGatewayHandles = [];
|
|
1782
|
+
try {
|
|
1783
|
+
const startedPluginGateways = await startPluginChannelGateways({
|
|
1784
|
+
registry: pluginRegistry,
|
|
1785
|
+
logger: {
|
|
1786
|
+
info: (message) => console.log(`[plugins] ${message}`),
|
|
1787
|
+
warn: (message) => console.warn(`[plugins] ${message}`),
|
|
1788
|
+
error: (message) => console.error(`[plugins] ${message}`),
|
|
1789
|
+
debug: (message) => console.debug(`[plugins] ${message}`)
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
pluginGatewayHandles = startedPluginGateways.handles;
|
|
1793
|
+
for (const diag of startedPluginGateways.diagnostics) {
|
|
1794
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
1795
|
+
const text = `${prefix}${diag.message}`;
|
|
1796
|
+
if (diag.level === "error") {
|
|
1797
|
+
console.error(`[plugins] ${text}`);
|
|
1798
|
+
} else {
|
|
1799
|
+
console.warn(`[plugins] ${text}`);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
await Promise.allSettled([agent.run(), reloader.getChannels().startAll()]);
|
|
1803
|
+
} finally {
|
|
1804
|
+
await stopPluginChannelGateways(pluginGatewayHandles);
|
|
1805
|
+
setPluginRuntimeBridge(null);
|
|
1806
|
+
}
|
|
1170
1807
|
}
|
|
1171
1808
|
startUiIfEnabled(uiConfig, uiStaticDir) {
|
|
1172
1809
|
if (!uiConfig.enabled) {
|
|
@@ -1310,6 +1947,18 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1310
1947
|
clearServiceState();
|
|
1311
1948
|
console.log(`\u2713 ${APP_NAME} stopped`);
|
|
1312
1949
|
}
|
|
1950
|
+
async confirmYesNo(question) {
|
|
1951
|
+
const rl = createInterface({
|
|
1952
|
+
input: process.stdin,
|
|
1953
|
+
output: process.stdout
|
|
1954
|
+
});
|
|
1955
|
+
const answer = await new Promise((resolve5) => {
|
|
1956
|
+
rl.question(`${question} [y/N] `, (line) => resolve5(line));
|
|
1957
|
+
});
|
|
1958
|
+
rl.close();
|
|
1959
|
+
const normalized = answer.trim().toLowerCase();
|
|
1960
|
+
return normalized === "y" || normalized === "yes";
|
|
1961
|
+
}
|
|
1313
1962
|
makeProvider(config, options) {
|
|
1314
1963
|
const provider = getProvider(config);
|
|
1315
1964
|
const model = config.agents.defaults.model;
|
|
@@ -1330,6 +1979,36 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1330
1979
|
wireApi: provider?.wireApi ?? null
|
|
1331
1980
|
});
|
|
1332
1981
|
}
|
|
1982
|
+
resolveFileNpmSpecToLocalPath(raw) {
|
|
1983
|
+
const trimmed = raw.trim();
|
|
1984
|
+
if (!trimmed.toLowerCase().startsWith("file:")) {
|
|
1985
|
+
return null;
|
|
1986
|
+
}
|
|
1987
|
+
const rest = trimmed.slice("file:".length);
|
|
1988
|
+
if (!rest) {
|
|
1989
|
+
return { ok: false, error: "unsupported file: spec: missing path" };
|
|
1990
|
+
}
|
|
1991
|
+
if (rest.startsWith("///")) {
|
|
1992
|
+
return { ok: true, path: rest.slice(2) };
|
|
1993
|
+
}
|
|
1994
|
+
if (rest.startsWith("//localhost/")) {
|
|
1995
|
+
return { ok: true, path: rest.slice("//localhost".length) };
|
|
1996
|
+
}
|
|
1997
|
+
if (rest.startsWith("//")) {
|
|
1998
|
+
return {
|
|
1999
|
+
ok: false,
|
|
2000
|
+
error: 'unsupported file: URL host (expected "file:<path>" or "file:///abs/path")'
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
return { ok: true, path: rest };
|
|
2004
|
+
}
|
|
2005
|
+
looksLikePath(raw) {
|
|
2006
|
+
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");
|
|
2007
|
+
}
|
|
2008
|
+
isArchivePath(filePath) {
|
|
2009
|
+
const lower = filePath.toLowerCase();
|
|
2010
|
+
return lower.endsWith(".zip") || lower.endsWith(".tgz") || lower.endsWith(".tar.gz") || lower.endsWith(".tar");
|
|
2011
|
+
}
|
|
1333
2012
|
createWorkspaceTemplates(workspace, options = {}) {
|
|
1334
2013
|
const created = [];
|
|
1335
2014
|
const force = Boolean(options.force);
|
|
@@ -1413,7 +2092,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1413
2092
|
resolveBuiltinSkillsDir() {
|
|
1414
2093
|
try {
|
|
1415
2094
|
const require2 = createRequire(import.meta.url);
|
|
1416
|
-
const entry = require2.resolve("nextclaw
|
|
2095
|
+
const entry = require2.resolve("@nextclaw/core");
|
|
1417
2096
|
const pkgRoot = resolve4(dirname(entry), "..");
|
|
1418
2097
|
const distSkills = join3(pkgRoot, "dist", "skills");
|
|
1419
2098
|
if (existsSync4(distSkills)) {
|
|
@@ -1516,7 +2195,16 @@ var skills = program.command("skills").description("Manage skills");
|
|
|
1516
2195
|
registerClawHubInstall(skills);
|
|
1517
2196
|
var clawhub = program.command("clawhub").description("Install skills from ClawHub");
|
|
1518
2197
|
registerClawHubInstall(clawhub);
|
|
2198
|
+
var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
|
|
2199
|
+
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));
|
|
2200
|
+
plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));
|
|
2201
|
+
plugins.command("enable <id>").description("Enable a plugin in config").action((id) => runtime.pluginsEnable(id));
|
|
2202
|
+
plugins.command("disable <id>").description("Disable a plugin in config").action((id) => runtime.pluginsDisable(id));
|
|
2203
|
+
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));
|
|
2204
|
+
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));
|
|
2205
|
+
plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
|
|
1519
2206
|
var channels = program.command("channels").description("Manage channels");
|
|
2207
|
+
channels.command("add").description("Configure a plugin channel (OpenClaw-compatible setup)").requiredOption("--channel <id>", "Plugin channel id").option("--code <code>", "Pairing code").option("--token <token>", "Connector token").option("--name <name>", "Display name").option("--url <url>", "API base URL").option("--http-url <url>", "Alias for --url").action((opts) => runtime.channelsAdd(opts));
|
|
1520
2208
|
channels.command("status").description("Show channel status").action(() => runtime.channelsStatus());
|
|
1521
2209
|
channels.command("login").description("Link device via QR code").action(() => runtime.channelsLogin());
|
|
1522
2210
|
var cron = program.command("cron").description("Manage scheduled tasks");
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from 'nextclaw
|
|
1
|
+
export * from '@nextclaw/core';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
export * from "nextclaw
|
|
2
|
+
export * from "@nextclaw/core";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextclaw",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"chokidar": "^3.6.0",
|
|
40
40
|
"commander": "^12.1.0",
|
|
41
|
-
"nextclaw
|
|
42
|
-
"nextclaw
|
|
41
|
+
"@nextclaw/core": "^0.4.9",
|
|
42
|
+
"@nextclaw/server": "^0.3.5",
|
|
43
|
+
"@nextclaw/openclaw-compat": "^0.1.2"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@types/node": "^20.17.6",
|
|
@@ -55,8 +56,8 @@
|
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
58
|
"dev": "tsx watch --tsconfig tsconfig.json src/cli/index.ts",
|
|
58
|
-
"dev:build": "pnpm -C ../nextclaw-core build && pnpm -C ../nextclaw-server build && tsx src/cli/index.ts",
|
|
59
|
-
"build": "pnpm -C ../nextclaw-core build && pnpm -C ../nextclaw-server build && tsup src/index.ts src/cli/index.ts --format esm --dts --out-dir dist && node scripts/copy-ui-dist.mjs",
|
|
59
|
+
"dev:build": "pnpm -C ../nextclaw-core build && pnpm -C ../nextclaw-openclaw-compat build && pnpm -C ../nextclaw-server build && tsx src/cli/index.ts",
|
|
60
|
+
"build": "pnpm -C ../nextclaw-core build && pnpm -C ../nextclaw-openclaw-compat build && pnpm -C ../nextclaw-server build && tsup src/index.ts src/cli/index.ts --format esm --dts --out-dir dist && node scripts/copy-ui-dist.mjs",
|
|
60
61
|
"start": "node dist/cli.js",
|
|
61
62
|
"lint": "eslint .",
|
|
62
63
|
"tsc": "tsc -p tsconfig.json",
|