@customclaw/composio 0.0.6 → 0.0.7

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
@@ -22,6 +22,7 @@ openclaw plugins install @customclaw/composio
22
22
  "enabled": true,
23
23
  "config": {
24
24
  "apiKey": "your-api-key",
25
+ "defaultUserId": "my-app-user-123",
25
26
  "allowedToolkits": ["gmail", "sentry"]
26
27
  }
27
28
  }
@@ -41,16 +42,19 @@ The plugin gives your agent two tools:
41
42
  - `composio_execute_tool` — runs a Composio action (e.g. `GMAIL_FETCH_EMAILS`, `SENTRY_LIST_ISSUES`)
42
43
  - `composio_manage_connections` — checks connection status and generates OAuth links when a toolkit isn't connected yet
43
44
 
45
+ `composio_execute_tool` also accepts optional `user_id` and `connected_account_id` fields for deterministic account selection when multiple accounts exist.
46
+
44
47
  The agent handles the rest. Ask it to "check my latest emails" and it will call the right tool, prompt you to connect Gmail if needed, and fetch the results.
45
48
 
46
49
  ## CLI
47
50
 
48
51
  ```bash
49
- openclaw composio list # list available toolkits
50
- openclaw composio status # check what's connected
51
- openclaw composio connect gmail # open OAuth link
52
- openclaw composio disconnect gmail # remove a connection
53
- openclaw composio search "send email" # find tool slugs
52
+ openclaw composio list --user-id user-123 # list available toolkits for a user scope
53
+ openclaw composio status [toolkit] --user-id user-123 # check connection status in a user scope
54
+ openclaw composio accounts [toolkit] # inspect connected accounts (id/user_id/status)
55
+ openclaw composio connect gmail --user-id user-123 # open OAuth link for a specific user scope
56
+ openclaw composio disconnect gmail --user-id user-123 # remove a connection in that user scope
57
+ openclaw composio search "send email" --user-id user-123
54
58
  ```
55
59
 
56
60
  ## Config options
@@ -58,9 +62,22 @@ openclaw composio search "send email" # find tool slugs
58
62
  | Key | Description |
59
63
  |-----|-------------|
60
64
  | `apiKey` | Composio API key (required) |
65
+ | `defaultUserId` | Default Composio `user_id` scope when `--user-id` is not provided |
61
66
  | `allowedToolkits` | Only allow these toolkits (e.g. `["gmail", "sentry"]`) |
62
67
  | `blockedToolkits` | Block specific toolkits |
63
68
 
69
+ ## User ID Scope (Important)
70
+
71
+ Composio connections are scoped by `user_id`. If a toolkit is connected in the dashboard
72
+ under one user ID but OpenClaw checks another (for example `default`), status and execution
73
+ may look disconnected.
74
+
75
+ Tips:
76
+
77
+ - Set `defaultUserId` in plugin config for your app's primary identity.
78
+ - Use `--user-id` explicitly when checking status/connect/disconnect.
79
+ - Use `openclaw composio accounts <toolkit>` to discover which `user_id` owns active connections.
80
+
64
81
  ## Updating
65
82
 
