chore: use ao-agent as composio default user

This commit is contained in:
whoisasx 2026-05-13 21:49:01 +05:30
parent 2401abf6e9
commit c665da9183
6 changed files with 99 additions and 59 deletions

View File

@ -420,7 +420,7 @@ describe("setup composio command", () => {
);
expect(mockComposioConstructorOptions).toEqual([{ apiKey: "ak_interactive" }]);
expect(mockConnectedAccountsList).toHaveBeenCalledWith({
userIds: ["ao-local"],
userIds: ["ao-agent"],
toolkitSlugs: ["slack"],
statuses: ["ACTIVE"],
limit: 25,
@ -437,7 +437,7 @@ describe("setup composio command", () => {
plugin: "composio",
defaultApp: "slack",
composioApiKey: "ak_interactive",
userId: "ao-local",
userId: "ao-agent",
channelName: "iamasx",
connectedAccountId: "ca_slack_123",
});
@ -461,7 +461,7 @@ describe("setup composio command", () => {
await program.parseAsync(["node", "test", "setup", "composio"]);
expect(mockAuthConfigsList).toHaveBeenCalledWith({ toolkit: "slack" });
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-local", "auth_slack_123", {
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-agent", "auth_slack_123", {
allowMultiple: true,
});
const writtenYaml = mockWriteFileSync.mock.calls[0][1] as string;
@ -537,7 +537,7 @@ describe("setup composio command", () => {
defaultApp: "discord",
mode: "webhook",
webhookUrl: "https://discord.com/api/webhooks/1234567890/webhook-token",
userId: "ao-local",
userId: "ao-agent",
toolVersion: "20260429_01",
composioApiKey: "ak_interactive",
});
@ -958,7 +958,7 @@ projects:
await program.parseAsync(["node", "test", "setup", "composio"]);
expect(mockAuthConfigsCreate).not.toHaveBeenCalledWith("gmail", expect.anything());
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-local", "auth_gmail_send", {
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-agent", "auth_gmail_send", {
allowMultiple: true,
});
const writtenYaml = mockWriteFileSync.mock.calls[0][1] as string;
@ -1051,7 +1051,7 @@ projects:
plugin: "composio",
defaultApp: "slack",
composioApiKey: "ak_interactive",
userId: "ao-local",
userId: "ao-agent",
channelName: "iamasx",
connectedAccountId: "ca_slack_123",
});
@ -1059,6 +1059,35 @@ projects:
expect(parsed.defaults?.notifiers).toContain("composio-slack");
});
it("preserves an existing ao-local Composio userId", async () => {
mockReadFileSync.mockReturnValue(`
notifiers:
composio-slack:
plugin: composio
defaultApp: slack
userId: ao-local
`);
const program = createProgram();
await program.parseAsync([
"node",
"test",
"setup",
"composio-slack",
"--api-key",
"ak_test",
"--connected-account-id",
"ca_slack_123",
"--non-interactive",
]);
const writtenYaml = mockWriteFileSync.mock.calls[0][1] as string;
const parsed = parseYaml(writtenYaml) as {
notifiers?: Record<string, Record<string, unknown>>;
};
expect(parsed.notifiers?.["composio-slack"]?.["userId"]).toBe("ao-local");
});
it("interactive Composio Discord webhook setup writes the dedicated notifier", async () => {
Object.defineProperty(process.stdin, "isTTY", { value: true, configurable: true });
mockClack.select
@ -1084,7 +1113,7 @@ projects:
defaultApp: "discord",
mode: "webhook",
webhookUrl: "https://discord.com/api/webhooks/1234567890/webhook-token",
userId: "ao-local",
userId: "ao-agent",
});
expect(parsed.notifiers?.["composio"]).toBeUndefined();
expect(parsed.defaults?.notifiers).toContain("composio-discord");
@ -1124,7 +1153,7 @@ projects:
defaultApp: "discord",
mode: "bot",
channelId: "1234567890",
userId: "ao-local",
userId: "ao-agent",
connectedAccountId: "ca_discord_123",
});
expect(writtenYaml).not.toContain("bot-token");
@ -1175,7 +1204,7 @@ projects:
plugin: "composio",
defaultApp: "gmail",
emailTo: "admin@example.com",
userId: "ao-local",
userId: "ao-agent",
connectedAccountId: "ca_gmail_123",
});
expect(parsed.notifiers?.["composio"]).toBeUndefined();
@ -1515,7 +1544,7 @@ projects:
defaultApp: "discord",
mode: "webhook",
webhookUrl: "https://discord.com/api/webhooks/1234567890/webhook-token",
userId: "ao-local",
userId: "ao-agent",
toolVersion: "20260429_01",
composioApiKey: "ak_test",
});
@ -1557,7 +1586,7 @@ projects:
authScheme: "BEARER_TOKEN",
credentials: { token: "bot-token" },
});
expect(mockConnectedAccountsInitiate).toHaveBeenCalledWith("ao-local", "auth_discord_created", {
expect(mockConnectedAccountsInitiate).toHaveBeenCalledWith("ao-agent", "auth_discord_created", {
allowMultiple: true,
config: {
authScheme: "BEARER_TOKEN",
@ -1580,7 +1609,7 @@ projects:
defaultApp: "discord",
mode: "bot",
channelId: "1234567890",
userId: "ao-local",
userId: "ao-agent",
connectedAccountId: "ca_discord_123",
toolVersion: "20260429_01",
composioApiKey: "ak_test",
@ -1783,7 +1812,7 @@ projects:
]);
expect(mockAuthConfigsList).toHaveBeenCalledWith({ toolkit: "gmail" });
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-local", "auth_gmail_send", {
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-agent", "auth_gmail_send", {
allowMultiple: true,
});
expect(mockWriteFileSync).not.toHaveBeenCalled();
@ -1877,7 +1906,7 @@ projects:
]);
expect(mockAuthConfigsList).not.toHaveBeenCalledWith({ toolkit: "gmail" });
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-local", "auth_gmail_custom", {
expect(mockConnectedAccountsLink).toHaveBeenCalledWith("ao-agent", "auth_gmail_custom", {
allowMultiple: true,
});
expect(mockWriteFileSync).not.toHaveBeenCalled();

