multiclaws 0.4.33 → 0.4.35
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 +34 -15
- package/dist/service/a2a-adapter.js +57 -25
- package/dist/service/agent-profile.d.ts +1 -0
- package/dist/service/agent-profile.js +5 -6
- package/dist/service/multiclaws-service.d.ts +4 -2
- package/dist/service/multiclaws-service.js +102 -52
- package/package.json +1 -1
- package/skills/multiclaws/SKILL.md +36 -24
package/dist/index.js
CHANGED
|
@@ -625,16 +625,25 @@ const plugin = {
|
|
|
625
625
|
api.on("gateway_stop", () => {
|
|
626
626
|
structured.logger.info("[multiclaws] gateway_stop observed");
|
|
627
627
|
});
|
|
628
|
-
// Collect notification targets from incoming messages
|
|
628
|
+
// Collect notification targets from incoming messages
|
|
629
629
|
api.on("message_received", (_event, ctx) => {
|
|
630
|
-
if (service
|
|
630
|
+
if (!service || !ctx.channelId)
|
|
631
|
+
return;
|
|
632
|
+
if (ctx.channelId === "webchat" && ctx.conversationId) {
|
|
633
|
+
// WebChat: use conversationId with the message tool
|
|
634
|
+
service.addNotificationTarget(`webchat:${ctx.conversationId}`, { type: "channel", conversationId: ctx.conversationId });
|
|
635
|
+
}
|
|
636
|
+
else if (ctx.channelId !== "webchat" && ctx.conversationId) {
|
|
637
|
+
// External channels (Telegram, etc.)
|
|
631
638
|
service.addNotificationTarget(`${ctx.channelId}:${ctx.conversationId}`, { type: "channel", conversationId: ctx.conversationId });
|
|
632
639
|
}
|
|
633
640
|
});
|
|
634
641
|
// Inject onboarding prompt when profile is pending first-run setup
|
|
635
|
-
// Also capture web session targets for notifications
|
|
642
|
+
// Also capture web session targets for notifications (skip internal sub-agent sessions)
|
|
643
|
+
const INTERNAL_SESSION_PREFIXES = ["delegate-", "a2a-"];
|
|
636
644
|
api.on("before_prompt_build", async (_event, ctx) => {
|
|
637
|
-
if (service && ctx.sessionKey
|
|
645
|
+
if (service && ctx.sessionKey &&
|
|
646
|
+
!INTERNAL_SESSION_PREFIXES.some((p) => ctx.sessionKey.startsWith(p))) {
|
|
638
647
|
service.addNotificationTarget(`web:${ctx.sessionKey}`, { type: "web", sessionKey: ctx.sessionKey });
|
|
639
648
|
}
|
|
640
649
|
if (!service)
|
|
@@ -648,22 +657,32 @@ const plugin = {
|
|
|
648
657
|
|
|
649
658
|
multiclaws 插件刚安装,需要在本次对话中完成以下初始化流程(优先处理,不管用户说了什么):
|
|
650
659
|
|
|
651
|
-
1.
|
|
652
|
-
-
|
|
653
|
-
-
|
|
654
|
-
-
|
|
655
|
-
- 时区和语言
|
|
660
|
+
1. **确认用户名**(需要用户明确回答):
|
|
661
|
+
- 询问用户希望使用什么名字
|
|
662
|
+
- 这个名字会以「{名字} 的 OpenClaw」格式展示给团队其他成员
|
|
663
|
+
- 例如用户叫「小明」,则其他成员看到的是「小明 的 OpenClaw」
|
|
656
664
|
|
|
657
|
-
2.
|
|
658
|
-
|
|
659
|
-
- **Bio**:展示生成的 bio,询问是否需要修改
|
|
660
|
-
- **网络情况**:告知用户「所有实例通过 FRP 隧道通信,需在插件配置中设置 tunnel 字段(包含 frps 服务器地址、端口、token 和可用端口范围),frpc 会自动下载安装」,无需用户回答
|
|
665
|
+
2. **自动生成 bio**(无需用户确认,直接生成并保存):
|
|
666
|
+
扫描当前环境,生成详细的 bio(markdown 格式)。bio 是给其他 AI 智能体看的,用来判断这个智能体能做什么任务、能访问什么数据。必须准确反映实际能力,具体检查:
|
|
661
667
|
|
|
662
|
-
|
|
668
|
+
- **已安装的工具(tools)**:列出所有可用的工具名称,说明能执行哪些类型的操作(如文件读写、代码执行、网络请求等)
|
|
669
|
+
- **已安装的 skills**:列出 skill 名称和功能描述
|
|
670
|
+
- **已连接的渠道(channels)**:检查是否连接了 Telegram、Discord、Gmail、Slack、微信等,列出具体渠道名称
|
|
671
|
+
- **已安装的插件(plugins)**:列出所有已加载的插件及其主要功能
|
|
672
|
+
- **工作区内容**:检查当前工作目录(pwd)下的项目结构,包括:
|
|
673
|
+
- git 仓库信息(仓库名、分支)
|
|
674
|
+
- 主要编程语言和框架
|
|
675
|
+
- 项目名称和用途(从 README 或 package.json 推断)
|
|
676
|
+
- **可访问的数据源**:检查是否有日历(Google Calendar 等)、邮件(Gmail 等)、Notion、数据库、API 等数据访问能力
|
|
677
|
+
- **系统信息**:时区、操作系统、语言偏好
|
|
678
|
+
|
|
679
|
+
3. 用户确认名字后,立即调用 \`multiclaws_profile_set(ownerName="确认后的名字", bio="生成的bio")\` 保存。
|
|
663
680
|
|
|
664
681
|
4. 调用 \`multiclaws_profile_clear_pending_review()\` 完成初始化。
|
|
665
682
|
|
|
666
|
-
|
|
683
|
+
5. **网络情况告知**(无需用户回答):告知用户「所有实例通过 FRP 隧道通信,需在插件配置中设置 tunnel 字段(包含 frps 服务器地址、端口、token 和可用端口范围),frpc 会自动下载安装」
|
|
684
|
+
|
|
685
|
+
**注意**:只有名字需要用户明确确认;bio 自动生成直接保存无需确认;网络情况仅告知无需回答。`,
|
|
667
686
|
};
|
|
668
687
|
}
|
|
669
688
|
catch (err) {
|
|
@@ -37,33 +37,43 @@ class OpenClawAgentExecutor {
|
|
|
37
37
|
async execute(context, eventBus) {
|
|
38
38
|
const taskText = extractTextFromMessage(context.userMessage);
|
|
39
39
|
const taskId = context.taskId;
|
|
40
|
+
this.logger.info(`[a2a-adapter] ▶ execute() called — taskId=${taskId}, textLen=${taskText.length}`);
|
|
40
41
|
if (!taskText.trim()) {
|
|
42
|
+
this.logger.warn(`[a2a-adapter] ✗ empty task text, rejecting — taskId=${taskId}`);
|
|
41
43
|
this.publishMessage(eventBus, "Error: empty task received.");
|
|
42
44
|
eventBus.finished();
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
|
-
const
|
|
47
|
+
const meta = context.userMessage.metadata ?? {};
|
|
48
|
+
const fromAgentUrl = meta.agentUrl ?? "unknown";
|
|
49
|
+
const fromAgentName = meta.agentName || fromAgentUrl;
|
|
50
|
+
this.logger.info(`[a2a-adapter] task ${taskId} from ${fromAgentName} (${fromAgentUrl}): ${taskText.slice(0, 120)}`);
|
|
46
51
|
this.taskTracker.create({
|
|
47
|
-
fromPeerId:
|
|
52
|
+
fromPeerId: fromAgentUrl,
|
|
48
53
|
toPeerId: "local",
|
|
49
54
|
task: taskText,
|
|
50
55
|
});
|
|
56
|
+
this.logger.info(`[a2a-adapter] task ${taskId} tracked`);
|
|
51
57
|
if (!this.gatewayConfig) {
|
|
52
|
-
this.logger.error(
|
|
58
|
+
this.logger.error(`[a2a-adapter] ✗ gateway config not available — taskId=${taskId}`);
|
|
53
59
|
this.taskTracker.update(taskId, { status: "failed", error: "gateway config not available" });
|
|
54
60
|
this.publishMessage(eventBus, "Error: gateway config not available, cannot execute task.");
|
|
55
61
|
eventBus.finished();
|
|
56
62
|
return;
|
|
57
63
|
}
|
|
58
64
|
// Notify local user about incoming task
|
|
59
|
-
|
|
65
|
+
const notifyTargets = this.getNotificationTargets();
|
|
66
|
+
this.logger.info(`[a2a-adapter] task ${taskId} notifying user (${notifyTargets.size} targets)`);
|
|
67
|
+
void this.notifyUser(`📨 收到来自 **${fromAgentName}** 的委派任务:${taskText.slice(0, 200)}`);
|
|
60
68
|
try {
|
|
61
|
-
this.logger.info(`[a2a-adapter] executing task ${taskId}: ${taskText.slice(0, 100)}`);
|
|
62
69
|
// Create a promise that resolves when sub-agent calls multiclaws_a2a_callback
|
|
63
|
-
const
|
|
70
|
+
const timeoutMs = 180_000;
|
|
71
|
+
const resultPromise = this.createCallback(taskId, timeoutMs);
|
|
72
|
+
this.logger.info(`[a2a-adapter] task ${taskId} callback registered (timeout=${timeoutMs / 1000}s)`);
|
|
64
73
|
// Spawn the subagent with instructions to call back when done
|
|
65
74
|
const prompt = buildA2ASubagentPrompt(taskId, taskText);
|
|
66
|
-
|
|
75
|
+
this.logger.info(`[a2a-adapter] task ${taskId} spawning sub-agent via sessions_spawn (cwd=${this.cwd}, sessionKey=a2a-${taskId})`);
|
|
76
|
+
const spawnResult = await (0, gateway_client_1.invokeGatewayTool)({
|
|
67
77
|
gateway: this.gatewayConfig,
|
|
68
78
|
tool: "sessions_spawn",
|
|
69
79
|
args: {
|
|
@@ -74,20 +84,22 @@ class OpenClawAgentExecutor {
|
|
|
74
84
|
sessionKey: `a2a-${taskId}`,
|
|
75
85
|
timeoutMs: 15_000,
|
|
76
86
|
});
|
|
77
|
-
this.logger.info(`[a2a-adapter] task ${taskId} spawned
|
|
87
|
+
this.logger.info(`[a2a-adapter] task ${taskId} sub-agent spawned — result=${JSON.stringify(spawnResult).slice(0, 200)}`);
|
|
88
|
+
this.logger.info(`[a2a-adapter] task ${taskId} waiting for callback from sub-agent...`);
|
|
78
89
|
// Wait for the sub-agent to call back
|
|
79
90
|
const output = await resultPromise;
|
|
80
91
|
// Return result
|
|
81
92
|
this.taskTracker.update(taskId, { status: "completed", result: output });
|
|
82
|
-
this.logger.info(`[a2a-adapter] task ${taskId} completed
|
|
93
|
+
this.logger.info(`[a2a-adapter] ✓ task ${taskId} completed — resultLen=${output.length}, preview=${output.slice(0, 120)}`);
|
|
83
94
|
this.publishMessage(eventBus, output || "Task completed with no output.");
|
|
84
95
|
}
|
|
85
96
|
catch (err) {
|
|
86
97
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
87
|
-
this.logger.error(`[a2a-adapter] task
|
|
98
|
+
this.logger.error(`[a2a-adapter] ✗ task ${taskId} failed: ${errorMsg}`);
|
|
88
99
|
this.taskTracker.update(taskId, { status: "failed", error: errorMsg });
|
|
89
100
|
this.publishMessage(eventBus, `Error: ${errorMsg}`);
|
|
90
101
|
}
|
|
102
|
+
this.logger.info(`[a2a-adapter] task ${taskId} eventBus.finished()`);
|
|
91
103
|
eventBus.finished();
|
|
92
104
|
}
|
|
93
105
|
/**
|
|
@@ -96,10 +108,13 @@ class OpenClawAgentExecutor {
|
|
|
96
108
|
*/
|
|
97
109
|
resolveCallback(taskId, result) {
|
|
98
110
|
const pending = this.pendingCallbacks.get(taskId);
|
|
99
|
-
if (!pending)
|
|
111
|
+
if (!pending) {
|
|
112
|
+
this.logger.warn(`[a2a-adapter] resolveCallback: no pending callback for taskId=${taskId} (may have timed out)`);
|
|
100
113
|
return false;
|
|
114
|
+
}
|
|
101
115
|
clearTimeout(pending.timer);
|
|
102
116
|
this.pendingCallbacks.delete(taskId);
|
|
117
|
+
this.logger.info(`[a2a-adapter] resolveCallback: taskId=${taskId} resolved — resultLen=${result.length}`);
|
|
103
118
|
pending.resolve(result);
|
|
104
119
|
return true;
|
|
105
120
|
}
|
|
@@ -111,6 +126,7 @@ class OpenClawAgentExecutor {
|
|
|
111
126
|
clearTimeout(pending.timer);
|
|
112
127
|
this.pendingCallbacks.delete(taskId);
|
|
113
128
|
pending.reject(new Error("canceled"));
|
|
129
|
+
this.logger.info(`[a2a-adapter] cancelTask: pending callback rejected for taskId=${taskId}`);
|
|
114
130
|
}
|
|
115
131
|
this.taskTracker.update(taskId, { status: "failed", error: "canceled" });
|
|
116
132
|
this.publishMessage(eventBus, "Task was canceled.");
|
|
@@ -127,6 +143,7 @@ class OpenClawAgentExecutor {
|
|
|
127
143
|
return new Promise((resolve, reject) => {
|
|
128
144
|
const timer = setTimeout(() => {
|
|
129
145
|
this.pendingCallbacks.delete(taskId);
|
|
146
|
+
this.logger.error(`[a2a-adapter] ✗ task ${taskId} callback timed out after ${timeoutMs / 1000}s — pending callbacks remaining: ${this.pendingCallbacks.size}`);
|
|
130
147
|
reject(new Error(`task timed out after ${timeoutMs / 1000}s waiting for sub-agent callback`));
|
|
131
148
|
}, timeoutMs);
|
|
132
149
|
this.pendingCallbacks.set(taskId, { resolve, reject, timer });
|
|
@@ -135,21 +152,36 @@ class OpenClawAgentExecutor {
|
|
|
135
152
|
/** Send a notification to all known targets. Individual failures are silently ignored. */
|
|
136
153
|
async notifyUser(message) {
|
|
137
154
|
const targets = this.getNotificationTargets();
|
|
138
|
-
if (!this.gatewayConfig || targets.size === 0)
|
|
155
|
+
if (!this.gatewayConfig || targets.size === 0) {
|
|
156
|
+
this.logger.info(`[a2a-adapter] notifyUser: skipped (gateway=${!!this.gatewayConfig}, targets=${targets.size})`);
|
|
139
157
|
return;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
}
|
|
159
|
+
const results = await Promise.allSettled([...targets.entries()].map(async ([key, target]) => {
|
|
160
|
+
this.logger.info(`[a2a-adapter] notifyUser: sending to ${key} (type=${target.type})`);
|
|
161
|
+
try {
|
|
162
|
+
await (target.type === "channel"
|
|
163
|
+
? (0, gateway_client_1.invokeGatewayTool)({
|
|
164
|
+
gateway: this.gatewayConfig,
|
|
165
|
+
tool: "message",
|
|
166
|
+
args: { action: "send", target: target.conversationId, message },
|
|
167
|
+
timeoutMs: 5_000,
|
|
168
|
+
})
|
|
169
|
+
: (0, gateway_client_1.invokeGatewayTool)({
|
|
170
|
+
gateway: this.gatewayConfig,
|
|
171
|
+
tool: "chat.send",
|
|
172
|
+
args: { sessionKey: target.sessionKey, message },
|
|
173
|
+
timeoutMs: 5_000,
|
|
174
|
+
}));
|
|
175
|
+
this.logger.info(`[a2a-adapter] notifyUser: sent to ${key} ✓`);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
this.logger.warn(`[a2a-adapter] notifyUser: failed to send to ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
}));
|
|
182
|
+
const ok = results.filter((r) => r.status === "fulfilled").length;
|
|
183
|
+
const fail = results.filter((r) => r.status === "rejected").length;
|
|
184
|
+
this.logger.info(`[a2a-adapter] notifyUser: done (${ok} ok, ${fail} failed)`);
|
|
153
185
|
}
|
|
154
186
|
publishMessage(eventBus, text) {
|
|
155
187
|
const message = {
|
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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;
|
|
@@ -133,10 +133,12 @@ export declare class MulticlawsService extends EventEmitter {
|
|
|
133
133
|
private extractArtifactText;
|
|
134
134
|
/** Fetch with up to 2 retries and exponential backoff. */
|
|
135
135
|
private fetchWithRetry;
|
|
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
|
-
/**
|
|
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
|
-
|
|
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:
|
|
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",
|
|
@@ -256,26 +251,32 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
256
251
|
/* Task delegation */
|
|
257
252
|
/* ---------------------------------------------------------------- */
|
|
258
253
|
async delegateTask(params) {
|
|
259
|
-
this.log("info", `delegateTask(agentUrl=${params.agentUrl},
|
|
254
|
+
this.log("info", `[delegate] ▶ delegateTask(agentUrl=${params.agentUrl}, taskLen=${params.task.length})`);
|
|
255
|
+
this.log("info", `[delegate] task preview: ${params.task.slice(0, 120)}`);
|
|
260
256
|
await this.requireCompleteProfile();
|
|
261
257
|
const agentRecord = await this.agentRegistry.get(params.agentUrl);
|
|
262
258
|
if (!agentRecord) {
|
|
263
|
-
this.log("warn", `
|
|
259
|
+
this.log("warn", `[delegate] ✗ unknown agent: ${params.agentUrl}`);
|
|
264
260
|
return { status: "failed", error: `unknown agent: ${params.agentUrl}` };
|
|
265
261
|
}
|
|
262
|
+
this.log("info", `[delegate] agent found: ${agentRecord.name} (${agentRecord.url})`);
|
|
266
263
|
const track = this.taskTracker.create({
|
|
267
264
|
fromPeerId: "local",
|
|
268
265
|
toPeerId: params.agentUrl,
|
|
269
266
|
task: params.task,
|
|
270
267
|
});
|
|
271
268
|
this.taskTracker.update(track.taskId, { status: "running" });
|
|
269
|
+
this.log("info", `[delegate] task tracked: ${track.taskId}, status=running`);
|
|
272
270
|
try {
|
|
271
|
+
this.log("info", `[delegate] ${track.taskId} creating A2A client for ${agentRecord.url}`);
|
|
273
272
|
const client = await this.createA2AClient(agentRecord);
|
|
273
|
+
this.log("info", `[delegate] ${track.taskId} A2A client created, starting fire-and-forget send`);
|
|
274
274
|
// Fire-and-forget execution: keep running in the background so that
|
|
275
275
|
// the gateway call can return quickly and the task can outlive
|
|
276
276
|
// the gateway's HTTP timeout.
|
|
277
277
|
void (async () => {
|
|
278
278
|
try {
|
|
279
|
+
this.log("info", `[delegate] ${track.taskId} sending A2A message (background)...`);
|
|
279
280
|
const result = await client.sendMessage({
|
|
280
281
|
message: {
|
|
281
282
|
kind: "message",
|
|
@@ -284,22 +285,24 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
284
285
|
messageId: track.taskId,
|
|
285
286
|
},
|
|
286
287
|
});
|
|
288
|
+
this.log("info", `[delegate] ${track.taskId} A2A response received (background)`);
|
|
287
289
|
this.processTaskResult(track.taskId, result);
|
|
288
290
|
}
|
|
289
291
|
catch (err) {
|
|
290
292
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
291
293
|
this.taskTracker.update(track.taskId, { status: "failed", error: errorMsg });
|
|
292
|
-
this.log("
|
|
294
|
+
this.log("error", `[delegate] ✗ ${track.taskId} background send failed: ${errorMsg}`);
|
|
293
295
|
}
|
|
294
296
|
})();
|
|
295
297
|
// Return immediately so that gateway tool invocations are fast and
|
|
296
298
|
// do not depend on the remote agent's total execution time.
|
|
299
|
+
this.log("info", `[delegate] ${track.taskId} returned immediately (fire-and-forget)`);
|
|
297
300
|
return { taskId: track.taskId, status: "running" };
|
|
298
301
|
}
|
|
299
302
|
catch (err) {
|
|
300
303
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
301
304
|
this.taskTracker.update(track.taskId, { status: "failed", error: errorMsg });
|
|
302
|
-
this.log("error", `
|
|
305
|
+
this.log("error", `[delegate] ✗ ${track.taskId} failed: ${errorMsg}`);
|
|
303
306
|
return { taskId: track.taskId, status: "failed", error: errorMsg };
|
|
304
307
|
}
|
|
305
308
|
}
|
|
@@ -308,37 +311,47 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
308
311
|
* Used by sub-agents internally via the multiclaws_delegate_send tool.
|
|
309
312
|
*/
|
|
310
313
|
async delegateTaskSync(params) {
|
|
311
|
-
this.log("info", `delegateTaskSync(agentUrl=${params.agentUrl},
|
|
314
|
+
this.log("info", `[delegate-sync] ▶ delegateTaskSync(agentUrl=${params.agentUrl}, taskLen=${params.task.length})`);
|
|
315
|
+
this.log("info", `[delegate-sync] task preview: ${params.task.slice(0, 120)}`);
|
|
312
316
|
await this.requireCompleteProfile();
|
|
313
317
|
const agentRecord = await this.agentRegistry.get(params.agentUrl);
|
|
314
318
|
if (!agentRecord) {
|
|
315
|
-
this.log("warn", `
|
|
319
|
+
this.log("warn", `[delegate-sync] ✗ unknown agent: ${params.agentUrl}`);
|
|
316
320
|
return { status: "failed", error: `unknown agent: ${params.agentUrl}` };
|
|
317
321
|
}
|
|
322
|
+
this.log("info", `[delegate-sync] agent found: ${agentRecord.name} (${agentRecord.url})`);
|
|
318
323
|
const track = this.taskTracker.create({
|
|
319
324
|
fromPeerId: "local",
|
|
320
325
|
toPeerId: params.agentUrl,
|
|
321
326
|
task: params.task,
|
|
322
327
|
});
|
|
323
328
|
this.taskTracker.update(track.taskId, { status: "running" });
|
|
329
|
+
this.log("info", `[delegate-sync] task tracked: ${track.taskId}, status=running`);
|
|
324
330
|
try {
|
|
331
|
+
this.log("info", `[delegate-sync] ${track.taskId} creating A2A client for ${agentRecord.url}`);
|
|
325
332
|
const client = await this.createA2AClient(agentRecord);
|
|
333
|
+
this.log("info", `[delegate-sync] ${track.taskId} sending A2A message (sync, with metadata: selfUrl=${this.selfUrl}, selfName=${this.agentCard?.name ?? "unknown"})...`);
|
|
326
334
|
const result = await client.sendMessage({
|
|
327
335
|
message: {
|
|
328
336
|
kind: "message",
|
|
329
337
|
role: "user",
|
|
330
338
|
parts: [{ kind: "text", text: params.task }],
|
|
331
339
|
messageId: track.taskId,
|
|
340
|
+
metadata: {
|
|
341
|
+
agentUrl: this.selfUrl,
|
|
342
|
+
agentName: this.agentCard?.name ?? "unknown",
|
|
343
|
+
},
|
|
332
344
|
},
|
|
333
345
|
});
|
|
346
|
+
this.log("info", `[delegate-sync] ${track.taskId} A2A response received`);
|
|
334
347
|
const taskResult = this.processTaskResult(track.taskId, result);
|
|
335
|
-
this.log("
|
|
348
|
+
this.log("info", `[delegate-sync] ✓ ${track.taskId} completed — status=${taskResult.status}, outputLen=${taskResult.output?.length ?? 0}`);
|
|
336
349
|
return taskResult;
|
|
337
350
|
}
|
|
338
351
|
catch (err) {
|
|
339
352
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
340
353
|
this.taskTracker.update(track.taskId, { status: "failed", error: errorMsg });
|
|
341
|
-
this.log("error", `
|
|
354
|
+
this.log("error", `[delegate-sync] ✗ ${track.taskId} failed: ${errorMsg}`);
|
|
342
355
|
return { taskId: track.taskId, status: "failed", error: errorMsg };
|
|
343
356
|
}
|
|
344
357
|
}
|
|
@@ -348,26 +361,30 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
348
361
|
* reports results back to the user via the message tool.
|
|
349
362
|
*/
|
|
350
363
|
async spawnDelegation(params) {
|
|
351
|
-
this.log("info", `spawnDelegation(agentUrl=${params.agentUrl},
|
|
364
|
+
this.log("info", `[spawn-delegate] ▶ spawnDelegation(agentUrl=${params.agentUrl}, taskLen=${params.task.length})`);
|
|
365
|
+
this.log("info", `[spawn-delegate] task preview: ${params.task.slice(0, 120)}`);
|
|
352
366
|
await this.requireCompleteProfile();
|
|
353
367
|
const agent = await this.agentRegistry.get(params.agentUrl);
|
|
354
368
|
if (!agent) {
|
|
355
|
-
this.log("warn", `
|
|
369
|
+
this.log("warn", `[spawn-delegate] ✗ unknown agent: ${params.agentUrl}`);
|
|
356
370
|
throw new Error(`unknown agent: ${params.agentUrl}`);
|
|
357
371
|
}
|
|
372
|
+
this.log("info", `[spawn-delegate] agent found: ${agent.name} (${agent.url})`);
|
|
358
373
|
if (!this.gatewayConfig) {
|
|
359
|
-
this.log("error",
|
|
374
|
+
this.log("error", `[spawn-delegate] ✗ gateway config not available`);
|
|
360
375
|
throw new Error("gateway config not available — cannot spawn sub-agent");
|
|
361
376
|
}
|
|
362
377
|
const prompt = buildDelegationPrompt(agent, params.task);
|
|
363
|
-
|
|
378
|
+
const sessionKey = `delegate-${Date.now()}`;
|
|
379
|
+
this.log("info", `[spawn-delegate] spawning sub-agent via sessions_spawn (cwd=${this.resolvedCwd}, sessionKey=${sessionKey}, promptLen=${prompt.length})`);
|
|
380
|
+
const spawnResult = await (0, gateway_client_1.invokeGatewayTool)({
|
|
364
381
|
gateway: this.gatewayConfig,
|
|
365
382
|
tool: "sessions_spawn",
|
|
366
383
|
args: { task: prompt, mode: "run", cwd: this.resolvedCwd },
|
|
367
|
-
sessionKey
|
|
384
|
+
sessionKey,
|
|
368
385
|
timeoutMs: 15_000,
|
|
369
386
|
});
|
|
370
|
-
this.log("info", `
|
|
387
|
+
this.log("info", `[spawn-delegate] ✓ sub-agent spawned for ${agent.name} — result=${JSON.stringify(spawnResult).slice(0, 200)}`);
|
|
371
388
|
return { message: `已启动子 agent 向 ${agent.name} 委派任务` };
|
|
372
389
|
}
|
|
373
390
|
getTaskStatus(taskId) {
|
|
@@ -385,8 +402,8 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
385
402
|
*/
|
|
386
403
|
async requireCompleteProfile() {
|
|
387
404
|
const profile = await this.profileStore.load();
|
|
388
|
-
if (!profile.ownerName?.trim()
|
|
389
|
-
throw new Error("档案未完成设置。请先调用 multiclaws_profile_set(ownerName=\"你的名字\"
|
|
405
|
+
if (!profile.ownerName?.trim()) {
|
|
406
|
+
throw new Error("档案未完成设置。请先调用 multiclaws_profile_set(ownerName=\"你的名字\") 设置用户名后再继续。");
|
|
390
407
|
}
|
|
391
408
|
}
|
|
392
409
|
async setProfile(patch) {
|
|
@@ -408,7 +425,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
408
425
|
if (this.agentCard) {
|
|
409
426
|
this.agentCard.description = this.profileDescription;
|
|
410
427
|
if (profile.ownerName?.trim()) {
|
|
411
|
-
this.agentCard.name = profile.ownerName.trim();
|
|
428
|
+
this.agentCard.name = (0, agent_profile_1.formatAgentCardName)(profile.ownerName.trim());
|
|
412
429
|
}
|
|
413
430
|
}
|
|
414
431
|
}
|
|
@@ -462,7 +479,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
462
479
|
const team = await this.teamStore.createTeam({
|
|
463
480
|
teamName: name,
|
|
464
481
|
selfUrl: this.selfUrl,
|
|
465
|
-
selfName: this.
|
|
482
|
+
selfName: this.getFormattedName(),
|
|
466
483
|
selfDescription: this.profileDescription,
|
|
467
484
|
});
|
|
468
485
|
this.log("info", `team created: ${team.teamId} (${team.teamName})`);
|
|
@@ -512,7 +529,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
512
529
|
// 2. Announce self to seed (seed broadcasts to others)
|
|
513
530
|
const selfMember = {
|
|
514
531
|
url: this.selfUrl,
|
|
515
|
-
name: this.
|
|
532
|
+
name: this.getFormattedName(),
|
|
516
533
|
description: this.profileDescription,
|
|
517
534
|
joinedAtMs: Date.now(),
|
|
518
535
|
};
|
|
@@ -566,7 +583,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
566
583
|
const selfNormalized = this.selfUrl.replace(/\/+$/, "");
|
|
567
584
|
const selfMember = {
|
|
568
585
|
url: this.selfUrl,
|
|
569
|
-
name: this.
|
|
586
|
+
name: this.getFormattedName(),
|
|
570
587
|
joinedAtMs: 0,
|
|
571
588
|
};
|
|
572
589
|
const others = team.members.filter((m) => m.url.replace(/\/+$/, "") !== selfNormalized);
|
|
@@ -772,12 +789,12 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
772
789
|
try {
|
|
773
790
|
const teams = await this.teamStore.listTeams();
|
|
774
791
|
const selfNormalized = this.selfUrl.replace(/\/+$/, "");
|
|
775
|
-
const
|
|
792
|
+
const agentName = this.getFormattedName();
|
|
776
793
|
for (const team of teams) {
|
|
777
794
|
// Update self in team store
|
|
778
795
|
await this.teamStore.addMember(team.teamId, {
|
|
779
796
|
url: this.selfUrl,
|
|
780
|
-
name:
|
|
797
|
+
name: agentName,
|
|
781
798
|
description: this.profileDescription,
|
|
782
799
|
joinedAtMs: Date.now(),
|
|
783
800
|
});
|
|
@@ -789,7 +806,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
789
806
|
headers: { "Content-Type": "application/json" },
|
|
790
807
|
body: JSON.stringify({
|
|
791
808
|
url: this.selfUrl,
|
|
792
|
-
name:
|
|
809
|
+
name: agentName,
|
|
793
810
|
description: this.profileDescription,
|
|
794
811
|
}),
|
|
795
812
|
}).catch(() => {
|
|
@@ -858,24 +875,27 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
858
875
|
* return the final Task or Message as soon as B signals completion.
|
|
859
876
|
*/
|
|
860
877
|
processTaskResult(trackId, result) {
|
|
861
|
-
this.log("
|
|
878
|
+
this.log("info", `[process-result] processing result for ${trackId}, resultType=${("status" in result && result.status) ? "Task" : "Message"}`);
|
|
862
879
|
try {
|
|
863
880
|
if ("status" in result && result.status) {
|
|
864
881
|
const task = result;
|
|
865
882
|
const state = task.status?.state ?? "unknown";
|
|
866
883
|
const output = this.extractArtifactText(task);
|
|
884
|
+
this.log("info", `[process-result] ${trackId} Task response — state=${state}, outputLen=${output.length}, preview=${output.slice(0, 120)}`);
|
|
867
885
|
if (state === "completed") {
|
|
868
886
|
this.taskTracker.update(trackId, { status: "completed", result: output });
|
|
887
|
+
this.log("info", `[process-result] ✓ ${trackId} marked completed`);
|
|
869
888
|
}
|
|
870
889
|
else if (state === "failed") {
|
|
871
890
|
this.taskTracker.update(trackId, { status: "failed", error: output || "remote task failed" });
|
|
891
|
+
this.log("warn", `[process-result] ✗ ${trackId} marked failed — error=${output || "remote task failed"}`);
|
|
872
892
|
}
|
|
873
893
|
else {
|
|
874
894
|
// For any other state (unknown, working, etc.), mark as failed to avoid
|
|
875
895
|
// tasks stuck in "running" forever until TTL prune.
|
|
876
896
|
this.taskTracker.update(trackId, { status: "failed", error: `unexpected remote state: ${state}` });
|
|
897
|
+
this.log("warn", `[process-result] ✗ ${trackId} unexpected state=${state}, marked failed`);
|
|
877
898
|
}
|
|
878
|
-
this.log("debug", `processTaskResult completed, status=${state}`);
|
|
879
899
|
return { taskId: task.id, output, status: state };
|
|
880
900
|
}
|
|
881
901
|
const msg = result;
|
|
@@ -884,11 +904,11 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
884
904
|
.map((p) => p.text)
|
|
885
905
|
.join("\n") ?? "";
|
|
886
906
|
this.taskTracker.update(trackId, { status: "completed", result: text });
|
|
887
|
-
this.log("
|
|
907
|
+
this.log("info", `[process-result] ✓ ${trackId} Message response — completed, textLen=${text.length}, preview=${text.slice(0, 120)}`);
|
|
888
908
|
return { taskId: trackId, output: text, status: "completed" };
|
|
889
909
|
}
|
|
890
910
|
catch (err) {
|
|
891
|
-
this.log("error", `
|
|
911
|
+
this.log("error", `[process-result] ✗ ${trackId} processing failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
892
912
|
throw err;
|
|
893
913
|
}
|
|
894
914
|
}
|
|
@@ -920,11 +940,16 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
920
940
|
}
|
|
921
941
|
throw lastError;
|
|
922
942
|
}
|
|
923
|
-
/**
|
|
943
|
+
/** Resolve a pending A2A callback from sub-agent. */
|
|
924
944
|
resolveA2ACallback(taskId, result) {
|
|
925
|
-
|
|
945
|
+
this.log("info", `[a2a-callback] resolveA2ACallback(taskId=${taskId}, resultLen=${result.length})`);
|
|
946
|
+
if (!this.agentExecutor) {
|
|
947
|
+
this.log("warn", `[a2a-callback] ✗ no agentExecutor available for taskId=${taskId}`);
|
|
926
948
|
return false;
|
|
927
|
-
|
|
949
|
+
}
|
|
950
|
+
const resolved = this.agentExecutor.resolveCallback(taskId, result);
|
|
951
|
+
this.log("info", `[a2a-callback] ${resolved ? "✓" : "✗"} taskId=${taskId} ${resolved ? "resolved" : "no pending callback found"}`);
|
|
952
|
+
return resolved;
|
|
928
953
|
}
|
|
929
954
|
addNotificationTarget(key, target) {
|
|
930
955
|
if (!this.notificationTargets.has(key)) {
|
|
@@ -932,23 +957,48 @@ class MulticlawsService extends node_events_1.EventEmitter {
|
|
|
932
957
|
this.log("debug", `notification target registered: ${key} (total: ${this.notificationTargets.size})`);
|
|
933
958
|
}
|
|
934
959
|
}
|
|
935
|
-
/**
|
|
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. */
|
|
936
965
|
async notifyUser(message) {
|
|
937
|
-
|
|
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");
|
|
938
969
|
return;
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
+
}
|
|
952
1002
|
}
|
|
953
1003
|
log(level, message) {
|
|
954
1004
|
this.options.logger?.[level]?.(`[multiclaws] ${message}`);
|
package/package.json
CHANGED
|
@@ -13,35 +13,43 @@ MultiClaws 让多个 OpenClaw 实例通过 [A2A(Agent-to-Agent)](https://goo
|
|
|
13
13
|
|
|
14
14
|
当 hook 注入了初始化任务时,按以下步骤执行:
|
|
15
15
|
|
|
16
|
-
1.
|
|
17
|
-
|
|
18
|
-
- 已连接的渠道(Telegram、Discord、Gmail 等)
|
|
19
|
-
- 工作区内容(git 仓库、关键文件、项目目录)
|
|
20
|
-
- 已安装的插件
|
|
21
|
-
- 时区、语言等上下文
|
|
16
|
+
1. **确认用户名**(需用户明确回答):
|
|
17
|
+
询问用户希望使用什么名字。名字会以「{名字} 的 OpenClaw」格式展示给团队成员。
|
|
22
18
|
|
|
23
|
-
2.
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
19
|
+
2. **自动生成 bio**(无需用户确认,直接保存),扫描并检查:
|
|
20
|
+
- **可用工具(tools)**:列出所有工具名称,说明能执行的操作类型
|
|
21
|
+
- **已安装 skills**:列出 skill 名称和功能
|
|
22
|
+
- **已连接渠道**:Telegram、Discord、Gmail、Slack、微信等,列出具体渠道
|
|
23
|
+
- **已安装插件**:列出所有插件及主要功能
|
|
24
|
+
- **工作区**:当前目录下的项目(git 仓库名、语言、框架、项目用途)
|
|
25
|
+
- **数据访问**:日历、邮件、Notion、数据库等
|
|
26
|
+
- **系统信息**:时区、OS、语言
|
|
27
27
|
|
|
28
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
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
|
-
如果 `
|
|
60
|
-
1.
|
|
61
|
-
2.
|
|
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
|
-
-
|
|
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
|
|
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
|
|
148
|
+
2.(如果 ownerName 为空)确认名字,自动生成 bio
|
|
137
149
|
3. multiclaws_team_join(inviteCode="mc:xxxx")
|
|
138
150
|
→ 自动同步所有团队成员
|
|
139
151
|
```
|