multiclaws 0.4.28 → 0.4.30

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
@@ -542,14 +542,16 @@ const plugin = {
542
542
  api.on("gateway_stop", () => {
543
543
  structured.logger.info("[multiclaws] gateway_stop observed");
544
544
  });
545
+ // Collect all channel IDs for broadcasting notifications
546
+ api.on("message_received", (_event, ctx) => {
547
+ if (service && ctx.channelId) {
548
+ service.addChannelId(ctx.channelId);
549
+ }
550
+ });
545
551
  // Inject onboarding prompt when profile is pending first-run setup
546
- api.on("before_prompt_build", async (_event, ctx) => {
552
+ api.on("before_prompt_build", async (_event, _ctx) => {
547
553
  if (!service)
548
554
  return;
549
- // Capture the active channel so notifications go to the right place
550
- if (ctx.channelId) {
551
- service.setActiveChannelId(ctx.channelId);
552
- }
553
555
  try {
554
556
  const review = await service.getPendingProfileReview();
555
557
  if (!review.pending)
@@ -5,7 +5,7 @@ export type A2AAdapterOptions = {
5
5
  gatewayConfig: GatewayConfig | null;
6
6
  taskTracker: TaskTracker;
7
7
  cwd?: string;
8
- getActiveChannelId?: () => string | null;
8
+ getChannelIds?: () => ReadonlySet<string>;
9
9
  logger: {
10
10
  info: (msg: string) => void;
11
11
  warn: (msg: string) => void;
@@ -25,7 +25,7 @@ export type A2AAdapterOptions = {
25
25
  export declare class OpenClawAgentExecutor implements AgentExecutor {
26
26
  private gatewayConfig;
27
27
  private readonly taskTracker;
28
- private readonly getActiveChannelId;
28
+ private readonly getChannelIds;
29
29
  private readonly logger;
30
30
  private readonly cwd;
31
31
  constructor(options: A2AAdapterOptions);
@@ -47,7 +47,7 @@ export declare class OpenClawAgentExecutor implements AgentExecutor {
47
47
  private extractTextFromHistoryMessage;
48
48
  cancelTask(taskId: string, eventBus: ExecutionEventBus): Promise<void>;
49
49
  updateGatewayConfig(config: GatewayConfig): void;
50
- /** Send a notification to the local user via the gateway message tool. */
50
+ /** Send a notification to all known channels. Individual failures are silently ignored. */
51
51
  private notifyUser;
52
52
  private publishMessage;
53
53
  }
@@ -41,13 +41,13 @@ function extractDetails(result) {
41
41
  class OpenClawAgentExecutor {
42
42
  gatewayConfig;
43
43
  taskTracker;
44
- getActiveChannelId;
44
+ getChannelIds;
45
45
  logger;
46
46
  cwd;
47
47
  constructor(options) {
48
48
  this.gatewayConfig = options.gatewayConfig;
49
49
  this.taskTracker = options.taskTracker;
50
- this.getActiveChannelId = options.getActiveChannelId ?? (() => null);
50
+ this.getChannelIds = options.getChannelIds ?? (() => new Set());
51
51
  this.logger = options.logger;
52
52
  this.cwd = options.cwd || process.cwd();
53
53
  }
@@ -142,10 +142,11 @@ class OpenClawAgentExecutor {
142
142
  });
143
143
  const result = this.extractCompletedResult(histResult);
144
144
  if (result !== null) {
145
+ this.logger.info(`[a2a-adapter] poll attempt ${attempt}: session ${sessionKey} completed, resultLen=${result.length}`);
145
146
  return result;
146
147
  }
147
- // Log details every 50 attempts to help diagnose stuck sessions
148
- if (attempt % 50 === 0) {
148
+ // Log details on first attempt, then every 10 attempts for diagnosis
149
+ if (attempt === 1 || attempt % 10 === 0) {
149
150
  const details = extractDetails(histResult);
150
151
  const messages = (details?.messages ?? []);
151
152
  const lastMsg = messages[messages.length - 1];
@@ -164,7 +165,9 @@ class OpenClawAgentExecutor {
164
165
  this.logger.warn(`[a2a-adapter] poll attempt ${attempt} error: ${err instanceof Error ? err.message : err}`);
165
166
  }
166
167
  }
167
- throw new Error(`task timed out after ${Math.round(timeoutMs / 1000)}s waiting for subagent`);
168
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
169
+ this.logger.error(`[a2a-adapter] waitForCompletion timed out: session=${sessionKey}, elapsed=${elapsed}s, attempts=${attempt}`);
170
+ throw new Error(`task timed out after ${elapsed}s (${attempt} attempts) waiting for subagent`);
168
171
  }
169
172
  /**
170
173
  * Extract all assistant text from session history once the session is complete.
@@ -180,6 +183,12 @@ class OpenClawAgentExecutor {
180
183
  // Check for session-level error/status from gateway
181
184
  const sessionError = details.error;
182
185
  const sessionStatus = details.status;
186
+ // Immediately fail on terminal session statuses — do NOT keep polling
187
+ const terminalStatuses = ["forbidden", "failed", "error", "not_found", "unauthorized"];
188
+ if (sessionStatus && terminalStatuses.includes(sessionStatus)) {
189
+ this.logger.warn(`[a2a-adapter] extractCompletedResult: terminal status="${sessionStatus}", error="${sessionError ?? "none"}"`);
190
+ return `Error: session status "${sessionStatus}"${sessionError ? `: ${sessionError}` : ""}`;
191
+ }
183
192
  const messages = (details.messages ?? []);
184
193
  if (messages.length === 0 && !details.isComplete)
185
194
  return null;
@@ -253,22 +262,17 @@ class OpenClawAgentExecutor {
253
262
  updateGatewayConfig(config) {
254
263
  this.gatewayConfig = config;
255
264
  }
256
- /** Send a notification to the local user via the gateway message tool. */
265
+ /** Send a notification to all known channels. Individual failures are silently ignored. */
257
266
  async notifyUser(message) {
258
- const target = this.getActiveChannelId();
259
- if (!this.gatewayConfig || !target)
267
+ const channels = this.getChannelIds();
268
+ if (!this.gatewayConfig || channels.size === 0)
260
269
  return;
261
- try {
262
- await (0, gateway_client_1.invokeGatewayTool)({
263
- gateway: this.gatewayConfig,
264
- tool: "message",
265
- args: { action: "send", target, message },
266
- timeoutMs: 5_000,
267
- });
268
- }
269
- catch {
270
- this.logger.warn(`[a2a-adapter] notifyUser failed: ${message.slice(0, 80)}`);
271
- }
270
+ await Promise.allSettled([...channels].map((target) => (0, gateway_client_1.invokeGatewayTool)({
271
+ gateway: this.gatewayConfig,
272
+ tool: "message",
273
+ args: { action: "send", target, message },
274
+ timeoutMs: 5_000,
275
+ })));
272
276
  }
273
277
  publishMessage(eventBus, text) {
274
278
  const message = {
@@ -45,7 +45,7 @@ export declare class MulticlawsService extends EventEmitter {
45
45
  private profileDescription;
46
46
  private readonly gatewayConfig;
47
47
  private readonly resolvedCwd;
48
- private activeChannelId;
48
+ private readonly knownChannelIds;
49
49
  constructor(options: MulticlawsServiceOptions);
50
50
  start(): Promise<void>;
51
51
  stop(): Promise<void>;
@@ -126,9 +126,9 @@ export declare class MulticlawsService extends EventEmitter {
126
126
  private extractArtifactText;
127
127
  /** Fetch with up to 2 retries and exponential backoff. */
128
128
  private fetchWithRetry;
129
- /** Update the active channel ID used for notifications. */
130
- setActiveChannelId(channelId: string): void;
131
- /** Send a notification message to the local user via the gateway message tool. */
129
+ /** Register a channel ID for notifications. */
130
+ addChannelId(channelId: string): void;
131
+ /** Send a notification to all known channels. Individual failures are silently ignored. */
132
132
  private notifyUser;
133
133
  private log;
134
134
  }
@@ -68,7 +68,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
68
68
  profileDescription = "OpenClaw agent";
69
69
  gatewayConfig;
70
70
  resolvedCwd;
71
- activeChannelId = null;
71
+ knownChannelIds = new Set();
72
72
  constructor(options) {
73
73
  super();
74
74
  this.options = options;
@@ -123,7 +123,7 @@ class MulticlawsService extends node_events_1.EventEmitter {
123
123
  gatewayConfig: this.options.gatewayConfig ?? null,
124
124
  taskTracker: this.taskTracker,
125
125
  cwd: this.resolvedCwd,
126
- getActiveChannelId: () => this.activeChannelId,
126
+ getChannelIds: () => this.knownChannelIds,
127
127
  logger,
128
128
  });
129
129
  this.agentCard = {
@@ -919,26 +919,23 @@ class MulticlawsService extends node_events_1.EventEmitter {
919
919
  }
920
920
  throw lastError;
921
921
  }
922
- /** Update the active channel ID used for notifications. */
923
- setActiveChannelId(channelId) {
924
- this.activeChannelId = channelId;
925
- this.log("debug", `activeChannelId set to: ${channelId}`);
922
+ /** Register a channel ID for notifications. */
923
+ addChannelId(channelId) {
924
+ if (!this.knownChannelIds.has(channelId)) {
925
+ this.knownChannelIds.add(channelId);
926
+ this.log("debug", `channel registered: ${channelId} (total: ${this.knownChannelIds.size})`);
927
+ }
926
928
  }
927
- /** Send a notification message to the local user via the gateway message tool. */
929
+ /** Send a notification to all known channels. Individual failures are silently ignored. */
928
930
  async notifyUser(message) {
929
- if (!this.gatewayConfig || !this.activeChannelId)
931
+ if (!this.gatewayConfig || this.knownChannelIds.size === 0)
930
932
  return;
931
- try {
932
- await (0, gateway_client_1.invokeGatewayTool)({
933
- gateway: this.gatewayConfig,
934
- tool: "message",
935
- args: { action: "send", target: this.activeChannelId, message },
936
- timeoutMs: 5_000,
937
- });
938
- }
939
- catch (err) {
940
- this.log("warn", `notifyUser failed: ${err instanceof Error ? err.message : String(err)} | msg=${message.slice(0, 80)}`);
941
- }
933
+ await Promise.allSettled([...this.knownChannelIds].map((target) => (0, gateway_client_1.invokeGatewayTool)({
934
+ gateway: this.gatewayConfig,
935
+ tool: "message",
936
+ args: { action: "send", target, message },
937
+ timeoutMs: 5_000,
938
+ })));
942
939
  }
943
940
  log(level, message) {
944
941
  this.options.logger?.[level]?.(`[multiclaws] ${message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multiclaws",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
4
4
  "description": "MultiClaws plugin for OpenClaw collaboration via A2A protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",