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
|
@@ -71,6 +71,9 @@ class OpenClawAgentExecutor {
|
|
|
71
71
|
try {
|
|
72
72
|
this.logger.info(`[a2a-adapter] executing task ${taskId}: ${taskText.slice(0, 100)}`);
|
|
73
73
|
// 1. Spawn the subagent
|
|
74
|
+
// Use a dedicated session key to avoid inheriting the main session's
|
|
75
|
+
// thinking blocks, which would cause "thinking blocks cannot be modified"
|
|
76
|
+
// errors from the Claude API.
|
|
74
77
|
const spawnResult = await (0, gateway_client_1.invokeGatewayTool)({
|
|
75
78
|
gateway: this.gatewayConfig,
|
|
76
79
|
tool: "sessions_spawn",
|
|
@@ -78,6 +81,7 @@ class OpenClawAgentExecutor {
|
|
|
78
81
|
task: taskText,
|
|
79
82
|
mode: "run",
|
|
80
83
|
},
|
|
84
|
+
sessionKey: `a2a-${taskId}`,
|
|
81
85
|
timeoutMs: 15_000,
|
|
82
86
|
});
|
|
83
87
|
// Extract details from gateway response: { content: [...], details: { childSessionKey, ... } }
|
|
@@ -87,8 +91,9 @@ class OpenClawAgentExecutor {
|
|
|
87
91
|
throw new Error("sessions_spawn did not return a childSessionKey");
|
|
88
92
|
}
|
|
89
93
|
// 2. Poll for completion
|
|
94
|
+
const gatewaySessionKey = `a2a-${taskId}`;
|
|
90
95
|
this.logger.info(`[a2a-adapter] task ${taskId} spawned as ${childSessionKey}, waiting for result...`);
|
|
91
|
-
const output = await this.waitForCompletion(childSessionKey, 180_000);
|
|
96
|
+
const output = await this.waitForCompletion(childSessionKey, 180_000, gatewaySessionKey);
|
|
92
97
|
// 3. Return result
|
|
93
98
|
this.taskTracker.update(taskId, { status: "completed", result: output });
|
|
94
99
|
this.logger.info(`[a2a-adapter] task ${taskId} completed`);
|
|
@@ -106,7 +111,8 @@ class OpenClawAgentExecutor {
|
|
|
106
111
|
* Poll sessions_history until the subagent session completes.
|
|
107
112
|
* Collects ALL assistant text messages and returns them joined.
|
|
108
113
|
*/
|
|
109
|
-
async waitForCompletion(sessionKey, timeoutMs) {
|
|
114
|
+
async waitForCompletion(sessionKey, timeoutMs, gatewaySessionKey) {
|
|
115
|
+
this.logger.info(`[a2a-adapter] waitForCompletion(sessionKey=${sessionKey}, timeoutMs=${timeoutMs})`);
|
|
110
116
|
const gateway = this.gatewayConfig;
|
|
111
117
|
const startTime = Date.now();
|
|
112
118
|
let attempt = 0;
|
|
@@ -124,6 +130,7 @@ class OpenClawAgentExecutor {
|
|
|
124
130
|
limit: 50,
|
|
125
131
|
includeTools: false,
|
|
126
132
|
},
|
|
133
|
+
sessionKey: gatewaySessionKey,
|
|
127
134
|
timeoutMs: 8_000,
|
|
128
135
|
});
|
|
129
136
|
const result = this.extractCompletedResult(histResult);
|
|
@@ -220,6 +227,7 @@ class OpenClawAgentExecutor {
|
|
|
220
227
|
return null;
|
|
221
228
|
}
|
|
222
229
|
async cancelTask(taskId, eventBus) {
|
|
230
|
+
this.logger.info(`[a2a-adapter] cancelTask(taskId=${taskId})`);
|
|
223
231
|
this.taskTracker.update(taskId, { status: "failed", error: "canceled" });
|
|
224
232
|
this.publishMessage(eventBus, "Task was canceled.");
|
|
225
233
|
eventBus.finished();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BasicLogger } from "../infra/logger";
|
|
1
2
|
export type AgentProfile = {
|
|
2
3
|
ownerName: string;
|
|
3
4
|
/** Free-form markdown describing this agent: role, capabilities, data sources, etc. */
|
|
@@ -6,7 +7,9 @@ export type AgentProfile = {
|
|
|
6
7
|
export declare function renderProfileDescription(profile: AgentProfile): string;
|
|
7
8
|
export declare class ProfileStore {
|
|
8
9
|
private readonly filePath;
|
|
9
|
-
|
|
10
|
+
private readonly logger?;
|
|
11
|
+
constructor(filePath: string, logger?: BasicLogger | undefined);
|
|
12
|
+
private log;
|
|
10
13
|
load(): Promise<AgentProfile>;
|
|
11
14
|
save(profile: AgentProfile): Promise<void>;
|
|
12
15
|
update(patch: Partial<AgentProfile>): Promise<AgentProfile>;
|
|
@@ -16,23 +16,44 @@ function renderProfileDescription(profile) {
|
|
|
16
16
|
}
|
|
17
17
|
class ProfileStore {
|
|
18
18
|
filePath;
|
|
19
|
-
|
|
19
|
+
logger;
|
|
20
|
+
constructor(filePath, logger) {
|
|
20
21
|
this.filePath = filePath;
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
}
|
|
24
|
+
log(level, message) {
|
|
25
|
+
const fn = level === "debug" ? this.logger?.debug : this.logger?.[level];
|
|
26
|
+
fn?.(`[profile-store] ${message}`);
|
|
21
27
|
}
|
|
22
28
|
async load() {
|
|
23
29
|
return await (0, json_store_1.readJsonWithFallback)(this.filePath, emptyProfile());
|
|
24
30
|
}
|
|
25
31
|
async save(profile) {
|
|
26
|
-
|
|
32
|
+
this.log("debug", `save(ownerName=${profile.ownerName})`);
|
|
33
|
+
try {
|
|
34
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, profile);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
this.log("error", `save failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
27
40
|
}
|
|
28
41
|
async update(patch) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
profile
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
this.log("debug", `update(keys=${Object.keys(patch).join(",")})`);
|
|
43
|
+
try {
|
|
44
|
+
const profile = await this.load();
|
|
45
|
+
if (patch.ownerName !== undefined)
|
|
46
|
+
profile.ownerName = patch.ownerName;
|
|
47
|
+
if (patch.bio !== undefined)
|
|
48
|
+
profile.bio = patch.bio;
|
|
49
|
+
await this.save(profile);
|
|
50
|
+
this.log("debug", `update completed`);
|
|
51
|
+
return profile;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
this.log("error", `update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
36
57
|
}
|
|
37
58
|
}
|
|
38
59
|
exports.ProfileStore = ProfileStore;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BasicLogger } from "../infra/logger";
|
|
1
2
|
export type AgentRecord = {
|
|
2
3
|
url: string;
|
|
3
4
|
name: string;
|
|
@@ -9,7 +10,9 @@ export type AgentRecord = {
|
|
|
9
10
|
};
|
|
10
11
|
export declare class AgentRegistry {
|
|
11
12
|
private readonly filePath;
|
|
12
|
-
|
|
13
|
+
private readonly logger?;
|
|
14
|
+
constructor(filePath: string, logger?: BasicLogger | undefined);
|
|
15
|
+
private log;
|
|
13
16
|
private readStore;
|
|
14
17
|
add(params: {
|
|
15
18
|
url: string;
|
|
@@ -19,50 +19,74 @@ function normalizeStore(raw) {
|
|
|
19
19
|
}
|
|
20
20
|
class AgentRegistry {
|
|
21
21
|
filePath;
|
|
22
|
-
|
|
22
|
+
logger;
|
|
23
|
+
constructor(filePath, logger) {
|
|
23
24
|
this.filePath = filePath;
|
|
25
|
+
this.logger = logger;
|
|
26
|
+
}
|
|
27
|
+
log(level, message) {
|
|
28
|
+
const fn = level === "debug" ? this.logger?.debug : this.logger?.[level];
|
|
29
|
+
fn?.(`[agent-registry] ${message}`);
|
|
24
30
|
}
|
|
25
31
|
async readStore() {
|
|
26
32
|
const store = await (0, json_store_1.readJsonWithFallback)(this.filePath, emptyStore());
|
|
27
33
|
return normalizeStore(store);
|
|
28
34
|
}
|
|
29
35
|
async add(params) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
const normalizedUrl = params.url.replace(/\/+$/, "");
|
|
37
|
+
this.log("debug", `add(url=${normalizedUrl}, name=${params.name})`);
|
|
38
|
+
try {
|
|
39
|
+
const result = await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
40
|
+
const store = await this.readStore();
|
|
41
|
+
const existing = store.agents.findIndex((a) => a.url === normalizedUrl);
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const record = {
|
|
44
|
+
url: normalizedUrl,
|
|
45
|
+
name: params.name,
|
|
46
|
+
description: params.description ?? "",
|
|
47
|
+
skills: params.skills ?? [],
|
|
48
|
+
apiKey: params.apiKey,
|
|
49
|
+
addedAtMs: existing >= 0 ? store.agents[existing].addedAtMs : now,
|
|
50
|
+
lastSeenAtMs: now,
|
|
51
|
+
};
|
|
52
|
+
if (existing >= 0) {
|
|
53
|
+
store.agents[existing] = record;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
store.agents.push(record);
|
|
57
|
+
}
|
|
58
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
59
|
+
return record;
|
|
60
|
+
});
|
|
61
|
+
this.log("debug", `add completed, agent=${result.name}`);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
this.log("error", `add failed for url=${normalizedUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
async remove(url) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
const normalizedUrl = url.replace(/\/+$/, "");
|
|
71
|
+
this.log("debug", `remove(url=${normalizedUrl})`);
|
|
72
|
+
try {
|
|
73
|
+
const result = await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
74
|
+
const store = await this.readStore();
|
|
75
|
+
const before = store.agents.length;
|
|
76
|
+
store.agents = store.agents.filter((a) => a.url !== normalizedUrl);
|
|
77
|
+
if (store.agents.length === before) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
this.log("debug", `remove completed, found=${result}`);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
this.log("error", `remove failed for url=${normalizedUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
66
90
|
}
|
|
67
91
|
async list() {
|
|
68
92
|
const store = await this.readStore();
|
|
@@ -74,16 +98,23 @@ class AgentRegistry {
|
|
|
74
98
|
return store.agents.find((a) => a.url === normalizedUrl) ?? null;
|
|
75
99
|
}
|
|
76
100
|
async updateDescription(url, description) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
agent
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
101
|
+
const normalizedUrl = url.replace(/\/+$/, "");
|
|
102
|
+
this.log("debug", `updateDescription(url=${normalizedUrl})`);
|
|
103
|
+
try {
|
|
104
|
+
await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
105
|
+
const store = await this.readStore();
|
|
106
|
+
const agent = store.agents.find((a) => a.url === normalizedUrl);
|
|
107
|
+
if (agent) {
|
|
108
|
+
agent.description = description;
|
|
109
|
+
agent.lastSeenAtMs = Date.now();
|
|
110
|
+
await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
this.log("error", `updateDescription failed for url=${normalizedUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
87
118
|
}
|
|
88
119
|
async updateLastSeen(url) {
|
|
89
120
|
await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
|
|
@@ -42,6 +42,7 @@ export declare class MulticlawsService extends EventEmitter {
|
|
|
42
42
|
private frpTunnel;
|
|
43
43
|
private selfUrl;
|
|
44
44
|
private profileDescription;
|
|
45
|
+
private readonly gatewayConfig;
|
|
45
46
|
constructor(options: MulticlawsServiceOptions);
|
|
46
47
|
start(): Promise<void>;
|
|
47
48
|
stop(): Promise<void>;
|
|
@@ -56,6 +57,25 @@ export declare class MulticlawsService extends EventEmitter {
|
|
|
56
57
|
agentUrl: string;
|
|
57
58
|
task: string;
|
|
58
59
|
}): Promise<DelegateTaskResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Synchronous delegation: sends A2A task and waits for the result.
|
|
62
|
+
* Used by sub-agents internally via the multiclaws_delegate_send tool.
|
|
63
|
+
*/
|
|
64
|
+
delegateTaskSync(params: {
|
|
65
|
+
agentUrl: string;
|
|
66
|
+
task: string;
|
|
67
|
+
}): Promise<DelegateTaskResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Spawn a sub-agent to handle delegation asynchronously.
|
|
70
|
+
* The sub-agent uses multiclaws_delegate_send internally and
|
|
71
|
+
* reports results back to the user via the message tool.
|
|
72
|
+
*/
|
|
73
|
+
spawnDelegation(params: {
|
|
74
|
+
agentUrl: string;
|
|
75
|
+
task: string;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
message: string;
|
|
78
|
+
}>;
|
|
59
79
|
getTaskStatus(taskId: string): import("../task/tracker").TaskRecord | null;
|
|
60
80
|
getProfile(): Promise<AgentProfile>;
|
|
61
81
|
/**
|