multiclaws 0.4.34 → 0.4.36

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
@@ -640,9 +640,10 @@ const plugin = {
640
640
  });
641
641
  // Inject onboarding prompt when profile is pending first-run setup
642
642
  // Also capture web session targets for notifications (skip internal sub-agent sessions)
643
+ // Skip when channelId is set — those are already handled by message_received hook
643
644
  const INTERNAL_SESSION_PREFIXES = ["delegate-", "a2a-"];
644
645
  api.on("before_prompt_build", async (_event, ctx) => {
645
- if (service && ctx.sessionKey &&
646
+ if (service && ctx.sessionKey && !ctx.channelId &&
646
647
  !INTERNAL_SESSION_PREFIXES.some((p) => ctx.sessionKey.startsWith(p))) {
647
648
  service.addNotificationTarget(`web:${ctx.sessionKey}`, { type: "web", sessionKey: ctx.sessionKey });
648
649
  }
@@ -657,22 +658,32 @@ const plugin = {
657
658
 
658
659
  multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程(优先处理,不管用户说了什么):
659
660
 
660
- 1. **扫描当前环境**,自动生成 bio(markdown 格式),内容包括:
661
- - 用户角色和职责
662
- - 可处理的任务类型(基于已安装 skills、工具、连接的渠道)
663
- - 可访问的数据/系统(git 仓库、邮件、日历、Notion 等)
664
- - 时区和语言
661
+ 1. **确认用户名**(需要用户明确回答):
662
+ - 询问用户希望使用什么名字
663
+ - 这个名字会以「{名字} 的 OpenClaw」格式展示给团队其他成员
664
+ - 例如用户叫「小明」,则其他成员看到的是「小明 OpenClaw」
665
665
 
666
- 2. 向用户展示生成的档案,并逐一确认以下三项(名字和 bio 需要用户明确回答):
667
- - **名字**:展示推断出的名字,询问是否需要修改
668
- - **Bio**:展示生成的 bio,询问是否需要修改
669
- - **网络情况**:告知用户「所有实例通过 FRP 隧道通信,需在插件配置中设置 tunnel 字段(包含 frps 服务器地址、端口、token 和可用端口范围),frpc 会自动下载安装」,无需用户回答
666
+ 2. **自动生成 bio**(无需用户确认,直接生成并保存):
667
+ 扫描当前环境,生成详细的 bio(markdown 格式)。bio 是给其他 AI 智能体看的,用来判断这个智能体能做什么任务、能访问什么数据。必须准确反映实际能力,具体检查:
670
668
 
671
- 3. 根据用户对名字和 bio 的回答更新档案内容(如需修改),然后调用 \`multiclaws_profile_set(ownerName="...", bio="...")\` 保存。
669
+ - **已安装的工具(tools)**:列出所有可用的工具名称,说明能执行哪些类型的操作(如文件读写、代码执行、网络请求等)
670
+ - **已安装的 skills**:列出 skill 名称和功能描述
671
+ - **已连接的渠道(channels)**:检查是否连接了 Telegram、Discord、Gmail、Slack、微信等,列出具体渠道名称
672
+ - **已安装的插件(plugins)**:列出所有已加载的插件及其主要功能
673
+ - **工作区内容**:检查当前工作目录(pwd)下的项目结构,包括:
674
+ - git 仓库信息(仓库名、分支)
675
+ - 主要编程语言和框架
676
+ - 项目名称和用途(从 README 或 package.json 推断)
677
+ - **可访问的数据源**:检查是否有日历(Google Calendar 等)、邮件(Gmail 等)、Notion、数据库、API 等数据访问能力
678
+ - **系统信息**:时区、操作系统、语言偏好
679
+
680
+ 3. 用户确认名字后,立即调用 \`multiclaws_profile_set(ownerName="确认后的名字", bio="生成的bio")\` 保存。
672
681
 
673
682
  4. 调用 \`multiclaws_profile_clear_pending_review()\` 完成初始化。
674
683
 
675
- **注意**:名字和 bio 需要用户明确确认;网络情况仅告知无需回答。`,
684
+ 5. **网络情况告知**(无需用户回答):告知用户「所有实例通过 FRP 隧道通信,需在插件配置中设置 tunnel 字段(包含 frps 服务器地址、端口、token 和可用端口范围),frpc 会自动下载安装」
685
+
686
+ **注意**:只有名字需要用户明确确认;bio 自动生成直接保存无需确认;网络情况仅告知无需回答。`,
676
687
  };
677
688
  }
678
689
  catch (err) {
@@ -5,6 +5,7 @@ export type AgentProfile = {
5
5
  bio: string;
6
6
  };
7
7
  export declare function renderProfileDescription(profile: AgentProfile): string;
8
+ export declare function formatAgentCardName(ownerName: string): string;
8
9
  export declare class ProfileStore {
9
10
  private readonly filePath;
10
11
  private readonly logger?;
@@ -2,17 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ProfileStore = void 0;
4
4
  exports.renderProfileDescription = renderProfileDescription;
5
+ exports.formatAgentCardName = formatAgentCardName;
5
6
  const json_store_1 = require("../infra/json-store");
6
7
  function emptyProfile() {
7
8
  return { ownerName: "", bio: "" };
8
9
  }
9
10
  function renderProfileDescription(profile) {
10
- const parts = [];
11
- if (profile.ownerName)
12
- parts.push(profile.ownerName);
13
- if (profile.bio)
14
- parts.push(profile.bio);
15
- return parts.join("\n\n") || "OpenClaw agent";
11
+ return profile.bio?.trim() || "OpenClaw agent";
12
+ }
13
+ function formatAgentCardName(ownerName) {
14
+ return `${ownerName} 的 OpenClaw`;
16
15
  }
17
16
  class ProfileStore {
18
17
  filePath;
@@ -136,7 +136,9 @@ export declare class MulticlawsService extends EventEmitter {
136
136
  /** Resolve a pending A2A callback from sub-agent. */
137
137
  resolveA2ACallback(taskId: string, result: string): boolean;
138
138
  addNotificationTarget(key: string, target: NotificationTarget): void;
139
- /** Send a notification to all known targets. Individual failures are silently ignored. */
139
+ /** Consistent name for this agent: AgentCard.name or fallback. */
140
+ private getFormattedName;
141
+ /** Send a notification to all known targets with detailed logging. */
140
142
  notifyUser(message: string): Promise<void>;
141
143
  private log;
142
144
  }
@@ -109,13 +109,8 @@ class MulticlawsService extends node_events_1.EventEmitter {
109
109
  this.log("info", `FRP tunnel ready: ${publicUrl}`);
110
110
  }
111
111
  // Load profile for AgentCard description
112
- let profile = await this.profileStore.load();
113
- const isIncompleteProfile = !profile.ownerName?.trim() || !profile.bio?.trim();
112
+ const profile = await this.profileStore.load();
114
113
  if (!profile.ownerName?.trim()) {
115
- profile.ownerName = this.options.displayName ?? node_os_1.default.hostname();
116
- await this.profileStore.save(profile);
117
- }
118
- if (isIncompleteProfile) {
119
114
  await this.setPendingProfileReview();
120
115
  }
121
116
  this.profileDescription = (0, agent_profile_1.renderProfileDescription)(profile);
@@ -128,7 +123,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
128
123
  logger,
129
124
  });
130
125
  this.agentCard = {
131
- name: this.options.displayName ?? (profile.ownerName || "OpenClaw Agent"),
126
+ name: profile.ownerName?.trim() ? (0, agent_profile_1.formatAgentCardName)(profile.ownerName.trim()) : "OpenClaw Agent",
132
127
  description: this.profileDescription,
133
128
  url: this.selfUrl,
134
129
  version: "0.3.0",
@@ -407,8 +402,8 @@ class MulticlawsService extends node_events_1.EventEmitter {
407
402
  */
408
403
  async requireCompleteProfile() {
409
404
  const profile = await this.profileStore.load();
410
- if (!profile.ownerName?.trim() || !profile.bio?.trim()) {
411
- throw new Error("档案未完成设置。请先调用 multiclaws_profile_set(ownerName=\"你的名字\", bio=\"你的介绍\") 完成设置后再继续。");
405
+ if (!profile.ownerName?.trim()) {
406
+ throw new Error("档案未完成设置。请先调用 multiclaws_profile_set(ownerName=\"你的名字\") 设置用户名后再继续。");
412
407
  }
413
408
  }
414
409
  async setProfile(patch) {
@@ -430,7 +425,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
430
425
  if (this.agentCard) {
431
426
  this.agentCard.description = this.profileDescription;
432
427
  if (profile.ownerName?.trim()) {
433
- this.agentCard.name = profile.ownerName.trim();
428
+ this.agentCard.name = (0, agent_profile_1.formatAgentCardName)(profile.ownerName.trim());
434
429
  }
435
430
  }
436
431
  }
@@ -484,7 +479,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
484
479
  const team = await this.teamStore.createTeam({
485
480
  teamName: name,
486
481
  selfUrl: this.selfUrl,
487
- selfName: this.options.displayName ?? node_os_1.default.hostname(),
482
+ selfName: this.getFormattedName(),
488
483
  selfDescription: this.profileDescription,
489
484
  });
490
485
  this.log("info", `team created: ${team.teamId} (${team.teamName})`);
@@ -534,7 +529,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
534
529
  // 2. Announce self to seed (seed broadcasts to others)
535
530
  const selfMember = {
536
531
  url: this.selfUrl,
537
- name: this.options.displayName ?? node_os_1.default.hostname(),
532
+ name: this.getFormattedName(),
538
533
  description: this.profileDescription,
539
534
  joinedAtMs: Date.now(),
540
535
  };
@@ -588,7 +583,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
588
583
  const selfNormalized = this.selfUrl.replace(/\/+$/, "");
589
584
  const selfMember = {
590
585
  url: this.selfUrl,
591
- name: this.options.displayName ?? node_os_1.default.hostname(),
586
+ name: this.getFormattedName(),
592
587
  joinedAtMs: 0,
593
588
  };
594
589
  const others = team.members.filter((m) => m.url.replace(/\/+$/, "") !== selfNormalized);
@@ -794,12 +789,12 @@ class MulticlawsService extends node_events_1.EventEmitter {
794
789
  try {
795
790
  const teams = await this.teamStore.listTeams();
796
791
  const selfNormalized = this.selfUrl.replace(/\/+$/, "");
797
- const displayName = this.options.displayName ?? node_os_1.default.hostname();
792
+ const agentName = this.getFormattedName();
798
793
  for (const team of teams) {
799
794
  // Update self in team store
800
795
  await this.teamStore.addMember(team.teamId, {
801
796
  url: this.selfUrl,
802
- name: displayName,
797
+ name: agentName,
803
798
  description: this.profileDescription,
804
799
  joinedAtMs: Date.now(),
805
800
  });
@@ -811,7 +806,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
811
806
  headers: { "Content-Type": "application/json" },
812
807
  body: JSON.stringify({
813
808
  url: this.selfUrl,
814
- name: displayName,
809
+ name: agentName,
815
810
  description: this.profileDescription,
816
811
  }),
817
812
  }).catch(() => {
@@ -962,23 +957,48 @@ class MulticlawsService extends node_events_1.EventEmitter {
962
957
  this.log("debug", `notification target registered: ${key} (total: ${this.notificationTargets.size})`);
963
958
  }
964
959
  }
965
- /** Send a notification to all known targets. Individual failures are silently ignored. */
960
+ /** Consistent name for this agent: AgentCard.name or fallback. */
961
+ getFormattedName() {
962
+ return this.agentCard?.name ?? "OpenClaw Agent";
963
+ }
964
+ /** Send a notification to all known targets with detailed logging. */
966
965
  async notifyUser(message) {
967
- if (!this.gatewayConfig || this.notificationTargets.size === 0)
966
+ this.log("info", `notifyUser: targets=${this.notificationTargets.size}, msg=${message.slice(0, 80)}`);
967
+ if (!this.gatewayConfig || this.notificationTargets.size === 0) {
968
+ this.log("warn", "notifyUser: skipped — no gatewayConfig or no targets");
968
969
  return;
969
- await Promise.allSettled([...this.notificationTargets.values()].map((target) => target.type === "channel"
970
- ? (0, gateway_client_1.invokeGatewayTool)({
971
- gateway: this.gatewayConfig,
972
- tool: "message",
973
- args: { action: "send", target: target.conversationId, message },
974
- timeoutMs: 5_000,
975
- })
976
- : (0, gateway_client_1.invokeGatewayTool)({
977
- gateway: this.gatewayConfig,
978
- tool: "chat.send",
979
- args: { sessionKey: target.sessionKey, message },
980
- timeoutMs: 5_000,
981
- })));
970
+ }
971
+ const entries = [...this.notificationTargets.entries()];
972
+ const results = await Promise.allSettled(entries.map(async ([key, target]) => {
973
+ this.log("info", `notifyUser: sending to ${key} (type=${target.type})`);
974
+ try {
975
+ await (target.type === "channel"
976
+ ? (0, gateway_client_1.invokeGatewayTool)({
977
+ gateway: this.gatewayConfig,
978
+ tool: "message",
979
+ args: { action: "send", target: target.conversationId, message },
980
+ timeoutMs: 5_000,
981
+ })
982
+ : (0, gateway_client_1.invokeGatewayTool)({
983
+ gateway: this.gatewayConfig,
984
+ tool: "chat.send",
985
+ args: { sessionKey: target.sessionKey, message },
986
+ timeoutMs: 5_000,
987
+ }));
988
+ this.log("info", `notifyUser: ${key} (${target.type}) succeeded`);
989
+ }
990
+ catch (err) {
991
+ this.log("warn", `notifyUser: ${key} (${target.type}) failed: ${err instanceof Error ? err.message : String(err)}`);
992
+ throw err;
993
+ }
994
+ }));
995
+ const failCount = results.filter((r) => r.status === "rejected").length;
996
+ if (failCount === entries.length) {
997
+ this.log("error", `notifyUser: ALL ${failCount} targets failed`);
998
+ }
999
+ else if (failCount > 0) {
1000
+ this.log("warn", `notifyUser: ${failCount}/${entries.length} targets failed`);
1001
+ }
982
1002
  }
983
1003
  log(level, message) {
984
1004
  this.options.logger?.[level]?.(`[multiclaws] ${message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multiclaws",
3
- "version": "0.4.34",
3
+ "version": "0.4.36",
4
4
  "description": "MultiClaws plugin for OpenClaw collaboration via A2A protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,35 +13,43 @@ MultiClaws 让多个 OpenClaw 实例通过 [A2A(Agent-to-Agent)](https://goo
13
13
 
14
14
  当 hook 注入了初始化任务时,按以下步骤执行:
15
15
 
16
- 1. **扫描当前环境**,自动生成 bio(markdown 格式),包含:
17
- - 可用的工具和 skills(推断能力)
18
- - 已连接的渠道(Telegram、Discord、Gmail 等)
19
- - 工作区内容(git 仓库、关键文件、项目目录)
20
- - 已安装的插件
21
- - 时区、语言等上下文
16
+ 1. **确认用户名**(需用户明确回答):
17
+ 询问用户希望使用什么名字。名字会以「{名字} OpenClaw」格式展示给团队成员。
22
18
 
23
- 2. 向用户展示生成的档案,并逐一确认以下三项:
24
- - **名字**:展示推断出的名字,询问是否需要修改(需用户明确回答)
25
- - **Bio**:展示生成的 bio,询问是否需要修改(需用户明确回答)
26
- - **网络情况**:告知用户「所有实例通过 FRP 隧道通信,需在插件配置中设置 tunnel 字段(frps 地址、端口、token、可用端口范围),frpc 会自动下载安装」,无需用户回答
19
+ 2. **自动生成 bio**(无需用户确认,直接保存),扫描并检查:
20
+ - **可用工具(tools)**:列出所有工具名称,说明能执行的操作类型
21
+ - **已安装 skills**:列出 skill 名称和功能
22
+ - **已连接渠道**:Telegram、Discord、Gmail、Slack、微信等,列出具体渠道
23
+ - **已安装插件**:列出所有插件及主要功能
24
+ - **工作区**:当前目录下的项目(git 仓库名、语言、框架、项目用途)
25
+ - **数据访问**:日历、邮件、Notion、数据库等
26
+ - **系统信息**:时区、OS、语言
27
27
 
28
- 3. 根据用户对名字和 bio 的回答更新内容后,调用 `multiclaws_profile_set(ownerName="...", bio="...")` 保存档案。
28
+ Bio 是给其他 AI 智能体看的,用来判断这个智能体能做什么、能访问什么数据。必须准确反映实际能力。
29
+
30
+ 3. 用户确认名字后,调用 `multiclaws_profile_set(ownerName="...", bio="...")` 保存。
29
31
 
30
32
  4. 调用 `multiclaws_profile_clear_pending_review()` 完成初始化。
31
33
 
34
+ 5. 告知网络情况(无需用户回答)。
35
+
32
36
  **示例 bio:**
33
37
  ```markdown
34
38
  后端工程师,负责 API 服务开发与维护。
35
39
 
36
- **可处理:**
37
- - 代码审查、调试、重构(Node.js / Go / Python)
38
- - API 文档编写与接口设计
39
- - 数据库查询与优化(PostgreSQL)
40
+ **可用工具:** exec, read, write, edit, glob, grep, process, git, message
41
+
42
+ **已连接渠道:** Telegram, Gmail
43
+
44
+ **工作区:**
45
+ - `/Users/eric/Project/api-service` — Node.js API 服务(Express, TypeScript, ~50k LOC)
40
46
 
41
47
  **数据访问:**
42
- - Codebase: `/Users/eric/Project/api-service`(Node.js,~50k LOC)
43
- - Email: Gmail
44
- - Calendar: Google Calendar
48
+ - Email: Gmail(收发邮件)
49
+ - Calendar: Google Calendar(查看日程)
50
+ - Database: PostgreSQL(只读查询)
51
+
52
+ **已安装插件:** multiclaws, calendar-sync
45
53
 
46
54
  **时区:** GMT+8
47
55
  ```
@@ -56,12 +64,16 @@ MultiClaws 让多个 OpenClaw 实例通过 [A2A(Agent-to-Agent)](https://goo
56
64
  multiclaws_profile_show()
57
65
  ```
58
66
 
59
- 如果 `bio` 为空或 `ownerName` 为空:
60
- 1. 自动生成 bio(同上)
61
- 2. 询问用户确认名字和 bio
67
+ 如果 `ownerName` 为空:
68
+ 1. 询问用户确认名字
69
+ 2. 自动生成 bio(无需用户确认)
62
70
  3. 调用 `multiclaws_profile_set(...)` 设置
63
71
  4. 然后继续团队操作
64
72
 
73
+ 如果 `ownerName` 已设置但 `bio` 为空:
74
+ 1. 自动生成 bio 并保存(无需用户确认)
75
+ 2. 继续团队操作
76
+
65
77
  ---
66
78
 
67
79
  ## 3. 保持档案更新
@@ -114,7 +126,7 @@ multiclaws_profile_show()
114
126
  - **只使用上面列出的工具。** 没有 `multiclaws_status` 工具。
115
127
  - **Bio 是自由格式的 markdown。** 写得让另一个 AI 能读懂这个智能体能做什么。
116
128
  - **每个智能体就像一个 skill。** 委派时读每个智能体的 bio,选最匹配的。
117
- - **名字和 bio 必须用户明确确认**;网络情况仅告知,无需用户回答。
129
+ - **只有名字需要用户明确确认**;bio 自动生成无需确认;网络情况仅告知无需回答。
118
130
 
119
131
  ---
120
132
 
@@ -124,7 +136,7 @@ multiclaws_profile_show()
124
136
 
125
137
  ```
126
138
  1. multiclaws_profile_show() — 检查档案
127
- 2.(如果为空)自动生成并设置 bio,确认名字和 bio
139
+ 2.(如果 ownerName 为空)确认名字,自动生成 bio
128
140
  3. multiclaws_team_create(name="...") — 返回 inviteCode (mc:xxxx)
129
141
  4. 告诉用户把邀请码分享给队友
130
142
  ```
@@ -133,7 +145,7 @@ multiclaws_profile_show()
133
145
 
134
146
  ```
135
147
  1. multiclaws_profile_show() — 检查档案
136
- 2.(如果为空)自动生成并设置 bio,确认名字和 bio
148
+ 2.(如果 ownerName 为空)确认名字,自动生成 bio
137
149
  3. multiclaws_team_join(inviteCode="mc:xxxx")
138
150
  → 自动同步所有团队成员
139
151
  ```