nextclaw 0.4.10 → 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.
Files changed (2) hide show
  1. package/dist/cli/index.js +191 -5
  2. package/package.json +4 -4
package/dist/cli/index.js CHANGED
@@ -42,7 +42,12 @@ import {
42
42
  installPluginFromNpmSpec,
43
43
  uninstallPlugin,
44
44
  resolveUninstallDirectoryTarget,
45
- loadPluginUiMetadata
45
+ setPluginRuntimeBridge,
46
+ getPluginChannelBindings,
47
+ getPluginUiMetadataFromRegistry,
48
+ resolvePluginChannelMessageToolHints,
49
+ startPluginChannelGateways,
50
+ stopPluginChannelGateways
46
51
  } from "@nextclaw/openclaw-compat";
47
52
  import { startUiServer } from "@nextclaw/server";
48
53
  import {
@@ -888,7 +893,13 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
888
893
  restrictToWorkspace: config.tools.restrictToWorkspace,
889
894
  contextConfig: config.agents.context,
890
895
  config,
891
- extensionRegistry
896
+ extensionRegistry,
897
+ resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
898
+ registry: pluginRegistry,
899
+ channel,
900
+ cfg: loadConfig(),
901
+ accountId
902
+ })
892
903
  });
893
904
  if (opts.message) {
894
905
  const response = await agentLoop.processDirect({
@@ -1347,6 +1358,21 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1347
1358
  console.log(`Telegram: ${config.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
1348
1359
  console.log(`Slack: ${config.channels.slack.enabled ? "\u2713" : "\u2717"}`);
1349
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
+ }
1350
1376
  }
1351
1377
  channelsLogin() {
1352
1378
  const bridgeDir = this.getBridgeDir();
@@ -1357,6 +1383,95 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1357
1383
  console.error(`Bridge failed: ${result.status ?? 1}`);
1358
1384
  }
1359
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
+ }
1360
1475
  cronList(opts) {
1361
1476
  const storePath = join3(getDataDir2(), "cron", "jobs.json");
1362
1477
  const service = new CronService(storePath);
@@ -1526,7 +1641,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1526
1641
  const sessionManager = new SessionManager(workspace);
1527
1642
  const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
1528
1643
  const cron2 = new CronService(cronStorePath);
1529
- const pluginUiMetadata = loadPluginUiMetadata({ config, workspaceDir: workspace });
1644
+ const pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
1530
1645
  const uiConfig = resolveUiConfig(config, options.uiOverrides);
1531
1646
  const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
1532
1647
  if (!provider) {
@@ -1571,7 +1686,52 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1571
1686
  contextConfig: config.agents.context,
1572
1687
  gatewayController,
1573
1688
  config,
1574
- extensionRegistry
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
+ }
1575
1735
  });
1576
1736
  cron2.onJob = async (job) => {
1577
1737
  const response = await agent.processDirect({
@@ -1618,7 +1778,32 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1618
1778
  watcher.on("unlink", () => reloader.scheduleReload("config unlink"));
1619
1779
  await cron2.start();
1620
1780
  await heartbeat.start();
1621
- await Promise.allSettled([agent.run(), reloader.getChannels().startAll()]);
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
+ }
1622
1807
  }
1623
1808
  startUiIfEnabled(uiConfig, uiStaticDir) {
1624
1809
  if (!uiConfig.enabled) {
@@ -2019,6 +2204,7 @@ plugins.command("uninstall <id>").description("Uninstall a plugin").option("--ke
2019
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));
2020
2205
  plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
2021
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));
2022
2208
  channels.command("status").description("Show channel status").action(() => runtime.channelsStatus());
2023
2209
  channels.command("login").description("Link device via QR code").action(() => runtime.channelsLogin());
2024
2210
  var cron = program.command("cron").description("Manage scheduled tasks");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.4.10",
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,9 +38,9 @@
38
38
  "dependencies": {
39
39
  "chokidar": "^3.6.0",
40
40
  "commander": "^12.1.0",
41
- "@nextclaw/core": "^0.4.8",
42
- "@nextclaw/server": "^0.3.4",
43
- "@nextclaw/openclaw-compat": "^0.1.1"
41
+ "@nextclaw/core": "^0.4.9",
42
+ "@nextclaw/server": "^0.3.5",
43
+ "@nextclaw/openclaw-compat": "^0.1.2"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^20.17.6",