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.
@@ -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
- constructor(filePath: string);
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
- constructor(filePath) {
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
- await (0, json_store_1.writeJsonAtomically)(this.filePath, profile);
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
- const profile = await this.load();
30
- if (patch.ownerName !== undefined)
31
- profile.ownerName = patch.ownerName;
32
- if (patch.bio !== undefined)
33
- profile.bio = patch.bio;
34
- await this.save(profile);
35
- return profile;
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
- constructor(filePath: string);
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
- constructor(filePath) {
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
- return await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
31
- const store = await this.readStore();
32
- const normalizedUrl = params.url.replace(/\/+$/, "");
33
- const existing = store.agents.findIndex((a) => a.url === normalizedUrl);
34
- const now = Date.now();
35
- const record = {
36
- url: normalizedUrl,
37
- name: params.name,
38
- description: params.description ?? "",
39
- skills: params.skills ?? [],
40
- apiKey: params.apiKey,
41
- addedAtMs: existing >= 0 ? store.agents[existing].addedAtMs : now,
42
- lastSeenAtMs: now,
43
- };
44
- if (existing >= 0) {
45
- store.agents[existing] = record;
46
- }
47
- else {
48
- store.agents.push(record);
49
- }
50
- await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
51
- return record;
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
- return await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
56
- const store = await this.readStore();
57
- const normalizedUrl = url.replace(/\/+$/, "");
58
- const before = store.agents.length;
59
- store.agents = store.agents.filter((a) => a.url !== normalizedUrl);
60
- if (store.agents.length === before) {
61
- return false;
62
- }
63
- await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
64
- return true;
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
- await (0, json_store_1.withJsonLock)(this.filePath, emptyStore(), async () => {
78
- const store = await this.readStore();
79
- const normalizedUrl = url.replace(/\/+$/, "");
80
- const agent = store.agents.find((a) => a.url === normalizedUrl);
81
- if (agent) {
82
- agent.description = description;
83
- agent.lastSeenAtMs = Date.now();
84
- await (0, json_store_1.writeJsonAtomically)(this.filePath, store);
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
  /**