multiclaws 0.4.37 → 0.4.38

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/index.js CHANGED
@@ -4,6 +4,7 @@ const handlers_1 = require("./gateway/handlers");
4
4
  const multiclaws_service_1 = require("./service/multiclaws-service");
5
5
  const logger_1 = require("./infra/logger");
6
6
  const telemetry_1 = require("./infra/telemetry");
7
+ const version_1 = require("./infra/version");
7
8
  /** Default FRP tunnel config for demo/testing */
8
9
  const DEFAULT_TUNNEL = {
9
10
  type: "frp",
@@ -558,7 +559,7 @@ function createTools(getService, logger) {
558
559
  const plugin = {
559
560
  id: "multiclaws",
560
561
  name: "MultiClaws",
561
- version: "0.3.1",
562
+ version: version_1.PLUGIN_VERSION,
562
563
  register(api) {
563
564
  const config = readConfig(api);
564
565
  (0, telemetry_1.initializeTelemetry)({ enableConsoleExporter: config.telemetry?.consoleExporter });
@@ -582,7 +583,7 @@ const plugin = {
582
583
  if (gw) {
583
584
  const tools = (gw.tools ?? {});
584
585
  const allow = Array.isArray(tools.allow) ? tools.allow : [];
585
- const adapterRequired = ["sessions_spawn", "sessions_history", "message", "chat.send"];
586
+ const adapterRequired = ["sessions_spawn", "sessions_list", "sessions_send", "sessions_history", "message", "chat.send"];
586
587
  const defaultA2AExecutionTools = ["exec", "read", "write", "edit", "process"];
587
588
  const pluginConf = api.pluginConfig ?? {};
588
589
  const a2aExecTools = Array.isArray(pluginConf.a2aAllowedTools)
@@ -0,0 +1 @@
1
+ export declare const PLUGIN_VERSION: string;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PLUGIN_VERSION = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ function readPackageVersion() {
10
+ try {
11
+ const pkgPath = node_path_1.default.resolve(__dirname, "..", "..", "package.json");
12
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf-8"));
13
+ return pkg.version ?? "0.0.0";
14
+ }
15
+ catch {
16
+ return "0.0.0";
17
+ }
18
+ }
19
+ exports.PLUGIN_VERSION = readPackageVersion();
@@ -7,6 +7,8 @@ export type A2AAdapterOptions = {
7
7
  taskTracker: TaskTracker;
8
8
  cwd?: string;
9
9
  getNotificationTargets?: () => ReadonlyMap<string, NotificationTarget>;
10
+ /** Called when a session is discovered via fallback; allows service to cache it for future use. */
11
+ registerDiscoveredTarget?: (sessionKey: string) => void;
10
12
  logger: {
11
13
  info: (msg: string) => void;
12
14
  warn: (msg: string) => void;
@@ -30,6 +32,7 @@ export declare class OpenClawAgentExecutor implements AgentExecutor {
30
32
  private gatewayConfig;
31
33
  private readonly taskTracker;
32
34
  private readonly getNotificationTargets;
35
+ private readonly registerDiscoveredTarget;
33
36
  private readonly logger;
34
37
  private readonly cwd;
35
38
  private readonly pendingCallbacks;
@@ -58,6 +61,12 @@ export declare class OpenClawAgentExecutor implements AgentExecutor {
58
61
  * or rejects on timeout or cancellation.
59
62
  */
60
63
  private createApprovalCallback;
64
+ /**
65
+ * Discover the most recently active non-internal session via sessions_list.
66
+ * Used as fallback when no notification targets have been registered yet
67
+ * (e.g. right after a gateway restart before the user sends their first message).
68
+ */
69
+ private discoverActiveSession;
61
70
  /** Send a notification to all known targets. Individual failures are silently ignored. */
62
71
  private notifyUser;
63
72
  private publishMessage;
@@ -16,28 +16,33 @@ function classifyTaskRisk(taskText) {
16
16
  const text = taskText.toLowerCase();
17
17
  // Explicit risky patterns (write / modify / execute / send)
18
18
  const riskyPatterns = [
19
- // English
20
- /\b(write|creat|delet|remov|modif|edit|updat|install|execut|deploy|push|commit|send|post|drop|format|rename|overwrite|reset|wipe|destroy|kill|terminat|rm\b|mkdir|touch\b|mv\b)\b/i,
21
- // Chinese write-oriented verbs
22
- /[写创建删除修改编辑更新安装执行运行发送提交部署重命名覆盖重置清空销毁终止]/,
19
+ // English — word-boundary matched to avoid false positives
20
+ /\b(write|creat|delet|remov|modif|edit|updat|install|execut|deploy|push|commit|send|post|drop|format|rename|overwrite|reset|wipe|destroy|kill|terminat|rm|mkdir|touch|mv)\b/i,
21
+ // Chinese — multi-character phrases to avoid single-char false positives
22
+ // e.g. 安 alone would match 安排(schedule) or 安全(safe)
23
+ /写入|写文件|写邮件|写信|创建|新建|删除|移除|修改|更改|编辑|更新|升级|安装|部署|执行|运行命令|发送|发邮件|提交|推送|重命名|覆盖|重置|清空|清除|销毁|终止|停止服务|kill进程/,
23
24
  ];
24
- if (riskyPatterns.some((p) => p.test(text))) {
25
- return "risky";
26
- }
27
- // Explicitly safe read-only patterns
25
+ // Explicitly safe read-only patterns — checked BEFORE risky to avoid false positives
26
+ // (e.g. "查询并发送报告" is risky overall, but "查询" alone should be safe)
28
27
  const safePatterns = [
28
+ // English read-only verbs
29
29
  /\b(list|show|get|check|view|read|query|find|search|display|fetch|retriev|look|what|which|count|how many|summariz|describ|explain|analyz|report)\b/i,
30
- /[查看获取搜索显示检查列出查询统计描述分析报告]/,
31
- // Calendar / schedule queries
32
- /\b(calendar|schedule|event|meeting|free|busy|availab)\b/i,
33
- /[日历日程会议空闲忙碌可用时间]/,
34
- // Process / system info
35
- /\b(process|pid|cpu|memory|disk|uptime|version|status|running|service)\b/i,
36
- /[进程内存磁盘状态运行版本服务]/,
30
+ // Chinese read-only verbs (multi-char to be specific)
31
+ /查看|查询|获取|搜索|显示|检查|列出|列举|统计|描述|分析|报告|读取|浏览/,
32
+ // Calendar / scheduling queries
33
+ /\b(calendar|schedule|event|meeting|free|busy|availab|appointment)\b/i,
34
+ /日历|日程|会议|空闲|忙碌|可用时间|时间段|什么时候|哪个时间|安排会议|约会|预约/,
35
+ // Process / system info queries
36
+ /\b(process|pid|cpu|memory|disk|uptime|version|status|running|service|log)\b/i,
37
+ /进程|内存|磁盘|系统状态|运行状态|版本信息|日志|监控/,
37
38
  ];
39
+ // Safe check first — if clearly a read query, don't let ambiguous chars trigger risky
38
40
  if (safePatterns.some((p) => p.test(text))) {
39
41
  return "safe";
40
42
  }
43
+ if (riskyPatterns.some((p) => p.test(text))) {
44
+ return "risky";
45
+ }
41
46
  // Default: treat as risky if uncertain
42
47
  return "risky";
43
48
  }
@@ -69,6 +74,7 @@ class OpenClawAgentExecutor {
69
74
  gatewayConfig;
70
75
  taskTracker;
71
76
  getNotificationTargets;
77
+ registerDiscoveredTarget;
72
78
  logger;
73
79
  cwd;
74
80
  pendingCallbacks = new Map();
@@ -77,6 +83,7 @@ class OpenClawAgentExecutor {
77
83
  this.gatewayConfig = options.gatewayConfig;
78
84
  this.taskTracker = options.taskTracker;
79
85
  this.getNotificationTargets = options.getNotificationTargets ?? (() => new Map());
86
+ this.registerDiscoveredTarget = options.registerDiscoveredTarget;
80
87
  this.logger = options.logger;
81
88
  this.cwd = options.cwd || process.cwd();
82
89
  }
@@ -274,11 +281,64 @@ class OpenClawAgentExecutor {
274
281
  this.pendingApprovals.set(taskId, { resolve, reject, timer });
275
282
  });
276
283
  }
284
+ /**
285
+ * Discover the most recently active non-internal session via sessions_list.
286
+ * Used as fallback when no notification targets have been registered yet
287
+ * (e.g. right after a gateway restart before the user sends their first message).
288
+ */
289
+ async discoverActiveSession() {
290
+ if (!this.gatewayConfig)
291
+ return null;
292
+ try {
293
+ const result = await (0, gateway_client_1.invokeGatewayTool)({
294
+ gateway: this.gatewayConfig,
295
+ tool: "sessions_list",
296
+ args: { limit: 10, activeMinutes: 120 },
297
+ timeoutMs: 5_000,
298
+ });
299
+ const INTERNAL_PREFIXES = ["delegate-", "a2a-"];
300
+ const session = result?.sessions?.find((s) => s.sessionKey &&
301
+ !INTERNAL_PREFIXES.some((p) => s.sessionKey.startsWith(p)));
302
+ return session?.sessionKey ?? null;
303
+ }
304
+ catch (err) {
305
+ this.logger.warn(`[a2a-adapter] discoverActiveSession failed: ${err instanceof Error ? err.message : String(err)}`);
306
+ return null;
307
+ }
308
+ }
277
309
  /** Send a notification to all known targets. Individual failures are silently ignored. */
278
310
  async notifyUser(message) {
279
311
  const targets = this.getNotificationTargets();
280
- if (!this.gatewayConfig || targets.size === 0) {
281
- this.logger.info(`[a2a-adapter] notifyUser: skipped (gateway=${!!this.gatewayConfig}, targets=${targets.size})`);
312
+ if (!this.gatewayConfig) {
313
+ this.logger.info(`[a2a-adapter] notifyUser: skipped (no gateway config)`);
314
+ return;
315
+ }
316
+ // Fallback: no registered targets yet (e.g. right after gateway restart).
317
+ // Discover the active session and send directly via sessions_send.
318
+ if (targets.size === 0) {
319
+ this.logger.info(`[a2a-adapter] notifyUser: no registered targets — attempting session discovery`);
320
+ const sessionKey = await this.discoverActiveSession();
321
+ if (sessionKey) {
322
+ this.logger.info(`[a2a-adapter] notifyUser: discovered session ${sessionKey}, sending via sessions_send`);
323
+ try {
324
+ await (0, gateway_client_1.invokeGatewayTool)({
325
+ gateway: this.gatewayConfig,
326
+ tool: "sessions_send",
327
+ args: { sessionKey, message },
328
+ timeoutMs: 5_000,
329
+ });
330
+ // Also register this session for future notifications
331
+ if (this.registerDiscoveredTarget) {
332
+ this.registerDiscoveredTarget(sessionKey);
333
+ }
334
+ }
335
+ catch (err) {
336
+ this.logger.warn(`[a2a-adapter] notifyUser: sessions_send to ${sessionKey} failed: ${err instanceof Error ? err.message : String(err)}`);
337
+ }
338
+ }
339
+ else {
340
+ this.logger.warn(`[a2a-adapter] notifyUser: no active session found, message lost`);
341
+ }
282
342
  return;
283
343
  }
284
344
  const results = await Promise.allSettled([...targets.entries()].map(async ([key, target]) => {
@@ -144,6 +144,8 @@ export declare class MulticlawsService extends EventEmitter {
144
144
  /** Consistent name for this agent: AgentCard.name or fallback. */
145
145
  private getFormattedName;
146
146
  /** Send a notification to all known targets with detailed logging. */
147
+ /** Discover the most recently active non-internal session via sessions_list. */
148
+ private discoverActiveSession;
147
149
  notifyUser(message: string): Promise<void>;
148
150
  private log;
149
151
  }
@@ -15,6 +15,7 @@ const express_1 = __importDefault(require("express"));
15
15
  const server_1 = require("@a2a-js/sdk/server");
16
16
  const express_2 = require("@a2a-js/sdk/server/express");
17
17
  const client_1 = require("@a2a-js/sdk/client");
18
+ const version_1 = require("../infra/version");
18
19
  const a2a_adapter_1 = require("./a2a-adapter");
19
20
  const agent_registry_1 = require("./agent-registry");
20
21
  const agent_profile_1 = require("./agent-profile");
@@ -120,13 +121,16 @@ class MulticlawsService extends node_events_1.EventEmitter {
120
121
  taskTracker: this.taskTracker,
121
122
  cwd: this.resolvedCwd,
122
123
  getNotificationTargets: () => this.notificationTargets,
124
+ registerDiscoveredTarget: (sessionKey) => {
125
+ this.addNotificationTarget(`web:${sessionKey}`, { type: "web", sessionKey });
126
+ },
123
127
  logger,
124
128
  });
125
129
  this.agentCard = {
126
130
  name: profile.ownerName?.trim() ? (0, agent_profile_1.formatAgentCardName)(profile.ownerName.trim()) : "OpenClaw Agent",
127
131
  description: this.profileDescription,
128
132
  url: this.selfUrl,
129
- version: "0.3.0",
133
+ version: version_1.PLUGIN_VERSION,
130
134
  protocolVersion: "0.2.2",
131
135
  defaultInputModes: ["text/plain"],
132
136
  defaultOutputModes: ["text/plain"],
@@ -976,10 +980,54 @@ class MulticlawsService extends node_events_1.EventEmitter {
976
980
  return this.agentCard?.name ?? "OpenClaw Agent";
977
981
  }
978
982
  /** Send a notification to all known targets with detailed logging. */
983
+ /** Discover the most recently active non-internal session via sessions_list. */
984
+ async discoverActiveSession() {
985
+ if (!this.gatewayConfig)
986
+ return null;
987
+ try {
988
+ const result = await (0, gateway_client_1.invokeGatewayTool)({
989
+ gateway: this.gatewayConfig,
990
+ tool: "sessions_list",
991
+ args: { limit: 10, activeMinutes: 120 },
992
+ timeoutMs: 5_000,
993
+ });
994
+ const INTERNAL_PREFIXES = ["delegate-", "a2a-"];
995
+ const session = result?.sessions?.find((s) => s.sessionKey && !INTERNAL_PREFIXES.some((p) => s.sessionKey.startsWith(p)));
996
+ return session?.sessionKey ?? null;
997
+ }
998
+ catch (err) {
999
+ this.log("warn", `discoverActiveSession failed: ${err instanceof Error ? err.message : String(err)}`);
1000
+ return null;
1001
+ }
1002
+ }
979
1003
  async notifyUser(message) {
980
1004
  this.log("info", `notifyUser: targets=${this.notificationTargets.size}, msg=${message.slice(0, 80)}`);
981
- if (!this.gatewayConfig || this.notificationTargets.size === 0) {
982
- this.log("warn", "notifyUser: skipped — no gatewayConfig or no targets");
1005
+ if (!this.gatewayConfig) {
1006
+ this.log("warn", "notifyUser: skipped — no gatewayConfig");
1007
+ return;
1008
+ }
1009
+ // Fallback: no registered targets yet (e.g. right after gateway restart)
1010
+ if (this.notificationTargets.size === 0) {
1011
+ this.log("warn", "notifyUser: no registered targets — attempting session discovery");
1012
+ const sessionKey = await this.discoverActiveSession();
1013
+ if (sessionKey) {
1014
+ this.log("info", `notifyUser: discovered session ${sessionKey}`);
1015
+ try {
1016
+ await (0, gateway_client_1.invokeGatewayTool)({
1017
+ gateway: this.gatewayConfig,
1018
+ tool: "sessions_send",
1019
+ args: { sessionKey, message },
1020
+ timeoutMs: 5_000,
1021
+ });
1022
+ this.addNotificationTarget(`web:${sessionKey}`, { type: "web", sessionKey });
1023
+ }
1024
+ catch (err) {
1025
+ this.log("warn", `notifyUser: sessions_send to ${sessionKey} failed: ${err instanceof Error ? err.message : String(err)}`);
1026
+ }
1027
+ }
1028
+ else {
1029
+ this.log("warn", "notifyUser: no active session found, message lost");
1030
+ }
983
1031
  return;
984
1032
  }
985
1033
  const entries = [...this.notificationTargets.entries()];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multiclaws",
3
- "version": "0.4.37",
3
+ "version": "0.4.38",
4
4
  "description": "MultiClaws plugin for OpenClaw collaboration via A2A protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",