multiclaws 0.4.19 → 0.4.21
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.d.ts +2 -1
- package/dist/gateway/handlers.js +63 -13
- package/dist/index.js +227 -83
- package/dist/infra/logger.d.ts +1 -0
- package/dist/infra/logger.js +7 -0
- package/dist/service/a2a-adapter.js +10 -2
- package/dist/service/agent-profile.d.ts +4 -1
- package/dist/service/agent-profile.js +30 -9
- package/dist/service/agent-registry.d.ts +4 -1
- package/dist/service/agent-registry.js +76 -45
- package/dist/service/multiclaws-service.d.ts +20 -0
- package/dist/service/multiclaws-service.js +487 -255
- package/dist/service/session-store.d.ts +3 -0
- package/dist/service/session-store.js +4 -1
- package/dist/task/tracker.d.ts +3 -0
- package/dist/task/tracker.js +6 -1
- package/dist/team/team-store.d.ts +4 -1
- package/dist/team/team-store.js +116 -67
- package/package.json +8 -8
- package/skills/multiclaws/SKILL.md +16 -80
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BasicLogger } from "../infra/logger";
|
|
1
2
|
export type SessionStatus = "active" | "input-required" | "completed" | "failed" | "canceled";
|
|
2
3
|
export type SessionMessage = {
|
|
3
4
|
role: "user" | "agent";
|
|
@@ -20,11 +21,13 @@ export type ConversationSession = {
|
|
|
20
21
|
export declare class SessionStore {
|
|
21
22
|
private readonly filePath;
|
|
22
23
|
private readonly ttlMs;
|
|
24
|
+
private readonly logger?;
|
|
23
25
|
private store;
|
|
24
26
|
private persistPending;
|
|
25
27
|
constructor(opts: {
|
|
26
28
|
filePath: string;
|
|
27
29
|
ttlMs?: number;
|
|
30
|
+
logger?: BasicLogger;
|
|
28
31
|
});
|
|
29
32
|
create(params: {
|
|
30
33
|
agentUrl: string;
|
|
@@ -31,11 +31,13 @@ function normalizeStore(raw) {
|
|
|
31
31
|
class SessionStore {
|
|
32
32
|
filePath;
|
|
33
33
|
ttlMs;
|
|
34
|
+
logger;
|
|
34
35
|
store;
|
|
35
36
|
persistPending = false;
|
|
36
37
|
constructor(opts) {
|
|
37
38
|
this.filePath = opts.filePath;
|
|
38
39
|
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
40
|
+
this.logger = opts.logger;
|
|
39
41
|
this.store = this.loadSync();
|
|
40
42
|
}
|
|
41
43
|
create(params) {
|
|
@@ -116,8 +118,9 @@ class SessionStore {
|
|
|
116
118
|
await promises_1.default.writeFile(tmp, JSON.stringify(this.store, null, 2), "utf8");
|
|
117
119
|
await promises_1.default.rename(tmp, this.filePath);
|
|
118
120
|
}
|
|
119
|
-
catch {
|
|
121
|
+
catch (err) {
|
|
120
122
|
// best-effort
|
|
123
|
+
this.logger?.warn?.(`[session-store] persistAsync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
prune() {
|
package/dist/task/tracker.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BasicLogger } from "../infra/logger";
|
|
1
2
|
export type TaskStatus = "queued" | "running" | "completed" | "failed";
|
|
2
3
|
export type TaskRecord = {
|
|
3
4
|
taskId: string;
|
|
@@ -16,12 +17,14 @@ export declare class TaskTracker {
|
|
|
16
17
|
private readonly ttlMs;
|
|
17
18
|
private readonly maxTasks;
|
|
18
19
|
private readonly store;
|
|
20
|
+
private readonly logger?;
|
|
19
21
|
private pruneTimer;
|
|
20
22
|
private persistPending;
|
|
21
23
|
constructor(opts?: {
|
|
22
24
|
ttlMs?: number;
|
|
23
25
|
maxTasks?: number;
|
|
24
26
|
filePath?: string;
|
|
27
|
+
logger?: BasicLogger;
|
|
25
28
|
});
|
|
26
29
|
create(params: {
|
|
27
30
|
fromPeerId: string;
|
package/dist/task/tracker.js
CHANGED
|
@@ -62,12 +62,14 @@ class TaskTracker {
|
|
|
62
62
|
ttlMs;
|
|
63
63
|
maxTasks;
|
|
64
64
|
store;
|
|
65
|
+
logger;
|
|
65
66
|
pruneTimer = null;
|
|
66
67
|
persistPending = false;
|
|
67
68
|
constructor(opts) {
|
|
68
69
|
this.ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
69
70
|
this.maxTasks = opts?.maxTasks ?? MAX_TASKS;
|
|
70
71
|
this.filePath = opts?.filePath ?? ".openclaw/multiclaws/tasks.json";
|
|
72
|
+
this.logger = opts?.logger;
|
|
71
73
|
// Sync load at startup is acceptable (runs once)
|
|
72
74
|
this.store = this.loadStoreSync();
|
|
73
75
|
this.pruneTimer = setInterval(() => this.prune(), PRUNE_INTERVAL_MS);
|
|
@@ -76,6 +78,7 @@ class TaskTracker {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
create(params) {
|
|
81
|
+
this.logger?.debug?.(`[task-tracker] create(from=${params.fromPeerId}, to=${params.toPeerId})`);
|
|
79
82
|
if (this.store.tasks.length >= this.maxTasks) {
|
|
80
83
|
this.prune();
|
|
81
84
|
}
|
|
@@ -95,6 +98,7 @@ class TaskTracker {
|
|
|
95
98
|
};
|
|
96
99
|
this.store.tasks.push(record);
|
|
97
100
|
this.schedulePersist();
|
|
101
|
+
this.logger?.debug?.(`[task-tracker] create completed, taskId=${record.taskId}`);
|
|
98
102
|
return record;
|
|
99
103
|
}
|
|
100
104
|
update(taskId, patch) {
|
|
@@ -153,8 +157,9 @@ class TaskTracker {
|
|
|
153
157
|
await promises_1.default.writeFile(tmp, JSON.stringify(this.store, null, 2), "utf8");
|
|
154
158
|
await promises_1.default.rename(tmp, this.filePath);
|
|
155
159
|
}
|
|
156
|
-
catch {
|
|
160
|
+
catch (err) {
|
|
157
161
|
// best-effort persistence — in-memory state is authoritative
|
|
162
|
+
this.logger?.warn?.(`[task-tracker] persistAsync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
prune() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BasicLogger } from "../infra/logger";
|
|
1
2
|
export type TeamMember = {
|
|
2
3
|
url: string;
|
|
3
4
|
name: string;
|
|
@@ -21,7 +22,9 @@ export declare function encodeInvite(teamId: string, seedUrl: string): string;
|
|
|
21
22
|
export declare function decodeInvite(code: string): InvitePayload;
|
|
22
23
|
export declare class TeamStore {
|
|
23
24
|
private readonly filePath;
|
|
24
|
-
|
|
25
|
+
private readonly logger?;
|
|
26
|
+
constructor(filePath: string, logger?: BasicLogger | undefined);
|
|
27
|
+
private log;
|
|
25
28
|
private readStore;
|
|
26
29
|
createTeam(params: {
|
|
27
30
|
teamName: string;
|
package/dist/team/team-store.js
CHANGED
|
@@ -46,28 +46,43 @@ function decodeInvite(code) {
|
|
|
46
46
|
// ── TeamStore ────────────────────────────────────────────────────────
|
|
47
47
|
class TeamStore {
|
|
48
48
|
filePath;
|
|
49
|
-
|
|
49
|
+
logger;
|
|
50
|
+
constructor(filePath, logger) {
|
|
50
51
|
this.filePath = filePath;
|
|
52
|
+
this.logger = logger;
|
|
53
|
+
}
|
|
54
|
+
log(level, message) {
|
|
55
|
+
const fn = level === "debug" ? this.logger?.debug : this.logger?.[level];
|
|
56
|
+
fn?.(`[team-store] ${message}`);
|
|
51
57
|
}
|
|
52
58
|
async readStore() {
|
|
53
59
|
const store = await (0, json_store_1.readJsonWithFallback)(this.filePath, emptyStore());
|
|
54
60
|
return normalizeStore(store);
|
|
55
61
|
}
|
|
56
62
|
async createTeam(params) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
this.log("debug", `createTeam(name=${params.teamName})`);
|
|
64
|
+
try {
|
|
65
|
+
const result = await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
66
|
+
const store = await this.readStore();
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const record = {
|
|
69
|
+
teamId: (0, node_crypto_1.randomUUID)(),
|
|
70
|
+
teamName: params.teamName,
|
|
71
|
+
selfUrl: params.selfUrl,
|
|
72
|
+
members: [{ url: params.selfUrl, name: params.selfName, description: params.selfDescription, joinedAtMs: now }],
|
|
73
|
+
createdAtMs: now,
|
|
74
|
+
};
|
|
75
|
+
store.teams.push(record);
|
|
76
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
77
|
+
return record;
|
|
78
|
+
});
|
|
79
|
+
this.log("debug", `createTeam completed, teamId=${result.teamId}`);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
this.log("error", `createTeam failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
71
86
|
}
|
|
72
87
|
async getTeam(teamId) {
|
|
73
88
|
const store = await this.readStore();
|
|
@@ -82,65 +97,99 @@ class TeamStore {
|
|
|
82
97
|
return store.teams[0] ?? null;
|
|
83
98
|
}
|
|
84
99
|
async addMember(teamId, member) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
team.members
|
|
94
|
-
if (
|
|
95
|
-
team.members[existing].
|
|
100
|
+
this.log("debug", `addMember(teamId=${teamId}, url=${member.url})`);
|
|
101
|
+
try {
|
|
102
|
+
const result = await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
103
|
+
const store = await this.readStore();
|
|
104
|
+
const team = store.teams.find((t) => t.teamId === teamId);
|
|
105
|
+
if (!team)
|
|
106
|
+
return false;
|
|
107
|
+
const normalizedUrl = member.url.replace(/\/+$/, "");
|
|
108
|
+
const existing = team.members.findIndex((m) => m.url.replace(/\/+$/, "") === normalizedUrl);
|
|
109
|
+
if (existing >= 0) {
|
|
110
|
+
team.members[existing].name = member.name;
|
|
111
|
+
if (member.description !== undefined) {
|
|
112
|
+
team.members[existing].description = member.description;
|
|
113
|
+
}
|
|
114
|
+
team.members[existing].joinedAtMs = member.joinedAtMs;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
team.members.push({ ...member, url: normalizedUrl });
|
|
96
118
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
119
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
120
|
+
return true;
|
|
121
|
+
});
|
|
122
|
+
this.log("debug", `addMember completed, result=${result}`);
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
this.log("error", `addMember failed for teamId=${teamId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
105
129
|
}
|
|
106
130
|
async removeMember(teamId, memberUrl) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
const normalizedUrl = memberUrl.replace(/\/+$/, "");
|
|
132
|
+
this.log("debug", `removeMember(teamId=${teamId}, url=${normalizedUrl})`);
|
|
133
|
+
try {
|
|
134
|
+
const result = await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
135
|
+
const store = await this.readStore();
|
|
136
|
+
const team = store.teams.find((t) => t.teamId === teamId);
|
|
137
|
+
if (!team)
|
|
138
|
+
return false;
|
|
139
|
+
const before = team.members.length;
|
|
140
|
+
team.members = team.members.filter((m) => m.url.replace(/\/+$/, "") !== normalizedUrl);
|
|
141
|
+
if (team.members.length === before)
|
|
142
|
+
return false;
|
|
143
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
this.log("debug", `removeMember completed, found=${result}`);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
this.log("error", `removeMember failed for teamId=${teamId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
120
153
|
}
|
|
121
154
|
async deleteTeam(teamId) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
155
|
+
this.log("debug", `deleteTeam(teamId=${teamId})`);
|
|
156
|
+
try {
|
|
157
|
+
const result = await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
158
|
+
const store = await this.readStore();
|
|
159
|
+
const before = store.teams.length;
|
|
160
|
+
store.teams = store.teams.filter((t) => t.teamId !== teamId);
|
|
161
|
+
if (store.teams.length === before)
|
|
162
|
+
return false;
|
|
163
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
164
|
+
return true;
|
|
165
|
+
});
|
|
166
|
+
this.log("debug", `deleteTeam completed, found=${result}`);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
this.log("error", `deleteTeam failed for teamId=${teamId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
131
173
|
}
|
|
132
174
|
async saveTeam(team) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
store.teams
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
175
|
+
this.log("debug", `saveTeam(teamId=${team.teamId})`);
|
|
176
|
+
try {
|
|
177
|
+
await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
178
|
+
const store = await this.readStore();
|
|
179
|
+
const idx = store.teams.findIndex((t) => t.teamId === team.teamId);
|
|
180
|
+
if (idx >= 0) {
|
|
181
|
+
store.teams[idx] = team;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
store.teams.push(team);
|
|
185
|
+
}
|
|
186
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
this.log("error", `saveTeam failed for teamId=${team.teamId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
144
193
|
}
|
|
145
194
|
}
|
|
146
195
|
exports.TeamStore = TeamStore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multiclaws",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"description": "MultiClaws plugin for OpenClaw collaboration via A2A protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,12 +15,6 @@
|
|
|
15
15
|
"openclaw.plugin.json",
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc -p tsconfig.json",
|
|
20
|
-
"test": "vitest run",
|
|
21
|
-
"test:watch": "vitest",
|
|
22
|
-
"clean": "rm -rf dist"
|
|
23
|
-
},
|
|
24
18
|
"keywords": [
|
|
25
19
|
"openclaw",
|
|
26
20
|
"plugin",
|
|
@@ -52,5 +46,11 @@
|
|
|
52
46
|
"@types/proper-lockfile": "^4.1.4",
|
|
53
47
|
"typescript": "^5.9.2",
|
|
54
48
|
"vitest": "^3.2.4"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.json",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"clean": "rm -rf dist"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
|
@@ -102,7 +102,8 @@ multiclaws_profile_show()
|
|
|
102
102
|
| `multiclaws_agents` | 列出所有已知智能体及 bio | — |
|
|
103
103
|
| `multiclaws_add_agent` | 手动添加远端智能体 | `url`, `apiKey`(可选) |
|
|
104
104
|
| `multiclaws_remove_agent` | 移除已知智能体 | `url` |
|
|
105
|
-
| `multiclaws_delegate` |
|
|
105
|
+
| `multiclaws_delegate` | 委派任务给远端智能体(自动 spawn 子 agent,立即返回) | `agentUrl`, `task` |
|
|
106
|
+
| `multiclaws_delegate_send` | 同步发送任务并等待结果(子 agent 内部使用,勿直接调用) | `agentUrl`, `task` |
|
|
106
107
|
| `multiclaws_task_status` | 查看委派任务状态 | `taskId` |
|
|
107
108
|
|
|
108
109
|
---
|
|
@@ -137,15 +138,15 @@ multiclaws_profile_show()
|
|
|
137
138
|
→ 自动同步所有团队成员
|
|
138
139
|
```
|
|
139
140
|
|
|
140
|
-
###
|
|
141
|
+
### 委派任务
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
所有委派(无论单轮还是多轮)都通过 `multiclaws_delegate` 进行。代码会自动 spawn 子 agent 执行,主 agent 立即返回,无需手动 `sessions_spawn`。
|
|
143
144
|
|
|
144
145
|
```
|
|
145
146
|
1. multiclaws_agents() — 列出智能体,读 bio
|
|
146
147
|
2. 选择 bio 最匹配任务的智能体
|
|
147
148
|
3. multiclaws_delegate(agentUrl="...", task="...")
|
|
148
|
-
|
|
149
|
+
→ 代码自动 spawn 子 agent,子 agent 通过 message 实时汇报结果
|
|
149
150
|
```
|
|
150
151
|
|
|
151
152
|
选择智能体时:
|
|
@@ -153,91 +154,26 @@ multiclaws_profile_show()
|
|
|
153
154
|
- 匹配数据需求(如「检查 API 代码」→ bio 中有该代码库的智能体)
|
|
154
155
|
- 多个匹配时选最具体的
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
需要联系多个智能体时,对每个智能体分别调用 `multiclaws_delegate`。
|
|
157
158
|
|
|
158
|
-
|
|
159
|
+
#### 示例
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
#### 工作流程
|
|
163
|
-
|
|
164
|
-
```
|
|
165
|
-
用户: "帮我和小明、小红约明天下午的会议"
|
|
166
|
-
|
|
167
|
-
1. multiclaws_agents() — 列出智能体,读 bio
|
|
168
|
-
2. sessions_spawn(task="<协作任务 prompt>", mode="run")
|
|
169
|
-
3. → 立即告诉用户: "已启动协作任务,会实时汇报进展"
|
|
170
|
-
4.(子 agent 在后台自主完成全部沟通,通过 message 实时汇报)
|
|
171
|
-
5. 子 agent 完成 → announce 回主 agent → 最终结果自动通知用户
|
|
161
|
+
**单人任务:**
|
|
172
162
|
```
|
|
163
|
+
用户: "问一下小明他那个 API 接口的参数格式"
|
|
173
164
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
spawn 子 agent 时,task 必须包含以下要素:
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
sessions_spawn(task="
|
|
180
|
-
## 任务
|
|
181
|
-
联系小明和小红,协商明天下午的会议时间。我这边下午 2-5 点都可以。
|
|
182
|
-
|
|
183
|
-
## 可用工具
|
|
184
|
-
- `multiclaws_agents()` — 查看所有智能体
|
|
185
|
-
- `multiclaws_delegate(agentUrl, task)` — 向智能体发送任务
|
|
186
|
-
|
|
187
|
-
## 执行步骤
|
|
188
|
-
1. 调用 multiclaws_agents() 获取智能体列表
|
|
189
|
-
2. 依次用 multiclaws_delegate 联系每个相关智能体
|
|
190
|
-
3. 每完成一个智能体的沟通,立即用 message 工具向用户汇报进展
|
|
191
|
-
4. 全部沟通完成后,汇总结果
|
|
192
|
-
|
|
193
|
-
## 中间汇报
|
|
194
|
-
每次 multiclaws_delegate 返回后,立即调用 message 工具告知用户当前进展。
|
|
195
|
-
例如:'已联系小明,他说明天下午 3 点和 4 点都可以。正在联系小红...'
|
|
196
|
-
|
|
197
|
-
## 完成条件
|
|
198
|
-
所有相关智能体都已回复,汇总最终结果。
|
|
199
|
-
", mode="run")
|
|
165
|
+
1. multiclaws_delegate(小明, "你那个 API 接口的参数格式是什么?")
|
|
166
|
+
→ 子 agent 自动发送、等待回复、通过 message 汇报结果
|
|
200
167
|
```
|
|
201
168
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
- **主 agent 不做多轮沟通。** 所有多轮协作都交给子 agent。
|
|
205
|
-
- **子 agent 用 `message` 工具实时汇报。** 子 agent 继承了父 agent 的频道上下文,`message` 发出的消息用户能直接看到。
|
|
206
|
-
- 每次 `multiclaws_delegate` 返回后,子 agent 必须立即用 `message` 汇报,不要等全部完成。
|
|
207
|
-
- 如果某个智能体没有回复或返回错误,子 agent 应在汇报中说明,继续联系其他智能体。
|
|
208
|
-
- 协商未达成一致时,子 agent 可继续发 `multiclaws_delegate`,最多 5 轮。
|
|
209
|
-
- 涉及多个智能体时,依次联系(串行),每个完成后立即汇报。
|
|
210
|
-
|
|
211
|
-
#### 示例场景
|
|
212
|
-
|
|
213
|
-
**约多人会议:**
|
|
169
|
+
**多人任务:**
|
|
214
170
|
```
|
|
215
171
|
用户: "帮我和小明、小红约明天下午的会议"
|
|
216
172
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
子 agent(后台执行):
|
|
223
|
-
1. multiclaws_delegate(小明, "明天下午 2-5 点开会,你什么时候有空?")
|
|
224
|
-
→ 小明回复: "3 点和 4 点都行"
|
|
225
|
-
→ message("已联系小明,他明天下午 3 点和 4 点都可以。正在联系小红...")
|
|
226
|
-
2. multiclaws_delegate(小红, "明天下午 2-5 点开会,小明 3 点和 4 点都行,你呢?")
|
|
227
|
-
→ 小红回复: "3 点可以"
|
|
228
|
-
→ message("小红也确认明天下午 3 点可以。")
|
|
229
|
-
3. multiclaws_delegate(小明, "确认明天下午 3 点开会")
|
|
230
|
-
4. multiclaws_delegate(小红, "确认明天下午 3 点开会")
|
|
231
|
-
5. 完成 → announce: "会议已确认:明天下午 3 点,参与人:小明、小红"
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
**简单单人协作:**
|
|
235
|
-
```
|
|
236
|
-
用户: "问一下小明他那个 API 接口的参数格式"
|
|
237
|
-
|
|
238
|
-
(单轮任务,不需要多轮 → 直接用智能委派)
|
|
239
|
-
1. multiclaws_delegate(小明, "你那个 API 接口的参数格式是什么?")
|
|
240
|
-
2. 把结果返回给用户
|
|
173
|
+
1. multiclaws_agents() → 找到小明、小红
|
|
174
|
+
2. multiclaws_delegate(小明, "明天下午 2-5 点开会,你什么时候有空?")
|
|
175
|
+
3. multiclaws_delegate(小红, "明天下午 2-5 点开会,你什么时候有空?")
|
|
176
|
+
→ 每个委派各自 spawn 子 agent,通过 message 实时汇报进展
|
|
241
177
|
```
|
|
242
178
|
|
|
243
179
|
---
|