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 +3 -2
- package/dist/infra/version.d.ts +1 -0
- package/dist/infra/version.js +19 -0
- package/dist/service/a2a-adapter.d.ts +9 -0
- package/dist/service/a2a-adapter.js +77 -17
- package/dist/service/multiclaws-service.d.ts +2 -0
- package/dist/service/multiclaws-service.js +51 -3
- package/package.json +1 -1
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:
|
|
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
|
|
21
|
-
// Chinese
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
/
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
281
|
-
this.logger.info(`[a2a-adapter] notifyUser: skipped (gateway
|
|
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:
|
|
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
|
|
982
|
-
this.log("warn", "notifyUser: skipped — no gatewayConfig
|
|
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()];
|