66
83
  ```bash
package/dist/cli.js CHANGED
@@ -7,13 +7,14 @@ export function registerComposioCli({ program, client, config, logger }) {
7
7
  composio
8
8
  .command("list")
9
9
  .description("List available Composio toolkits")
10
- .action(async () => {
10
+ .option("-u, --user-id <userId>", "User ID for session scoping")
11
+ .action(async (options) => {
11
12
  if (!config.enabled) {
12
13
  logger.error("Composio plugin is disabled");
13
14
  return;
14
15
  }
15
16
  try {
16
- const toolkits = await client.listToolkits();
17
+ const toolkits = await client.listToolkits(options.userId);
17
18
  console.log("\nAvailable Composio Toolkits:");
18
19
  console.log("─".repeat(40));
19
20
  for (const toolkit of toolkits.sort()) {
@@ -50,12 +51,59 @@ export function registerComposioCli({ program, client, config, logger }) {
50
51
  console.log(` ${icon} ${status.toolkit}: ${state}`);
51
52
  }
52
53
  }
54
+ if (toolkit && statuses.length === 1 && !statuses[0]?.connected) {
55
+ const currentUserId = options.userId || config.defaultUserId || "default";
56
+ const activeUserIds = await client.findActiveUserIdsForToolkit(toolkit);
57
+ const otherUserIds = activeUserIds.filter((uid) => uid !== currentUserId);
58
+ if (otherUserIds.length > 0) {
59
+ console.log(`\n Hint: '${toolkit}' is connected under other user_id(s): ${otherUserIds.join(", ")}`);
60
+ console.log(` Try: openclaw composio status ${toolkit} --user-id <user-id>`);
61
+ console.log(` Discover accounts: openclaw composio accounts ${toolkit}`);
62
+ }
63
+ }
53
64
  console.log();
54
65
  }
55
66
  catch (err) {
56
67
  logger.error(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
57
68
  }
58
69
  });
70
+ // openclaw composio accounts [toolkit]
71
+ composio
72
+ .command("accounts [toolkit]")
73
+ .description("List connected accounts (account IDs, user IDs, and statuses)")
74
+ .option("-u, --user-id <userId>", "Filter by user ID")
75
+ .option("-s, --statuses <statuses>", "Comma-separated statuses (default: ACTIVE)", "ACTIVE")
76
+ .action(async (toolkit, options) => {
77
+ if (!config.enabled) {
78
+ logger.error("Composio plugin is disabled");
79
+ return;
80
+ }
81
+ try {
82
+ const statuses = String(options.statuses || "")
83
+ .split(",")
84
+ .map(s => s.trim())
85
+ .filter(Boolean);
86
+ const accounts = await client.listConnectedAccounts({
87
+ toolkits: toolkit ? [toolkit] : undefined,
88
+ userIds: options.userId ? [options.userId] : undefined,
89
+ statuses: statuses.length > 0 ? statuses : ["ACTIVE"],
90
+ });
91
+ console.log("\nComposio Connected Accounts:");
92
+ console.log("─".repeat(80));
93
+ if (accounts.length === 0) {
94
+ console.log(" No connected accounts found");
95
+ console.log();
96
+ return;
97
+ }
98
+ for (const account of accounts) {
99
+ console.log(` ${account.id} | toolkit=${account.toolkit} | status=${account.status || "unknown"} | user_id=${account.userId || "hidden"}`);
100
+ }
101
+ console.log(`\nTotal: ${accounts.length} connected accounts\n`);
102
+ }
103
+ catch (err) {
104
+ logger.error(`Failed to list connected accounts: ${err instanceof Error ? err.message : String(err)}`);
105
+ }
106
+ });
59
107
  // openclaw composio connect <toolkit>
60
108
  composio
61
109
  .command("connect <toolkit>")
@@ -67,7 +115,9 @@ export function registerComposioCli({ program, client, config, logger }) {
67
115
  return;
68
116
  }
69
117
  try {
118
+ const currentUserId = options.userId || config.defaultUserId || "default";
70
119
  console.log(`\nInitiating connection to ${toolkit}...`);
120
+ console.log(`Using user_id: ${currentUserId}`);
71
121
  const result = await client.createConnection(toolkit, options.userId);
72
122
  if ("error" in result) {
73
123
  logger.error(`Failed to create connection: ${result.error}`);
@@ -77,7 +127,7 @@ export function registerComposioCli({ program, client, config, logger }) {
77
127
  console.log("─".repeat(40));
78
128
  console.log(result.authUrl);
79
129
  console.log("\nOpen this URL in your browser to authenticate.");
80
- console.log("After authentication, run 'openclaw composio status' to verify.\n");
130
+ console.log(`After authentication, run 'openclaw composio status ${toolkit} --user-id ${currentUserId}' to verify.\n`);
81
131
  // Try to open URL in browser
82
132
  try {
83
133
  const { exec } = await import("node:child_process");
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ComposioConfig, ToolSearchResult, ToolExecutionResult, ConnectionStatus } from "./types.js";
1
+ import type { ComposioConfig, ToolSearchResult, ToolExecutionResult, ConnectionStatus, ConnectedAccountSummary } from "./types.js";
2
2
  /**
3
3
  * Composio client wrapper using Tool Router pattern
4
4
  */
@@ -14,7 +14,9 @@ export declare class ComposioClient {
14
14
  /**
15
15
  * Get or create a Tool Router session for a user
16
16
  */
17
+ private makeSessionCacheKey;
17
18
  private getSession;
19
+ private clearUserSessionCache;
18
20
  /**
19
21
  * Check if a toolkit is allowed based on config
20
22
  */
@@ -34,11 +36,41 @@ export declare class ComposioClient {
34
36
  /**
35
37
  * Execute a single tool using COMPOSIO_MULTI_EXECUTE_TOOL
36
38
  */
37
- executeTool(toolSlug: string, args: Record<string, unknown>, userId?: string): Promise<ToolExecutionResult>;
39
+ executeTool(toolSlug: string, args: Record<string, unknown>, userId?: string, connectedAccountId?: string): Promise<ToolExecutionResult>;
40
+ private tryExecutionRecovery;
41
+ private tryDirectExecutionFallback;
42
+ private executeDirectTool;
43
+ private tryHintedIdentifierRetry;
44
+ private shouldFallbackToDirectExecution;
45
+ private shouldRetryFromServerHint;
46
+ private extractServerHintLiteral;
47
+ private buildRetryArgsFromHint;
48
+ private extractSingleMissingField;
49
+ private buildCombinedErrorText;
50
+ private extractNestedMetaError;
51
+ private resolveConnectedAccountForExecution;
38
52
  /**
39
53
  * Get connection status for toolkits using session.toolkits()
40
54
  */
41
55
  getConnectionStatus(toolkits?: string[], userId?: string): Promise<ConnectionStatus[]>;
56
+ private getToolkitStateMap;
57
+ private getActiveConnectedAccountToolkits;
58
+ private normalizeStatuses;
59
+ /**
60
+ * List connected accounts with optional filters.
61
+ * Uses raw API first to preserve user_id in responses, then falls back to SDK-normalized output.
62
+ */
63
+ listConnectedAccounts(options?: {
64
+ toolkits?: string[];
65
+ userIds?: string[];
66
+ statuses?: string[];
67
+ }): Promise<ConnectedAccountSummary[]>;
68
+ /**
69
+ * Find user IDs that have an active connected account for a toolkit.
70
+ */
71
+ findActiveUserIdsForToolkit(toolkit: string): Promise<string[]>;
72
+ private listConnectedAccountsRaw;
73
+ private listConnectedAccountsFallback;
42
74
  /**
43
75
  * Create an auth connection for a toolkit using session.authorize()
44
76
  */