nextclaw 0.6.21 → 0.6.23

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 CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getConfigPath as getConfigPath3,
12
12
  getDataDir as getDataDir7,
13
13
  ConfigSchema as ConfigSchema2,
14
- getWorkspacePath as getWorkspacePath5,
14
+ getWorkspacePath as getWorkspacePath6,
15
15
  expandHome as expandHome2,
16
16
  MessageBus as MessageBus2,
17
17
  AgentLoop as AgentLoop2,
@@ -1336,6 +1336,7 @@ var CHANNEL_LABELS = {
1336
1336
  feishu: "Feishu",
1337
1337
  mochat: "Mochat",
1338
1338
  dingtalk: "DingTalk",
1339
+ wecom: "WeCom",
1339
1340
  email: "Email",
1340
1341
  slack: "Slack",
1341
1342
  qq: "QQ"
@@ -1849,7 +1850,6 @@ var DiagnosticsCommands = class {
1849
1850
  // src/cli/commands/service.ts
1850
1851
  import {
1851
1852
  APP_NAME as APP_NAME2,
1852
- AgentLoop,
1853
1853
  ChannelManager as ChannelManager2,
1854
1854
  CronService as CronService2,
1855
1855
  getApiBase,
@@ -1857,14 +1857,15 @@ import {
1857
1857
  getDataDir as getDataDir5,
1858
1858
  getProvider,
1859
1859
  getProviderName,
1860
- getWorkspacePath as getWorkspacePath4,
1860
+ getWorkspacePath as getWorkspacePath5,
1861
1861
  HeartbeatService,
1862
1862
  LiteLLMProvider,
1863
1863
  loadConfig as loadConfig5,
1864
1864
  MessageBus,
1865
1865
  ProviderManager,
1866
1866
  saveConfig as saveConfig4,
1867
- SessionManager
1867
+ SessionManager,
1868
+ parseAgentScopedSessionKey as parseAgentScopedSessionKey2
1868
1869
  } from "@nextclaw/core";
1869
1870
  import {
1870
1871
  getPluginChannelBindings as getPluginChannelBindings2,
@@ -1950,14 +1951,15 @@ var GatewayControllerImpl = class {
1950
1951
  resolveDeliveryContext(sessionKey) {
1951
1952
  const normalizedSessionKey = this.normalizeOptionalString(sessionKey);
1952
1953
  const keyTarget = parseSessionKey(normalizedSessionKey);
1954
+ const keyRoute = keyTarget && keyTarget.channel !== "agent" ? keyTarget : null;
1953
1955
  const session = normalizedSessionKey ? this.deps.sessionManager?.getIfExists(normalizedSessionKey) : null;
1954
1956
  const metadata = session?.metadata ?? {};
1955
1957
  const rawContext = metadata.last_delivery_context;
1956
1958
  const cachedContext = rawContext && typeof rawContext === "object" && !Array.isArray(rawContext) ? rawContext : null;
1957
1959
  const cachedMetadataRaw = cachedContext?.metadata;
1958
1960
  const cachedMetadata = cachedMetadataRaw && typeof cachedMetadataRaw === "object" && !Array.isArray(cachedMetadataRaw) ? { ...cachedMetadataRaw } : {};
1959
- const channel = this.normalizeOptionalString(cachedContext?.channel) ?? keyTarget?.channel;
1960
- const chatId = this.normalizeOptionalString(cachedContext?.chatId) ?? this.normalizeOptionalString(metadata.last_to) ?? keyTarget?.chatId;
1961
+ const channel = this.normalizeOptionalString(cachedContext?.channel) ?? keyRoute?.channel;
1962
+ const chatId = this.normalizeOptionalString(cachedContext?.chatId) ?? this.normalizeOptionalString(metadata.last_to) ?? keyRoute?.chatId;
1961
1963
  const replyTo = this.normalizeOptionalString(cachedContext?.replyTo) ?? this.normalizeOptionalString(metadata.last_message_id);
1962
1964
  const accountId = this.normalizeOptionalString(cachedContext?.accountId) ?? this.normalizeOptionalString(metadata.last_account_id);
1963
1965
  if (!channel || !chatId) {
@@ -2299,6 +2301,175 @@ var MissingProvider = class extends LLMProvider {
2299
2301
  }
2300
2302
  };
2301
2303
 
2304
+ // src/cli/commands/agent-runtime-pool.ts
2305
+ import {
2306
+ AgentLoop,
2307
+ AgentRouteResolver,
2308
+ getWorkspacePath as getWorkspacePath4,
2309
+ parseAgentScopedSessionKey
2310
+ } from "@nextclaw/core";
2311
+ function normalizeAgentId(value) {
2312
+ const text = (value ?? "").trim().toLowerCase();
2313
+ return text || "main";
2314
+ }
2315
+ function resolveAgentProfiles(config2) {
2316
+ const defaults = config2.agents.defaults;
2317
+ const listed = Array.isArray(config2.agents.list) ? config2.agents.list.map((entry) => ({
2318
+ id: normalizeAgentId(entry.id),
2319
+ default: entry.default,
2320
+ workspace: entry.workspace,
2321
+ model: entry.model,
2322
+ maxToolIterations: entry.maxToolIterations,
2323
+ maxTokens: entry.maxTokens
2324
+ })).filter((entry) => Boolean(entry.id)) : [];
2325
+ const defaultAgentId = listed.find((entry) => entry.default)?.id ?? listed[0]?.id ?? "main";
2326
+ const seed = listed.length > 0 ? listed : [{ id: defaultAgentId }];
2327
+ const unique = /* @__PURE__ */ new Map();
2328
+ for (const entry of seed) {
2329
+ if (!unique.has(entry.id)) {
2330
+ unique.set(entry.id, entry);
2331
+ }
2332
+ }
2333
+ if (!unique.has(defaultAgentId)) {
2334
+ unique.set(defaultAgentId, { id: defaultAgentId });
2335
+ }
2336
+ return Array.from(unique.values()).map((entry) => ({
2337
+ id: entry.id,
2338
+ workspace: getWorkspacePath4(entry.workspace ?? defaults.workspace),
2339
+ model: entry.model ?? defaults.model,
2340
+ maxIterations: entry.maxToolIterations ?? defaults.maxToolIterations,
2341
+ maxTokens: entry.maxTokens ?? defaults.maxTokens
2342
+ }));
2343
+ }
2344
+ var GatewayAgentRuntimePool = class {
2345
+ constructor(options) {
2346
+ this.options = options;
2347
+ this.routeResolver = new AgentRouteResolver(options.config);
2348
+ this.rebuild(options.config);
2349
+ }
2350
+ routeResolver;
2351
+ runtimes = /* @__PURE__ */ new Map();
2352
+ running = false;
2353
+ defaultAgentId = "main";
2354
+ get primaryAgentId() {
2355
+ return this.defaultAgentId;
2356
+ }
2357
+ applyRuntimeConfig(config2) {
2358
+ this.options.config = config2;
2359
+ this.options.contextConfig = config2.agents.context;
2360
+ this.options.execConfig = config2.tools.exec;
2361
+ this.options.restrictToWorkspace = config2.tools.restrictToWorkspace;
2362
+ this.options.braveApiKey = config2.tools.web.search.apiKey || void 0;
2363
+ this.routeResolver.updateConfig(config2);
2364
+ this.rebuild(config2);
2365
+ }
2366
+ async processDirect(params) {
2367
+ const message = {
2368
+ channel: params.channel ?? "cli",
2369
+ senderId: "user",
2370
+ chatId: params.chatId ?? "direct",
2371
+ content: params.content,
2372
+ timestamp: /* @__PURE__ */ new Date(),
2373
+ attachments: [],
2374
+ metadata: params.metadata ?? {}
2375
+ };
2376
+ const forcedAgentId = this.readString(params.agentId) ?? parseAgentScopedSessionKey(params.sessionKey)?.agentId ?? void 0;
2377
+ const route = this.routeResolver.resolveInbound({
2378
+ message,
2379
+ forcedAgentId,
2380
+ sessionKeyOverride: params.sessionKey
2381
+ });
2382
+ const runtime2 = this.resolveRuntime(route.agentId);
2383
+ return runtime2.loop.processDirect({
2384
+ content: params.content,
2385
+ sessionKey: route.sessionKey,
2386
+ channel: message.channel,
2387
+ chatId: message.chatId,
2388
+ metadata: message.metadata
2389
+ });
2390
+ }
2391
+ async run() {
2392
+ this.running = true;
2393
+ while (this.running) {
2394
+ const message = await this.options.bus.consumeInbound();
2395
+ try {
2396
+ const explicitSessionKey = this.readString(message.metadata.session_key_override);
2397
+ const forcedAgentId = this.readString(message.metadata.target_agent_id);
2398
+ const route = this.routeResolver.resolveInbound({
2399
+ message,
2400
+ forcedAgentId,
2401
+ sessionKeyOverride: explicitSessionKey
2402
+ });
2403
+ const runtime2 = this.resolveRuntime(route.agentId);
2404
+ await runtime2.loop.handleInbound({
2405
+ message,
2406
+ sessionKey: route.sessionKey,
2407
+ publishResponse: true
2408
+ });
2409
+ } catch (error) {
2410
+ await this.options.bus.publishOutbound({
2411
+ channel: message.channel,
2412
+ chatId: message.chatId,
2413
+ content: `Sorry, I encountered an error: ${String(error)}`,
2414
+ media: [],
2415
+ metadata: {}
2416
+ });
2417
+ }
2418
+ }
2419
+ }
2420
+ readString(value) {
2421
+ if (typeof value !== "string") {
2422
+ return void 0;
2423
+ }
2424
+ const trimmed = value.trim();
2425
+ return trimmed || void 0;
2426
+ }
2427
+ resolveRuntime(agentId) {
2428
+ const normalized = normalizeAgentId(agentId);
2429
+ const runtime2 = this.runtimes.get(normalized);
2430
+ if (runtime2) {
2431
+ return runtime2;
2432
+ }
2433
+ const fallback = this.runtimes.get(this.defaultAgentId);
2434
+ if (fallback) {
2435
+ return fallback;
2436
+ }
2437
+ throw new Error("No agent runtime available");
2438
+ }
2439
+ rebuild(config2) {
2440
+ const profiles = resolveAgentProfiles(config2);
2441
+ const configuredDefault = this.readString(config2.agents.list.find((entry) => entry.default)?.id);
2442
+ this.defaultAgentId = configuredDefault ?? profiles[0]?.id ?? "main";
2443
+ const nextRuntimes = /* @__PURE__ */ new Map();
2444
+ for (const profile of profiles) {
2445
+ const loop = new AgentLoop({
2446
+ bus: this.options.bus,
2447
+ providerManager: this.options.providerManager,
2448
+ workspace: profile.workspace,
2449
+ model: profile.model,
2450
+ maxIterations: profile.maxIterations,
2451
+ maxTokens: profile.maxTokens,
2452
+ braveApiKey: this.options.braveApiKey,
2453
+ execConfig: this.options.execConfig,
2454
+ cronService: this.options.cronService,
2455
+ restrictToWorkspace: this.options.restrictToWorkspace,
2456
+ sessionManager: this.options.sessionManager,
2457
+ contextConfig: this.options.contextConfig,
2458
+ gatewayController: this.options.gatewayController,
2459
+ config: config2,
2460
+ extensionRegistry: this.options.extensionRegistry,
2461
+ resolveMessageToolHints: this.options.resolveMessageToolHints,
2462
+ agentId: profile.id
2463
+ });
2464
+ nextRuntimes.set(profile.id, {
2465
+ id: profile.id,
2466
+ loop
2467
+ });
2468
+ }
2469
+ this.runtimes = nextRuntimes;
2470
+ }
2471
+ };
2472
+
2302
2473
  // src/cli/commands/service.ts
2303
2474
  var ServiceCommands = class {
2304
2475
  constructor(deps) {
@@ -2306,7 +2477,7 @@ var ServiceCommands = class {
2306
2477
  }
2307
2478
  async startGateway(options = {}) {
2308
2479
  const config2 = loadConfig5();
2309
- const workspace = getWorkspacePath4(config2.agents.defaults.workspace);
2480
+ const workspace = getWorkspacePath5(config2.agents.defaults.workspace);
2310
2481
  const pluginRegistry = loadPluginRegistry(config2, workspace);
2311
2482
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
2312
2483
  logPluginDiagnostics(pluginRegistry);
@@ -2358,21 +2529,17 @@ var ServiceCommands = class {
2358
2529
  });
2359
2530
  }
2360
2531
  });
2361
- const agent = new AgentLoop({
2532
+ const runtimePool = new GatewayAgentRuntimePool({
2362
2533
  bus,
2363
2534
  providerManager,
2364
- workspace,
2365
- model: config2.agents.defaults.model,
2366
- maxIterations: config2.agents.defaults.maxToolIterations,
2367
- maxTokens: config2.agents.defaults.maxTokens,
2368
- braveApiKey: config2.tools.web.search.apiKey || void 0,
2369
- execConfig: config2.tools.exec,
2535
+ sessionManager,
2536
+ config: config2,
2370
2537
  cronService: cron2,
2371
2538
  restrictToWorkspace: config2.tools.restrictToWorkspace,
2372
- sessionManager,
2539
+ braveApiKey: config2.tools.web.search.apiKey || void 0,
2540
+ execConfig: config2.tools.exec,
2373
2541
  contextConfig: config2.agents.context,
2374
2542
  gatewayController,
2375
- config: config2,
2376
2543
  extensionRegistry,
2377
2544
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
2378
2545
  registry: pluginRegistry,
@@ -2381,7 +2548,7 @@ var ServiceCommands = class {
2381
2548
  accountId
2382
2549
  })
2383
2550
  });
2384
- reloader.setApplyAgentRuntimeConfig((nextConfig) => agent.applyRuntimeConfig(nextConfig));
2551
+ reloader.setApplyAgentRuntimeConfig((nextConfig) => runtimePool.applyRuntimeConfig(nextConfig));
2385
2552
  const pluginChannelBindings = getPluginChannelBindings2(pluginRegistry);
2386
2553
  setPluginRuntimeBridge({
2387
2554
  loadConfig: () => toPluginConfigView(loadConfig5(), pluginChannelBindings),
@@ -2400,16 +2567,17 @@ var ServiceCommands = class {
2400
2567
  if (!content) {
2401
2568
  return;
2402
2569
  }
2403
- 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"}`;
2570
+ const sessionKey = typeof ctx.SessionKey === "string" && ctx.SessionKey.trim().length > 0 ? ctx.SessionKey : void 0;
2404
2571
  const channel = typeof ctx.OriginatingChannel === "string" && ctx.OriginatingChannel.trim().length > 0 ? ctx.OriginatingChannel : "cli";
2405
2572
  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";
2406
2573
  const modelOverride = typeof ctx.Model === "string" && ctx.Model?.trim().length ? ctx.Model.trim() : typeof ctx.AgentModel === "string" && ctx.AgentModel?.trim().length ? ctx.AgentModel.trim() : void 0;
2407
2574
  try {
2408
- const response = await agent.processDirect({
2575
+ const response = await runtimePool.processDirect({
2409
2576
  content,
2410
2577
  sessionKey,
2411
2578
  channel,
2412
2579
  chatId,
2580
+ agentId: typeof ctx.AgentId === "string" ? ctx.AgentId : void 0,
2413
2581
  metadata: {
2414
2582
  ...typeof ctx.AccountId === "string" && ctx.AccountId.trim().length > 0 ? { account_id: ctx.AccountId } : {},
2415
2583
  ...modelOverride ? { model: modelOverride } : {}
@@ -2426,11 +2594,12 @@ var ServiceCommands = class {
2426
2594
  }
2427
2595
  });
2428
2596
  cron2.onJob = async (job) => {
2429
- const response = await agent.processDirect({
2597
+ const response = await runtimePool.processDirect({
2430
2598
  content: job.payload.message,
2431
2599
  sessionKey: `cron:${job.id}`,
2432
2600
  channel: job.payload.channel ?? "cli",
2433
- chatId: job.payload.to ?? "direct"
2601
+ chatId: job.payload.to ?? "direct",
2602
+ agentId: runtimePool.primaryAgentId
2434
2603
  });
2435
2604
  if (job.payload.deliver && job.payload.to) {
2436
2605
  await bus.publishOutbound({
@@ -2445,7 +2614,7 @@ var ServiceCommands = class {
2445
2614
  };
2446
2615
  const heartbeat = new HeartbeatService(
2447
2616
  workspace,
2448
- async (promptText) => agent.processDirect({ content: promptText, sessionKey: "heartbeat" }),
2617
+ async (promptText) => runtimePool.processDirect({ content: promptText, sessionKey: "heartbeat", agentId: runtimePool.primaryAgentId }),
2449
2618
  30 * 60,
2450
2619
  true
2451
2620
  );
@@ -2493,7 +2662,7 @@ var ServiceCommands = class {
2493
2662
  }
2494
2663
  await reloader.getChannels().startAll();
2495
2664
  await this.wakeFromRestartSentinel({ bus, sessionManager });
2496
- await agent.run();
2665
+ await runtimePool.run();
2497
2666
  } finally {
2498
2667
  await stopPluginChannelGateways(pluginGatewayHandles);
2499
2668
  setPluginRuntimeBridge(null);
@@ -2569,9 +2738,11 @@ var ServiceCommands = class {
2569
2738
  }
2570
2739
  const sessionKey = sentinelSessionKey ?? fallbackSessionKey ?? "cli:default";
2571
2740
  const parsedSession = parseSessionKey(sessionKey);
2741
+ const parsedAgentSession = parseAgentScopedSessionKey2(sessionKey);
2742
+ const parsedSessionRoute = parsedSession && parsedSession.channel !== "agent" ? parsedSession : null;
2572
2743
  const context = payload.deliveryContext;
2573
- const channel = this.normalizeOptionalString(context?.channel) ?? parsedSession?.channel ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_channel);
2574
- const chatId = this.normalizeOptionalString(context?.chatId) ?? parsedSession?.chatId ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_to);
2744
+ const channel = this.normalizeOptionalString(context?.channel) ?? parsedSessionRoute?.channel ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_channel);
2745
+ const chatId = this.normalizeOptionalString(context?.chatId) ?? parsedSessionRoute?.chatId ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_to);
2575
2746
  const replyTo = this.normalizeOptionalString(context?.replyTo);
2576
2747
  const accountId = this.normalizeOptionalString(context?.accountId);
2577
2748
  if (!channel || !chatId) {
@@ -2587,7 +2758,9 @@ var ServiceCommands = class {
2587
2758
  const metadata = {
2588
2759
  source: "restart-sentinel",
2589
2760
  restart_summary: summary,
2761
+ session_key_override: sessionKey,
2590
2762
  ...replyTo ? { reply_to: replyTo } : {},
2763
+ ...parsedAgentSession ? { target_agent_id: parsedAgentSession.agentId } : {},
2591
2764
  ...accountId ? { account_id: accountId, accountId } : {}
2592
2765
  };
2593
2766
  await params.bus.publishInbound({
@@ -3349,7 +3522,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
3349
3522
  }
3350
3523
  async agent(opts) {
3351
3524
  const config2 = loadConfig6();
3352
- const workspace = getWorkspacePath5(config2.agents.defaults.workspace);
3525
+ const workspace = getWorkspacePath6(config2.agents.defaults.workspace);
3353
3526
  const pluginRegistry = loadPluginRegistry(config2, workspace);
3354
3527
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
3355
3528
  logPluginDiagnostics(pluginRegistry);
@@ -3518,7 +3691,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
3518
3691
  await this.diagnosticsCommands.doctor(opts);
3519
3692
  }
3520
3693
  async skillsInstall(options) {
3521
- const workdir = options.workdir ? expandHome2(options.workdir) : getWorkspacePath5();
3694
+ const workdir = options.workdir ? expandHome2(options.workdir) : getWorkspacePath6();
3522
3695
  const result = await installClawHubSkill({
3523
3696
  slug: options.slug,
3524
3697
  version: options.version,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.6.21",
3
+ "version": "0.6.23",
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.6.20",
42
- "@nextclaw/server": "^0.4.5",
43
- "@nextclaw/openclaw-compat": "^0.1.13"
41
+ "@nextclaw/core": "^0.6.22",
42
+ "@nextclaw/server": "^0.4.7",
43
+ "@nextclaw/openclaw-compat": "^0.1.15"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^20.17.6",
@@ -384,6 +384,34 @@ Create an app in the [DingTalk open platform](https://open.dingtalk.com/) and ge
384
384
  }
385
385
  ```
386
386
 
387
+ ### WeCom (Enterprise WeChat)
388
+
389
+ Create an internal app in the [WeCom admin console](https://work.weixin.qq.com/), then collect:
390
+
391
+ - `corpId` (Enterprise ID)
392
+ - `agentId` (application Agent ID)
393
+ - `secret` (application secret)
394
+ - `token` (callback token)
395
+
396
+ Set the callback URL to `http://<your-host>:<callbackPort><callbackPath>` and keep callback mode in plaintext (the runtime currently skips encrypted callback payloads).
397
+
398
+ ```json
399
+ {
400
+ "channels": {
401
+ "wecom": {
402
+ "enabled": true,
403
+ "corpId": "YOUR_CORP_ID",
404
+ "agentId": "1000002",
405
+ "secret": "YOUR_APP_SECRET",
406
+ "token": "YOUR_CALLBACK_TOKEN",
407
+ "callbackPort": 18890,
408
+ "callbackPath": "/wecom/callback",
409
+ "allowFrom": []
410
+ }
411
+ }
412
+ }
413
+ ```
414
+
387
415
  ### WhatsApp
388
416
 
389
417
  WhatsApp typically requires a bridge (e.g. a companion service). Configure the bridge URL and optional allowlist: