@customclaw/composio 0.0.8 → 0.0.10

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/README.md CHANGED
@@ -96,7 +96,7 @@ may look disconnected.
96
96
  Tips:
97
97
 
98
98
  - Set `defaultUserId` in plugin config for your app's primary identity.
99
- - Use `--user-id` explicitly when checking status/connect/disconnect.
99
+ - Prefer passing `user_id`/`--user-id` explicitly when checking status, connecting, disconnecting, or executing tools.
100
100
  - Use `openclaw composio accounts <toolkit>` to discover which `user_id` owns active connections.
101
101
 
102
102
  ## Updating
package/dist/cli.js CHANGED
@@ -271,9 +271,11 @@ export function registerComposioCli({ program, getClient, config, logger }) {
271
271
  try {
272
272
  const toolkitSlug = toolkit ? normalizeToolkitSlug(toolkit) : undefined;
273
273
  const toolkits = toolkitSlug ? [toolkitSlug] : undefined;
274
+ const currentUserId = options.userId || config.defaultUserId || "default";
274
275
  const statuses = await composioClient.getConnectionStatus(toolkits, options.userId);
275
276
  console.log("\nComposio Connection Status:");
276
277
  console.log("─".repeat(40));
278
+ console.log(` Scope user_id: ${currentUserId}${options.userId ? " (explicit)" : " (default)"}`);
277
279
  if (statuses.length === 0) {
278
280
  console.log(" No connections found");
279
281
  }
@@ -285,7 +287,6 @@ export function registerComposioCli({ program, getClient, config, logger }) {
285
287
  }
286
288
  }
287
289
  if (toolkitSlug && statuses.length === 1 && !statuses[0]?.connected) {
288
- const currentUserId = options.userId || config.defaultUserId || "default";
289
290
  const activeUserIds = await composioClient.findActiveUserIdsForToolkit(toolkitSlug);
290
291
  const otherUserIds = activeUserIds.filter((uid) => uid !== currentUserId);
291
292
  if (otherUserIds.length > 0) {
package/dist/client.js CHANGED
@@ -290,7 +290,7 @@ export class ComposioClient {
290
290
  * Execute a single tool using COMPOSIO_MULTI_EXECUTE_TOOL
291
291
  */
292
292
  async executeTool(toolSlug, args, userId, connectedAccountId) {
293
- const uid = this.getUserId(userId);
293
+ const requestedUid = this.getUserId(userId);
294
294
  const normalizedToolSlug = normalizeToolSlug(toolSlug);
295
295
  const toolRestrictionError = this.getToolSlugRestrictionError(normalizedToolSlug);
296
296
  if (toolRestrictionError) {
@@ -305,13 +305,15 @@ export class ComposioClient {
305
305
  }
306
306
  const accountResolution = await this.resolveConnectedAccountForExecution({
307
307
  toolkit,
308
- userId: uid,
308
+ userId: requestedUid,
309
309
  connectedAccountId,
310
+ userIdWasExplicit: typeof userId === "string" && userId.trim().length > 0,
310
311
  });
311
312
  if ("error" in accountResolution) {
312
313
  return { success: false, error: accountResolution.error };
313
314
  }
314
- const session = await this.getSession(uid, accountResolution.connectedAccountId
315
+ const effectiveUid = accountResolution.userId || requestedUid;
316
+ const session = await this.getSession(effectiveUid, accountResolution.connectedAccountId
315
317
  ? { [toolkit]: accountResolution.connectedAccountId }
316
318
  : undefined);
317
319
  try {
@@ -321,7 +323,7 @@ export class ComposioClient {
321
323
  });
322
324
  if (!response.successful) {
323
325
  const recovered = await this.tryExecutionRecovery({
324
- uid,
326
+ uid: effectiveUid,
325
327
  toolSlug: normalizedToolSlug,
326
328
  args,
327
329
  connectedAccountId: accountResolution.connectedAccountId,
@@ -341,7 +343,7 @@ export class ComposioClient {
341
343
  const toolResponse = result.response;
342
344
  if (!toolResponse.successful) {
343
345
  const recovered = await this.tryExecutionRecovery({
344
- uid,
346
+ uid: effectiveUid,
345
347
  toolSlug: normalizedToolSlug,
346
348
  args,
347
349
  connectedAccountId: accountResolution.connectedAccountId,
@@ -483,9 +485,23 @@ export class ComposioClient {
483
485
  const explicitId = params.connectedAccountId?.trim();
484
486
  if (explicitId) {
485
487
  try {
486
- const account = await this.client.connectedAccounts.get(explicitId);
488
+ let rawAccount;
489
+ const retrieve = this.client?.client?.connectedAccounts?.retrieve;
490
+ if (typeof retrieve === "function") {
491
+ try {
492
+ rawAccount = await retrieve.call(this.client.client.connectedAccounts, explicitId);
493
+ }
494
+ catch {
495
+ // Best-effort: fall through to SDK get() which may omit user_id.
496
+ }
497
+ }
498
+ if (!rawAccount) {
499
+ rawAccount = await this.client.connectedAccounts.get(explicitId);
500
+ }
501
+ const account = rawAccount;
487
502
  const accountToolkit = normalizeToolkitSlug(String(account?.toolkit?.slug || ""));
488
503
  const accountStatus = String(account?.status || "").toUpperCase();
504
+ const accountUserId = String(account?.user_id || account?.userId || "").trim();
489
505
  if (accountToolkit && accountToolkit !== toolkit) {
490
506
  return {
491
507
  error: `Connected account '${explicitId}' belongs to toolkit '${accountToolkit}', but tool '${toolkit}' was requested.`,
@@ -496,7 +512,13 @@ export class ComposioClient {
496
512
  error: `Connected account '${explicitId}' is '${accountStatus}', not ACTIVE.`,
497
513
  };
498
514
  }
499
- return { connectedAccountId: explicitId };
515
+ if (params.userIdWasExplicit && accountUserId && accountUserId !== userId) {
516
+ return {
517
+ error: `Connected account '${explicitId}' belongs to user_id '${accountUserId}', ` +
518
+ `but '${userId}' was requested. Use matching user_id or omit user_id when providing connected_account_id.`,
519
+ };
520
+ }
521
+ return { connectedAccountId: explicitId, userId: accountUserId || undefined };
500
522
  }
501
523
  catch (err) {
502
524
  return {
@@ -87,7 +87,14 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
87
87
  text: string;
88
88
  }[];
89
89
  details: {
90
+ hints?: {
91
+ toolkit: string;
92
+ connected_user_ids: string[];
93
+ message: string;
94
+ }[] | undefined;
90
95
  action: string;
96
+ checked_user_id: string | undefined;
97
+ user_id_explicit: boolean;
91
98
  count: number;
92
99
  connections: {
93
100
  toolkit: string;
@@ -13,7 +13,7 @@ export const ComposioManageConnectionsToolSchema = Type.Object({
13
13
  description: "Multiple toolkits to check status for",
14
14
  })),
15
15
  user_id: Type.Optional(Type.String({
16
- description: "User ID for session scoping (uses default if not provided)",
16
+ description: "User ID for session scoping. Strongly recommended to avoid checking the wrong scope.",
17
17
  })),
18
18
  statuses: Type.Optional(Type.Array(Type.String(), {
19
19
  description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
@@ -34,6 +34,7 @@ export function createComposioConnectionsTool(client, _config) {
34
34
  async execute(_toolCallId, params) {
35
35
  const action = String(params.action || "status");
36
36
  const userId = typeof params.user_id === "string" ? params.user_id : undefined;
37
+ const userIdWasExplicit = typeof params.user_id === "string" && params.user_id.trim().length > 0;
37
38
  try {
38
39
  switch (action) {
39
40
  case "list": {
@@ -119,13 +120,32 @@ export function createComposioConnectionsTool(client, _config) {
119
120
  toolkitsToCheck = params.toolkits.filter((t) => typeof t === "string" && t.trim() !== "");
120
121
  }
121
122
  const statuses = await client.getConnectionStatus(toolkitsToCheck, userId);
123
+ const disconnectedToolkits = statuses.filter((s) => !s.connected).map((s) => s.toolkit);
124
+ const hints = [];
125
+ if (!userIdWasExplicit) {
126
+ for (const toolkit of disconnectedToolkits) {
127
+ const activeUserIds = await client.findActiveUserIdsForToolkit(toolkit);
128
+ if (activeUserIds.length === 0)
129
+ continue;
130
+ hints.push({
131
+ toolkit,
132
+ connected_user_ids: activeUserIds,
133
+ message: `No user_id was provided, so status checked the default scope. ` +
134
+ `'${toolkit}' has ACTIVE accounts under: ${activeUserIds.join(", ")}. ` +
135
+ "Pass user_id explicitly for deterministic results.",
136
+ });
137
+ }
138
+ }
122
139
  const response = {
123
140
  action: "status",
141
+ checked_user_id: statuses[0]?.userId,
142
+ user_id_explicit: userIdWasExplicit,
124
143
  count: statuses.length,
125
144
  connections: statuses.map((s) => ({
126
145
  toolkit: s.toolkit,
127
146
  connected: s.connected,
128
147
  })),
148
+ ...(hints.length > 0 ? { hints } : {}),
129
149
  };
130
150
  return {
131
151
  content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
@@ -10,7 +10,7 @@ export const ComposioExecuteToolSchema = Type.Object({
10
10
  description: "Tool arguments matching the tool's parameter schema",
11
11
  }),
12
12
  user_id: Type.Optional(Type.String({
13
- description: "User ID for session scoping (uses default if not provided)",
13
+ description: "User ID for session scoping. Strongly recommended to avoid executing in the wrong scope.",
14
14
  })),
15
15
  connected_account_id: Type.Optional(Type.String({
16
16
  description: "Optional connected account ID to pin execution to a specific account when multiple are connected",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customclaw/composio",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "description": "Composio Tool Router plugin for OpenClaw — access 1000+ third-party integrations",
6
6
  "main": "dist/index.js",