multiclaws 0.4.4 → 0.4.6
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/gateway/handlers.js
CHANGED
|
@@ -3,10 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createGatewayHandlers = createGatewayHandlers;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const nonEmptyString = zod_1.z.string().trim().min(1);
|
|
6
|
-
const agentAddSchema = zod_1.z.object({
|
|
7
|
-
url: nonEmptyString,
|
|
8
|
-
apiKey: zod_1.z.string().trim().min(1).optional(),
|
|
9
|
-
});
|
|
10
6
|
const agentRemoveSchema = zod_1.z.object({ url: nonEmptyString });
|
|
11
7
|
const sessionStartSchema = zod_1.z.object({
|
|
12
8
|
agentUrl: nonEmptyString,
|
|
@@ -49,17 +45,6 @@ function createGatewayHandlers(getService) {
|
|
|
49
45
|
safeHandle(respond, "agent_list_failed", error);
|
|
50
46
|
}
|
|
51
47
|
},
|
|
52
|
-
"multiclaws.agent.add": async ({ params, respond }) => {
|
|
53
|
-
try {
|
|
54
|
-
const parsed = agentAddSchema.parse(params);
|
|
55
|
-
const service = getService();
|
|
56
|
-
const agent = await service.addAgent(parsed);
|
|
57
|
-
respond(true, agent);
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
safeHandle(respond, "invalid_params", error);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
48
|
"multiclaws.agent.remove": async ({ params, respond }) => {
|
|
64
49
|
try {
|
|
65
50
|
const parsed = agentRemoveSchema.parse(params);
|
package/dist/index.js
CHANGED
|
@@ -45,31 +45,6 @@ function createTools(getService) {
|
|
|
45
45
|
return textResult(JSON.stringify({ agents }, null, 2), { agents });
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
|
-
const multiclawsAddAgent = {
|
|
49
|
-
name: "multiclaws_add_agent",
|
|
50
|
-
description: "Add a remote A2A agent by URL. Automatically fetches its Agent Card.",
|
|
51
|
-
parameters: {
|
|
52
|
-
type: "object",
|
|
53
|
-
additionalProperties: false,
|
|
54
|
-
properties: {
|
|
55
|
-
url: { type: "string" },
|
|
56
|
-
apiKey: { type: "string" },
|
|
57
|
-
},
|
|
58
|
-
required: ["url"],
|
|
59
|
-
},
|
|
60
|
-
execute: async (_toolCallId, args) => {
|
|
61
|
-
const service = requireService(getService());
|
|
62
|
-
const url = typeof args.url === "string" ? args.url.trim() : "";
|
|
63
|
-
if (!url)
|
|
64
|
-
throw new Error("url is required");
|
|
65
|
-
const apiKey = typeof args.apiKey === "string" ? args.apiKey.trim() : undefined;
|
|
66
|
-
const agent = await service.addAgent({ url, apiKey });
|
|
67
|
-
const status = agent.reachable
|
|
68
|
-
? `Agent added: ${agent.name} (${agent.url})`
|
|
69
|
-
: `⚠️ Agent added but NOT reachable: ${agent.url} — agent card could not be fetched. Verify the URL and ensure the agent is running.`;
|
|
70
|
-
return textResult(status, agent);
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
48
|
const multiclawsRemoveAgent = {
|
|
74
49
|
name: "multiclaws_remove_agent",
|
|
75
50
|
description: "Remove a known A2A agent by URL.",
|
|
@@ -349,7 +324,6 @@ function createTools(getService) {
|
|
|
349
324
|
};
|
|
350
325
|
return [
|
|
351
326
|
multiclawsAgents,
|
|
352
|
-
multiclawsAddAgent,
|
|
353
327
|
multiclawsRemoveAgent,
|
|
354
328
|
multiclawsSessionStart,
|
|
355
329
|
multiclawsSessionReply,
|
|
@@ -375,19 +349,34 @@ const plugin = {
|
|
|
375
349
|
(0, telemetry_1.initializeTelemetry)({ enableConsoleExporter: config.telemetry?.consoleExporter });
|
|
376
350
|
const structured = (0, logger_1.createStructuredLogger)(api.logger, "multiclaws");
|
|
377
351
|
let service = null;
|
|
378
|
-
// Ensure
|
|
379
|
-
//
|
|
352
|
+
// Ensure plugin tools and gateway dependencies are whitelisted at registration time.
|
|
353
|
+
// tools.alsoAllow is additive and won't override the user's tools.profile setting.
|
|
380
354
|
if (api.config) {
|
|
381
355
|
const gw = api.config.gateway;
|
|
382
356
|
if (gw) {
|
|
383
357
|
const tools = (gw.tools ?? {});
|
|
358
|
+
// 1. Gateway tools the plugin depends on → tools.allow
|
|
384
359
|
const allow = Array.isArray(tools.allow) ? tools.allow : [];
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
if (
|
|
388
|
-
tools.allow = [...allow, ...
|
|
389
|
-
gw.tools = tools;
|
|
360
|
+
const requiredGatewayTools = ["sessions_spawn", "sessions_history", "message"];
|
|
361
|
+
const missingGateway = requiredGatewayTools.filter((t) => !allow.includes(t));
|
|
362
|
+
if (missingGateway.length > 0) {
|
|
363
|
+
tools.allow = [...allow, ...missingGateway];
|
|
390
364
|
}
|
|
365
|
+
// 2. Plugin's own tools → tools.alsoAllow (additive, works with any profile)
|
|
366
|
+
const alsoAllow = Array.isArray(tools.alsoAllow) ? tools.alsoAllow : [];
|
|
367
|
+
const pluginToolNames = [
|
|
368
|
+
"multiclaws_agents", "multiclaws_remove_agent",
|
|
369
|
+
"multiclaws_session_start", "multiclaws_session_reply", "multiclaws_session_status",
|
|
370
|
+
"multiclaws_session_wait_all", "multiclaws_session_end",
|
|
371
|
+
"multiclaws_team_create", "multiclaws_team_join", "multiclaws_team_leave", "multiclaws_team_members",
|
|
372
|
+
"multiclaws_profile_set", "multiclaws_profile_show",
|
|
373
|
+
"multiclaws_profile_pending_review", "multiclaws_profile_clear_pending_review",
|
|
374
|
+
];
|
|
375
|
+
const missingPlugin = pluginToolNames.filter((t) => !alsoAllow.includes(t));
|
|
376
|
+
if (missingPlugin.length > 0) {
|
|
377
|
+
tools.alsoAllow = [...alsoAllow, ...missingPlugin];
|
|
378
|
+
}
|
|
379
|
+
gw.tools = tools;
|
|
391
380
|
}
|
|
392
381
|
}
|
|
393
382
|
const gatewayConfig = (() => {
|
|
@@ -463,10 +452,34 @@ const plugin = {
|
|
|
463
452
|
const review = await service.getPendingProfileReview();
|
|
464
453
|
if (!review.pending)
|
|
465
454
|
return;
|
|
455
|
+
const tsStatus = service.getTailscaleStatus();
|
|
456
|
+
let networkLine;
|
|
457
|
+
if (tsStatus === "ready") {
|
|
458
|
+
networkLine = "Tailscale 已检测到,跨网络协作已就绪。";
|
|
459
|
+
}
|
|
460
|
+
else if (tsStatus === "needs_auth") {
|
|
461
|
+
networkLine = "Tailscale 已安装但未登录。跨网络协作需要先完成登录,登录后重启 OpenClaw。局域网内可直接使用。";
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
networkLine = "当前仅支持局域网协作。如需跨网络,安装 Tailscale(https://tailscale.com/download)并登录同一 tailnet,然后重启 OpenClaw。";
|
|
465
|
+
}
|
|
466
466
|
return {
|
|
467
|
-
appendSystemContext: `## MultiClaws 首次安装 —
|
|
467
|
+
appendSystemContext: `## MultiClaws 首次安装 — 需要完成初始化
|
|
468
|
+
|
|
469
|
+
multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程(优先处理,不管用户说了什么)。
|
|
470
|
+
**务必使用插件工具调用,不要通过命令行或 gateway 方法操作。**
|
|
471
|
+
|
|
472
|
+
### 第一步:向用户介绍 MultiClaws
|
|
473
|
+
|
|
474
|
+
简要说明插件功能:
|
|
475
|
+
- MultiClaws 让多个 OpenClaw 实例组成团队协作
|
|
476
|
+
- 你可以创建团队、邀请队友加入,然后把任务委派给队友的 AI
|
|
477
|
+
- 它会根据每个智能体的档案(名字、角色、能力、数据源)自动选择最合适的执行者
|
|
478
|
+
- 支持多轮对话、并发扇出、链式协作等模式
|
|
479
|
+
|
|
480
|
+
### 第二步:设置档案
|
|
468
481
|
|
|
469
|
-
|
|
482
|
+
档案(名字 + bio)是你在团队中的「名片」,其他 AI 读它来决定是否把任务委派给你。
|
|
470
483
|
|
|
471
484
|
1. **扫描当前环境**,自动生成 bio(markdown 格式),内容包括:
|
|
472
485
|
- 用户角色和职责
|
|
@@ -474,16 +487,16 @@ multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程
|
|
|
474
487
|
- 可访问的数据/系统(git 仓库、邮件、日历、Notion 等)
|
|
475
488
|
- 时区和语言
|
|
476
489
|
|
|
477
|
-
2.
|
|
490
|
+
2. 向用户展示生成的档案,并逐一确认(名字和 bio 需要用户明确回答):
|
|
478
491
|
- **名字**:展示推断出的名字,询问是否需要修改
|
|
479
492
|
- **Bio**:展示生成的 bio,询问是否需要修改
|
|
480
|
-
- **网络情况**:告知用户「同局域网开箱即用;跨网络需安装 Tailscale(https://tailscale.com/download)并重启 OpenClaw」,无需用户回答
|
|
481
493
|
|
|
482
|
-
3.
|
|
494
|
+
3. 根据用户确认的内容,调用 \`multiclaws_profile_set(ownerName="...", bio="...")\` 保存。保存后初始化自动完成。
|
|
483
495
|
|
|
484
|
-
|
|
496
|
+
### 第三步:告知网络状态和使用方式
|
|
485
497
|
|
|
486
|
-
|
|
498
|
+
- **网络**:${networkLine}
|
|
499
|
+
- **如何开始**:说「创建一个叫 xxx 的团队」创建团队,把邀请码分享给队友;或说「用邀请码 mc:xxxx 加入团队」加入队友的团队。`,
|
|
487
500
|
};
|
|
488
501
|
}
|
|
489
502
|
catch (err) {
|
|
@@ -45,17 +45,12 @@ export declare class MulticlawsService extends EventEmitter {
|
|
|
45
45
|
private readonly httpRateLimiter;
|
|
46
46
|
private selfUrl;
|
|
47
47
|
private profileDescription;
|
|
48
|
+
private tailscaleStatus;
|
|
48
49
|
constructor(options: MulticlawsServiceOptions);
|
|
49
50
|
start(): Promise<void>;
|
|
50
51
|
stop(): Promise<void>;
|
|
51
52
|
updateGatewayConfig(config: GatewayConfig): void;
|
|
52
53
|
listAgents(): Promise<AgentRecord[]>;
|
|
53
|
-
addAgent(params: {
|
|
54
|
-
url: string;
|
|
55
|
-
apiKey?: string;
|
|
56
|
-
}): Promise<AgentRecord & {
|
|
57
|
-
reachable: boolean;
|
|
58
|
-
}>;
|
|
59
54
|
removeAgent(url: string): Promise<boolean>;
|
|
60
55
|
startSession(params: {
|
|
61
56
|
agentUrl: string;
|
|
@@ -91,8 +86,10 @@ export declare class MulticlawsService extends EventEmitter {
|
|
|
91
86
|
ownerName?: string;
|
|
92
87
|
bio?: string;
|
|
93
88
|
}): Promise<AgentProfile>;
|
|
89
|
+
private autoClearPendingReviewIfReady;
|
|
94
90
|
private updateProfileDescription;
|
|
95
91
|
private getPendingReviewPath;
|
|
92
|
+
getTailscaleStatus(): "ready" | "needs_auth" | "not_installed" | "unavailable";
|
|
96
93
|
getPendingProfileReview(): Promise<{
|
|
97
94
|
pending: boolean;
|
|
98
95
|
profile?: AgentProfile;
|
|
@@ -48,6 +48,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
48
48
|
httpRateLimiter = new rate_limiter_1.RateLimiter({ windowMs: 60_000, maxRequests: 60 });
|
|
49
49
|
selfUrl;
|
|
50
50
|
profileDescription = "OpenClaw agent";
|
|
51
|
+
tailscaleStatus = "unavailable";
|
|
51
52
|
constructor(options) {
|
|
52
53
|
super();
|
|
53
54
|
this.options = options;
|
|
@@ -75,18 +76,17 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
75
76
|
const tsIp = (0, tailscale_1.getTailscaleIpFromInterfaces)();
|
|
76
77
|
if (tsIp) {
|
|
77
78
|
this.selfUrl = `http://${tsIp}:${port}`;
|
|
79
|
+
this.tailscaleStatus = "ready";
|
|
78
80
|
this.log("info", `Tailscale IP detected: ${tsIp}`);
|
|
79
81
|
}
|
|
80
82
|
else {
|
|
81
|
-
// Slow path: Tailscale not active — run full detection
|
|
83
|
+
// Slow path: Tailscale not active — run full detection
|
|
82
84
|
const tailscale = await (0, tailscale_1.detectTailscale)();
|
|
85
|
+
this.tailscaleStatus = tailscale.status;
|
|
83
86
|
if (tailscale.status === "ready") {
|
|
84
87
|
this.selfUrl = `http://${tailscale.ip}:${port}`;
|
|
85
88
|
this.log("info", `Tailscale IP detected: ${tailscale.ip}`);
|
|
86
89
|
}
|
|
87
|
-
else {
|
|
88
|
-
void this.notifyTailscaleSetup(tailscale);
|
|
89
|
-
}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
// Load profile for AgentCard description
|
|
@@ -196,30 +196,6 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
196
196
|
async listAgents() {
|
|
197
197
|
return await this.agentRegistry.list();
|
|
198
198
|
}
|
|
199
|
-
async addAgent(params) {
|
|
200
|
-
const normalizedUrl = params.url.replace(/\/+$/, "");
|
|
201
|
-
try {
|
|
202
|
-
const client = await this.clientFactory.createFromUrl(normalizedUrl);
|
|
203
|
-
const card = await client.getAgentCard();
|
|
204
|
-
const record = await this.agentRegistry.add({
|
|
205
|
-
url: normalizedUrl,
|
|
206
|
-
name: card.name ?? normalizedUrl,
|
|
207
|
-
description: card.description ?? "",
|
|
208
|
-
skills: card.skills?.map((s) => s.name ?? s.id) ?? [],
|
|
209
|
-
apiKey: params.apiKey,
|
|
210
|
-
});
|
|
211
|
-
return { ...record, reachable: true };
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
this.log("warn", `agent at ${normalizedUrl} is not reachable, adding with limited info`);
|
|
215
|
-
const record = await this.agentRegistry.add({
|
|
216
|
-
url: normalizedUrl,
|
|
217
|
-
name: normalizedUrl,
|
|
218
|
-
apiKey: params.apiKey,
|
|
219
|
-
});
|
|
220
|
-
return { ...record, reachable: false };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
199
|
async removeAgent(url) {
|
|
224
200
|
return await this.agentRegistry.remove(url);
|
|
225
201
|
}
|
|
@@ -558,9 +534,20 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
558
534
|
async setProfile(patch) {
|
|
559
535
|
const profile = await this.profileStore.update(patch);
|
|
560
536
|
this.updateProfileDescription(profile);
|
|
537
|
+
// Auto-clear pending review once both ownerName and bio are filled
|
|
538
|
+
await this.autoClearPendingReviewIfReady(profile);
|
|
561
539
|
await this.broadcastProfileToTeams();
|
|
562
540
|
return profile;
|
|
563
541
|
}
|
|
542
|
+
async autoClearPendingReviewIfReady(profile) {
|
|
543
|
+
if (profile.ownerName?.trim() && profile.bio?.trim()) {
|
|
544
|
+
const review = await this.getPendingProfileReview();
|
|
545
|
+
if (review.pending) {
|
|
546
|
+
await this.clearPendingProfileReview();
|
|
547
|
+
this.log("info", "pending profile review auto-cleared after profile update");
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
564
551
|
updateProfileDescription(profile) {
|
|
565
552
|
this.profileDescription = (0, agent_profile_1.renderProfileDescription)(profile);
|
|
566
553
|
if (this.agentCard) {
|
|
@@ -573,6 +560,9 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
573
560
|
getPendingReviewPath() {
|
|
574
561
|
return node_path_1.default.join(this.options.stateDir, "multiclaws", "pending-profile-review.json");
|
|
575
562
|
}
|
|
563
|
+
getTailscaleStatus() {
|
|
564
|
+
return this.tailscaleStatus;
|
|
565
|
+
}
|
|
576
566
|
async getPendingProfileReview() {
|
|
577
567
|
const p = this.getPendingReviewPath();
|
|
578
568
|
const data = await (0, json_store_1.readJsonWithFallback)(p, {});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multiclaws",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "MultiClaws plugin for OpenClaw collaboration via A2A protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"typescript": "^5.9.2",
|
|
54
54
|
"vitest": "^3.2.4"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
|
@@ -15,8 +15,9 @@ MultiClaws 让多个 OpenClaw 实例通过 [A2A(Agent-to-Agent)](https://goo
|
|
|
15
15
|
- **名字**:询问是否需要修改(需用户明确回答)
|
|
16
16
|
- **Bio**:询问是否需要修改(需用户明确回答)
|
|
17
17
|
- **网络情况**:告知「同局域网开箱即用;跨网络需安装 Tailscale」,无需用户回答
|
|
18
|
-
3. 调用 `multiclaws_profile_set(ownerName="...", bio="...")`
|
|
19
|
-
|
|
18
|
+
3. 调用 `multiclaws_profile_set(ownerName="...", bio="...")` 保存(pending review 标记会自动清除,无需额外操作)
|
|
19
|
+
|
|
20
|
+
> **重要**:始终使用插件工具调用,不要通过命令行或 gateway 方法操作 MultiClaws。
|
|
20
21
|
|
|
21
22
|
---
|
|
22
23
|
|
|
@@ -131,13 +132,13 @@ multiclaws_profile_show()
|
|
|
131
132
|
| 工具 | 说明 | 参数 |
|
|
132
133
|
|------|------|------|
|
|
133
134
|
| `multiclaws_agents` | 列出已知 agent 及 bio | — |
|
|
134
|
-
| `multiclaws_add_agent` | 手动添加 agent | `url`, `apiKey`(可选) |
|
|
135
135
|
| `multiclaws_remove_agent` | 移除 agent | `url` |
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
139
139
|
## 重要规则
|
|
140
140
|
|
|
141
|
+
- **始终使用插件工具调用**,不要通过命令行、shell 或 gateway 方法操作 MultiClaws
|
|
141
142
|
- **不要问用户 IP 或 selfUrl**,插件自动处理
|
|
142
143
|
- **Bio 是 markdown**,写得让另一个 AI 能读懂这个 agent 能做什么
|
|
143
144
|
- **名字和 bio 必须用户明确确认**;网络情况仅告知
|