View File

@ -22,11 +22,13 @@ const COMPOSIO_SLACK_NOTIFIER = "composio-slack";
const COMPOSIO_DISCORD_WEBHOOK_NOTIFIER = "composio-discord";
const COMPOSIO_DISCORD_BOT_NOTIFIER = "composio-discord-bot";
const COMPOSIO_MAIL_NOTIFIER = "composio-mail";
const DEFAULT_COMPOSIO_USER_ID = "ao-agent";
const GMAIL_SEND_TOOL = "GMAIL_SEND_EMAIL";
const COMPOSIO_DASHBOARD_URL = "https://app.composio.dev";
const DISCORD_APP_URL = "https://discord.com/app";
const DISCORD_DEVELOPER_PORTAL_URL = "https://discord.com/developers/applications";
const DISCORD_WEBHOOK_DOCS_URL = "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
const DISCORD_WEBHOOK_DOCS_URL =
"https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
export class ComposioSetupError extends Error {
constructor(
@ -208,7 +210,9 @@ function asStringArray(value: unknown): string[] {
return [];
}
function resolveComposioRoutingPreset(value: string | undefined): NotifierRoutingPreset | undefined {
function resolveComposioRoutingPreset(
value: string | undefined,
): NotifierRoutingPreset | undefined {
try {
return resolveRoutingPresetOption(value, "Composio") as NotifierRoutingPreset | undefined;
} catch (error) {
@ -298,7 +302,7 @@ function resolveUserId(opts: { userId?: string }, existing: Record<string, unkno
stringValue(existing["entityId"]) ??
stringValue(process.env.COMPOSIO_USER_ID) ??
stringValue(process.env.COMPOSIO_ENTITY_ID) ??
"ao-local"
DEFAULT_COMPOSIO_USER_ID
);
}
@ -1001,10 +1005,7 @@ function printDiscordChannelIdInstructions(): void {
console.log("");
}
function printComposioDiscordBotReview(
resolved: ResolvedDiscordSetup,
apiKeySource: string,
): void {
function printComposioDiscordBotReview(resolved: ResolvedDiscordSetup, apiKeySource: string): void {
console.log("");
console.log(chalk.bold("Review Composio Discord bot setup"));
console.log(" app: Discord bot");
@ -1170,7 +1171,7 @@ async function promptInteractiveComposioUserId(
const currentUserId = resolveUserId(opts, existing);
console.log(
chalk.dim(
"userId is the Composio user namespace AO uses for tool execution and connected-account lookup. For local setups, ao-local is fine.",
`userId is the Composio user namespace AO uses for tool execution and connected-account lookup. For AO-managed setups, ${DEFAULT_COMPOSIO_USER_ID} is the recommended default.`,
),
);
@ -1439,7 +1440,9 @@ async function promptInteractiveSlackChannel(
existing: Record<string, unknown>,
): Promise<string | undefined | "back"> {
const existingChannel =
stringValue(opts.channel) ?? stringValue(existing["channelName"]) ?? stringValue(existing["channelId"]);
stringValue(opts.channel) ??
stringValue(existing["channelName"]) ??
stringValue(existing["channelId"]);
printComposioSlackChannelInfo();
while (true) {
@ -2091,7 +2094,11 @@ async function promptManualGmailConnectedAccountId(
}
try {
const account = await verifyUsableGmailConnectedAccount(client, userId, String(accountId).trim());
const account = await verifyUsableGmailConnectedAccount(
client,
userId,
String(accountId).trim(),
);
return account.id;
} catch (error) {
console.log(chalk.yellow(error instanceof Error ? error.message : String(error)));
@ -2171,7 +2178,9 @@ async function listGmailAuthConfigs(client: ComposioSetupClient): Promise<AuthCo
"Composio SDK client does not expose authConfigs.list(); enter a Gmail authConfigId manually.",
);
}
return authConfigsFromListResult(await client.authConfigs.list({ toolkit: GMAIL_TOOLKIT })).filter(
return authConfigsFromListResult(
await client.authConfigs.list({ toolkit: GMAIL_TOOLKIT }),
).filter(
(config) => !config.toolkit?.slug || config.toolkit.slug.toLowerCase() === GMAIL_TOOLKIT,
);
}
@ -2806,9 +2815,7 @@ async function runInteractiveComposioDiscordWebhookSetup(
writeComposioDiscordConfig(configPath, resolved);
console.log(chalk.green(`✓ Config written to ${configPath}`));
console.log(chalk.green("✓ Discord webhook configured through Composio"));
console.log(
chalk.dim(`Test it with: ao notify test --to ${targetName} --template basic`),
);
console.log(chalk.dim(`Test it with: ao notify test --to ${targetName} --template basic`));
clack.outro("Composio Discord webhook setup complete.");
return "done";
}
@ -2828,7 +2835,7 @@ async function runInteractiveComposioDiscordBotSetup(
const existingChannelId =
stringValue(opts.channelId) ?? (existingIsBot ? stringValue(existing["channelId"]) : undefined);
const existingConnectedAccountId = existingIsBot
? stringValue(opts.connectedAccountId) ?? stringValue(existing["connectedAccountId"])
? (stringValue(opts.connectedAccountId) ?? stringValue(existing["connectedAccountId"]))
: stringValue(opts.connectedAccountId);
const optionBotToken = stringValue(opts.botToken);
const canReplace = await confirmComposioDiscordWebhookConflict(
@ -2897,7 +2904,8 @@ async function runInteractiveComposioDiscordBotSetup(
client,
userId,
channelId,
connectedAccountId ?? (channelId === existingChannelId ? existingConnectedAccountId : undefined),
connectedAccountId ??
(channelId === existingChannelId ? existingConnectedAccountId : undefined),
optionBotToken,
);
if (result === "back") {
@ -2986,10 +2994,11 @@ async function runInteractiveComposioGmailSetup(
const existingEmailTo =
stringValue(opts.emailTo) ?? (existingIsGmail ? stringValue(existing["emailTo"]) : undefined);
const existingConnectedAccountId = existingIsGmail
? stringValue(opts.connectedAccountId) ?? stringValue(existing["connectedAccountId"])
? (stringValue(opts.connectedAccountId) ?? stringValue(existing["connectedAccountId"]))
: stringValue(opts.connectedAccountId);
const existingAuthConfigId =
stringValue(opts.authConfigId) ?? (existingIsGmail ? stringValue(existing["authConfigId"]) : undefined);
stringValue(opts.authConfigId) ??
(existingIsGmail ? stringValue(existing["authConfigId"]) : undefined);
const canReplace = await confirmComposioDiscordWebhookConflict(
clack,
targetName,

View File

@ -131,7 +131,7 @@ notifiers:
composio:
plugin: composio
# Run 'ao setup composio' to connect Slack through Composio
# userId: ao-local
# userId: ao-agent
# connectedAccountId: ca_...
# channelName: "#agents"
# composioApiKey: ak_... # optional; otherwise uses COMPOSIO_API_KEY
@ -142,7 +142,7 @@ notifiers:
# defaultApp: discord
# mode: webhook
# webhookUrl: https://discord.com/api/webhooks/...
# userId: ao-local
# userId: ao-agent
# composioApiKey: ak_... # optional; otherwise uses COMPOSIO_API_KEY
composio-discord-bot:
plugin: composio
@ -150,7 +150,7 @@ notifiers:
# defaultApp: discord
# mode: bot
# channelId: "1234567890"
# userId: ao-local
# userId: ao-agent
# connectedAccountId: ca_...
# composioApiKey: ak_... # optional; otherwise uses COMPOSIO_API_KEY
composio-mail:
@ -158,7 +158,7 @@ notifiers:
# Run 'ao setup composio-mail' to connect Gmail through Composio
# defaultApp: gmail
# emailTo: alerts@example.com
# userId: ao-local
# userId: ao-agent
# connectedAccountId: ca_...
# composioApiKey: ak_... # optional; otherwise uses COMPOSIO_API_KEY

View File

@ -131,6 +131,7 @@ describe("notifier-composio", () => {
expect(mockToolsExecute).toHaveBeenCalledWith(
"SLACK_SEND_MESSAGE",
expect.objectContaining({
userId: "ao-agent",
arguments: expect.objectContaining({ markdown_text: expect.any(String) }),
}),
);
@ -493,7 +494,7 @@ describe("notifier-composio", () => {
it("uses mail setup guidance for Gmail connection errors", async () => {
mockToolsExecute.mockRejectedValueOnce(
new Error("No connected account found for user ao-local for toolkit gmail"),
new Error("No connected account found for user ao-agent for toolkit gmail"),
);
const notifier = create({

View File

@ -51,6 +51,7 @@ const DEFAULT_TOOL_VERSION: Partial<Record<ComposioApp, string>> = {
const VALID_APPS = new Set<string>(["slack", "discord", "gmail"]);
const VALID_DISCORD_MODES = new Set<string>(["webhook", "bot"]);
const DEFAULT_COMPOSIO_USER_ID = "ao-agent";
const GMAIL_SUBJECT = "Agent Orchestrator Notification";
const DISCORD_WEBHOOK_TOOL_SLUG = "DISCORDBOT_EXECUTE_WEBHOOK";
@ -352,7 +353,7 @@ export function create(config?: Record<string, unknown>): Notifier {
stringConfig(config, "entityId") ??
process.env.COMPOSIO_USER_ID ??
process.env.COMPOSIO_ENTITY_ID ??
"ao-local";
DEFAULT_COMPOSIO_USER_ID;
const emailTo = stringConfig(config, "emailTo");
const toolVersion = resolveToolVersion(config, defaultApp);
const forceSkipVersionCheck = boolConfig(config, "dangerouslySkipVersionCheck");

View File

@ -37,7 +37,7 @@ You can also run the scriptable Slack setup directly:
```bash
export COMPOSIO_API_KEY=ak_...
ao setup composio-slack --user-id ao-local --channel "#agents" --non-interactive
ao setup composio-slack --user-id ao-agent --channel "#agents" --non-interactive
```
All Composio setup commands accept `--routing-preset urgent-only`,
@ -99,7 +99,7 @@ notifiers:
composio:
plugin: composio
defaultApp: slack # slack | discord | gmail
userId: ao-local # Composio user id
userId: ao-agent # Composio user id
connectedAccountId: ca_... # preferred when available
channelName: "#agents" # Slack
emailTo: alerts@example.com # required when defaultApp=gmail
@ -110,48 +110,48 @@ notifiers:
defaultApp: discord
mode: webhook
webhookUrl: https://discord.com/api/webhooks/...
userId: ao-local
userId: ao-agent
composio-discord-bot:
plugin: composio
defaultApp: discord
mode: bot
channelId: "1234567890"
userId: ao-local
userId: ao-agent
connectedAccountId: ca_...
composio-mail:
plugin: composio
defaultApp: gmail
emailTo: alerts@example.com
userId: ao-local
userId: ao-agent
connectedAccountId: ca_...
composio-slack:
plugin: composio
defaultApp: slack
userId: ao-local
userId: ao-agent
connectedAccountId: ca_...
channelName: "#agents"
```
## Config
| Key | Required | Default | What it does |
| -------------------- | --------------- | ---------------------- | ------------------------------------------------------------------ |
| `defaultApp` | ✓ | `slack` | Which Composio app to target: `slack`, `discord`, or `gmail` |
| `mode` | Discord only | auto | `webhook` uses Discord webhooks; `bot` uses bot channel messages |
| `userId` | optional | `ao-local` | Composio user id for connected-account lookup |
| `entityId` | optional | — | Backward-compatible alias for `userId` |
| `authConfigId` | setup only | — | Existing Composio Gmail auth config used by `--connect` |
| `connectedAccountId` | Slack/Gmail/bot | — | Specific connected account to use; not used for Discord webhook mode |
| `channelName` | for Slack | — | Slack channel name |
| `channelId` | Discord bot | — | Discord channel id for bot mode |
| `webhookUrl` | Discord webhook | — | Discord webhook URL for webhook mode |
| `emailTo` | ✓ when `gmail` | — | Recipient address |
| `composioApiKey` | optional | env `COMPOSIO_API_KEY` | Override the API key from config |
| `toolVersion` | optional | app default | Tool version passed to `@composio/core` |
| `toolVersions` | optional | — | App-specific tool versions, for example `{ slack: "20260508_00" }` |
| Key | Required | Default | What it does |
| -------------------- | --------------- | ---------------------- | -------------------------------------------------------------------- |
| `defaultApp` | ✓ | `slack` | Which Composio app to target: `slack`, `discord`, or `gmail` |
| `mode` | Discord only | auto | `webhook` uses Discord webhooks; `bot` uses bot channel messages |
| `userId` | optional | `ao-agent` | Composio user id for connected-account lookup |
| `entityId` | optional | — | Backward-compatible alias for `userId` |
| `authConfigId` | setup only | — | Existing Composio Gmail auth config used by `--connect` |
| `connectedAccountId` | Slack/Gmail/bot | — | Specific connected account to use; not used for Discord webhook mode |
| `channelName` | for Slack | — | Slack channel name |
| `channelId` | Discord bot | — | Discord channel id for bot mode |
| `webhookUrl` | Discord webhook | — | Discord webhook URL for webhook mode |
| `emailTo` | ✓ when `gmail` | — | Recipient address |
| `composioApiKey` | optional | env `COMPOSIO_API_KEY` | Override the API key from config |
| `toolVersion` | optional | app default | Tool version passed to `@composio/core` |
| `toolVersions` | optional | — | App-specific tool versions, for example `{ slack: "20260508_00" }` |
Slack defaults to `20260508_00`; Discord defaults to `20260429_01`; Gmail defaults to `20260506_01`. Discord uses Composio's `discordbot` toolkit for both webhook and bot modes. Webhook mode calls `DISCORDBOT_EXECUTE_WEBHOOK` with the Discord webhook id/token from `webhookUrl`, so no Composio Discord connect link is required.
If `mode` is omitted for Discord, AO uses `webhook` when `webhookUrl` is present and `bot` otherwise.