agent-messenger 2.26.0 → 2.27.0

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.
Files changed (37) hide show
  1. package/.claude-plugin/README.md +16 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +27 -0
  4. package/dist/package.json +1 -1
  5. package/dist/src/platforms/discord/listener.d.ts +1 -4
  6. package/dist/src/platforms/discord/listener.d.ts.map +1 -1
  7. package/dist/src/platforms/discord/listener.js +33 -15
  8. package/dist/src/platforms/discord/listener.js.map +1 -1
  9. package/dist/src/platforms/slack/qr-http-login.d.ts.map +1 -1
  10. package/dist/src/platforms/slack/qr-http-login.js +105 -6
  11. package/dist/src/platforms/slack/qr-http-login.js.map +1 -1
  12. package/docs/content/docs/sdk/discord.mdx +1 -9
  13. package/e2e/README.md +3 -1
  14. package/package.json +1 -1
  15. package/skills/agent-channeltalk/SKILL.md +1 -1
  16. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  17. package/skills/agent-discord/SKILL.md +37 -8
  18. package/skills/agent-discord/references/authentication.md +46 -8
  19. package/skills/agent-discordbot/SKILL.md +1 -1
  20. package/skills/agent-instagram/SKILL.md +1 -1
  21. package/skills/agent-kakaotalk/SKILL.md +1 -1
  22. package/skills/agent-line/SKILL.md +1 -1
  23. package/skills/agent-slack/SKILL.md +7 -4
  24. package/skills/agent-slack/references/authentication.md +8 -3
  25. package/skills/agent-slackbot/SKILL.md +1 -1
  26. package/skills/agent-teams/SKILL.md +1 -1
  27. package/skills/agent-telegram/SKILL.md +1 -1
  28. package/skills/agent-telegrambot/SKILL.md +1 -1
  29. package/skills/agent-webex/SKILL.md +1 -1
  30. package/skills/agent-webexbot/SKILL.md +1 -1
  31. package/skills/agent-wechatbot/SKILL.md +1 -1
  32. package/skills/agent-whatsapp/SKILL.md +1 -1
  33. package/skills/agent-whatsappbot/SKILL.md +1 -1
  34. package/src/platforms/discord/listener.test.ts +12 -4
  35. package/src/platforms/discord/listener.ts +36 -16
  36. package/src/platforms/slack/qr-http-login.test.ts +47 -0
  37. package/src/platforms/slack/qr-http-login.ts +112 -7
@@ -24,6 +24,7 @@ Or within Claude Code:
24
24
  Enables AI agents to interact with messaging platforms through CLI interfaces:
25
25
 
26
26
  ### Slack (`agent-slack`)
27
+ - **QR code sign-in** — recommended, safest auth (no credential extraction)
27
28
  - **Send messages** to channels and threads
28
29
  - **Read channels** and message history
29
30
  - **Manage reactions** (add/remove/list)
@@ -46,6 +47,7 @@ Enables AI agents to interact with messaging platforms through CLI interfaces:
46
47
  - Designed for **server-side and CI/CD** use cases
47
48
 
48
49
  ### Discord (`agent-discord`)
50
+ - **QR code sign-in** — recommended, scan with the mobile app (no credential extraction)
49
51
  - **Send messages** to channels
50
52
  - **Read channels** and message history
51
53
  - **Manage reactions** (add/remove/list)
@@ -141,7 +143,19 @@ Enables AI agents to interact with messaging platforms through CLI interfaces:
141
143
 
142
144
  ### Zero-Config Authentication
143
145
 
144
- Credentials are automatically extracted from your desktop apps on first command — no manual auth step needed. You can also extract manually:
146
+ Credentials are automatically extracted from your desktop apps on first command — no manual auth step needed. You can also authenticate manually.
147
+
148
+ For **Slack and Discord, QR code sign-in is the recommended method** — it's the safest and most reliable option because it authenticates through the platform's own login flow instead of reading credentials off disk, and it works without a desktop app or browser:
149
+
150
+ ```bash
151
+ # Slack — paste the QR from "Sign in on mobile" (recommended)
152
+ pbpaste | agent-slack auth qr
153
+
154
+ # Discord — scan the QR with the Discord mobile app (recommended)
155
+ agent-discord auth qr
156
+ ```
157
+
158
+ Or extract credentials directly from your desktop apps / browsers:
145
159
 
146
160
  ```bash
147
161
  # Slack
@@ -174,7 +188,7 @@ All commands output JSON by default for easy AI consumption. Use `--pretty` for
174
188
 
175
189
  ## Requirements
176
190
 
177
- - Desktop app installed and logged in for the platform(s) you want to use (Slack, Discord, Teams, KakaoTalk, and/or Channel Talk). Instagram uses username/password auth
191
+ - Desktop app installed and logged in for the platform(s) you want to use (Slack, Discord, Teams, KakaoTalk, and/or Channel Talk). For Slack and Discord, QR code sign-in is the recommended alternative — no desktop app required (Discord needs the mobile app to scan). Instagram uses username/password auth
178
192
  - For Telegram: TDLib is bundled; API credentials are auto-provisioned on first login
179
193
  - Node.js 18+ or Bun runtime
180
194
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.26.0",
3
+ "version": "2.27.0",
4
4
  "description": "Messaging platform interaction skills for AI agents. Interact with Slack, Discord, Microsoft Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, Instagram, KakaoTalk, and Channel Talk - send messages, read channels, manage reactions, upload files, and more through simple CLI interfaces.",
5
5
  "author": {
6
6
  "name": "agent-messenger",
package/README.md CHANGED
@@ -45,6 +45,10 @@ Every platform gates API access behind OAuth apps that need admin approval — d
45
45
 
46
46
  Agent Messenger reads session tokens from your Slack, Discord, Teams, KakaoTalk, or Channel Talk desktop app — zero config. If the desktop app isn't installed, it falls back to extracting from Chromium browsers, with `auth extract --browser-profile <path>` for custom Chromium profile locations. Webex and Instagram tokens are extracted directly from browsers. Telegram authenticates with a one-time phone code, and WhatsApp with a QR code or pairing code. Either way, your agent operates **as you** — same name, same permissions, same context. Bot tokens are fully supported too for server-side and CI/CD use cases.
47
47
 
48
+ > [!TIP]
49
+ > **For Slack and Discord, QR code sign-in (`auth qr`) is the recommended way to authenticate** — it's the safest and most reliable method. It signs you in through the platform's own login flow instead of reading credentials off disk, and works even without the desktop app. See the [Slack](skills/agent-slack/references/authentication.md) and [Discord](skills/agent-discord/references/authentication.md) auth guides.
50
+
51
+ - **QR Code Sign-In (Slack & Discord)** — The recommended, safest auth: sign in through the platform's official flow, no credential extraction and no desktop app required (Discord scans with the mobile app)
48
52
  - **Auto-Extract Auth** — Reads tokens from Slack, Discord, Teams, KakaoTalk, and Channel Talk desktop apps, with browser fallback and custom Chromium profile paths via `--browser-profile`. Webex and Instagram tokens extracted from Chromium browsers. Telegram and WhatsApp authenticate with a one-time code — still under a minute
49
53
  - **Act As Yourself** — Extracts your user session — not a bot token. Your agent sends messages, reacts, and searches as you. Need bot mode? Bot CLIs are included too
50
54
  - **One Interface** — Consistent command style across 7 platforms for supported actions (e.g. message send, message search, channel list, snapshot). Learn once
@@ -135,6 +139,13 @@ agent-slack message send general "Hello from the CLI!"
135
139
 
136
140
  That's it. Credentials are extracted automatically from your Slack desktop app on first run. No OAuth flows. No API tokens. No configuration files.
137
141
 
142
+ > **No desktop app?** QR code sign-in is the recommended way to authenticate Slack and Discord. In Slack, open "Sign in on mobile", copy the QR image, and pipe it in; in Discord, scan the printed QR with the mobile app:
143
+ >
144
+ > ```bash
145
+ > pbpaste | agent-slack auth qr # paste Slack's "Sign in on mobile" QR
146
+ > agent-discord auth qr # scan the printed QR with the Discord app
147
+ > ```
148
+
138
149
  ### Custom config directory
139
150
 
140
151
  By default, Agent Messenger stores credentials, sync state, and derived-key caches under `~/.config/agent-messenger`. To relocate everything (e.g. for CI sandboxes, multi-tenant setups, or non-default home directories), set the `AGENT_MESSENGER_CONFIG_DIR` environment variable:
@@ -229,6 +240,22 @@ const session = await loginWithQr(dataUrl)
229
240
  const client = await new SlackClient().login({ token: session.token, cookie: session.cookie })
230
241
  ```
231
242
 
243
+ ### QR Code Login (Discord)
244
+
245
+ Sign in by scanning a QR code with the Discord mobile app — no desktop app or browser token extraction required. `loginWithRemoteAuth` runs Discord's Remote Auth protocol and hands you a QR URL to display; once the user scans and confirms on their phone, you receive the user token.
246
+
247
+ ```typescript
248
+ import { DiscordClient, loginWithRemoteAuth } from 'agent-messenger/discord'
249
+
250
+ const session = await loginWithRemoteAuth({
251
+ onQrUrl: (url) => {
252
+ // Render this URL as a QR code (e.g. with the `qrcode` package)
253
+ console.log('Scan this with the Discord mobile app:', url)
254
+ },
255
+ })
256
+ const client = await new DiscordClient().login({ token: session.token })
257
+ ```
258
+
232
259
  ### Real-time Events (Slack)
233
260
 
234
261
  ```typescript
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.26.0",
3
+ "version": "2.27.0",
4
4
  "description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, Instagram, KakaoTalk, Channel Talk)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,7 +3,6 @@ import type { DiscordListenerEventMap } from './types.js';
3
3
  type EventKey = keyof DiscordListenerEventMap;
4
4
  export declare class DiscordListener {
5
5
  private client;
6
- private intents;
7
6
  private running;
8
7
  private ws;
9
8
  private emitter;
@@ -19,9 +18,7 @@ export declare class DiscordListener {
19
18
  private token;
20
19
  private cachedUser;
21
20
  private generation;
22
- constructor(client: DiscordClient, options?: {
23
- intents?: number;
24
- });
21
+ constructor(client: DiscordClient);
25
22
  start(): Promise<void>;
26
23
  stop(): void;
27
24
  on<K extends EventKey>(event: K, listener: (...args: DiscordListenerEventMap[K]) => void): this;
@@ -1 +1 @@
1
- {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../../src/platforms/discord/listener.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,uBAAuB,EAA8B,MAAM,SAAS,CAAA;AAmBlF,KAAK,QAAQ,GAAG,MAAM,uBAAuB,CAAA;AAE7C,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,oBAAoB,CAAO;IACnC,OAAO,CAAC,oBAAoB,CAA6C;IACzE,OAAO,CAAC,mBAAmB,CAA6C;IACxE,OAAO,CAAC,cAAc,CAA6C;IACnE,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,UAAU,CAAgD;IAClE,OAAO,CAAC,UAAU,CAAI;gBAEV,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAK3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,IAAI,IAAI,IAAI;IAeZ,EAAE,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAK/F,GAAG,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAKhG,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAKjG,OAAO,CAAC,SAAS;YAMH,OAAO;IA+DrB,OAAO,CAAC,aAAa;IAkDrB,OAAO,CAAC,cAAc;IAsBtB,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,WAAW;CAWpB"}
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../../src/platforms/discord/listener.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,uBAAuB,EAA8B,MAAM,SAAS,CAAA;AAyBlF,KAAK,QAAQ,GAAG,MAAM,uBAAuB,CAAA;AAE7C,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,oBAAoB,CAAO;IACnC,OAAO,CAAC,oBAAoB,CAA6C;IACzE,OAAO,CAAC,mBAAmB,CAA6C;IACxE,OAAO,CAAC,cAAc,CAA6C;IACnE,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,UAAU,CAAgD;IAClE,OAAO,CAAC,UAAU,CAAI;gBAEV,MAAM,EAAE,aAAa;IAI3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,IAAI,IAAI,IAAI;IAeZ,EAAE,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAK/F,GAAG,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAKhG,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAKjG,OAAO,CAAC,SAAS;YAMH,OAAO;IA+DrB,OAAO,CAAC,aAAa;IAkDrB,OAAO,CAAC,cAAc;IAsBtB,OAAO,CAAC,YAAY;IAiCpB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,WAAW;CAWpB"}
@@ -1,22 +1,26 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import WebSocket from 'ws';
3
- import { DiscordGatewayOpcode, DiscordIntent } from './types.js';
3
+ import { DiscordGatewayOpcode } from './types.js';
4
4
  const GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json';
5
5
  const GATEWAY_QUERY = '?v=10&encoding=json';
6
6
  const RECONNECT_BASE_DELAY = 1_000;
7
7
  const RECONNECT_MAX_DELAY = 30_000;
8
8
  const NON_RECOVERABLE_CLOSE_CODES = [4004, 4010, 4011, 4012, 4013, 4014];
9
9
  const SESSION_RESET_CLOSE_CODES = [4007, 4009];
10
- const DEFAULT_INTENTS = DiscordIntent.Guilds |
11
- DiscordIntent.GuildMessages |
12
- DiscordIntent.GuildMessageReactions |
13
- DiscordIntent.GuildMessageTyping |
14
- DiscordIntent.DirectMessages |
15
- DiscordIntent.DirectMessageReactions |
16
- DiscordIntent.DirectMessageTyping;
10
+ // User (non-bot) gateway capabilities bitmask, mirroring discord.py-self's default set.
11
+ // Capabilities shape the READY payload; bit 10 (client_state_v2) requires client_state.guild_versions.
12
+ const USER_GATEWAY_CAPABILITIES = 16381;
13
+ // Without MESSAGE_CONTENT (1<<15), Discord blanks `content`/`embeds`/`attachments` on messages
14
+ // from OTHER users (self/DM/mention content still arrives). User sessions get all intents only
15
+ // when `intents` is omitted OR explicitly set, so we send an all-intents value to guarantee content.
16
+ const USER_GATEWAY_INTENTS = 33_554_431;
17
+ // Discord validates client_build_number against recent web-client builds; a stale value can
18
+ // yield a connected-but-degraded session that fires READY yet delivers no message events.
19
+ // Overridable via env so it can be refreshed without a release when it eventually goes stale.
20
+ const DEFAULT_CLIENT_BUILD_NUMBER = 648814;
21
+ const USER_GATEWAY_BUILD_NUMBER = Number(process.env.AGENT_DISCORD_BUILD_NUMBER) || DEFAULT_CLIENT_BUILD_NUMBER;
17
22
  export class DiscordListener {
18
23
  client;
19
- intents;
20
24
  running = false;
21
25
  ws = null;
22
26
  emitter = new EventEmitter();
@@ -32,9 +36,8 @@ export class DiscordListener {
32
36
  token = null;
33
37
  cachedUser = null;
34
38
  generation = 0;
35
- constructor(client, options) {
39
+ constructor(client) {
36
40
  this.client = client;
37
- this.intents = options?.intents ?? DEFAULT_INTENTS;
38
41
  }
39
42
  async start() {
40
43
  if (this.running)
@@ -213,12 +216,27 @@ export class DiscordListener {
213
216
  op: DiscordGatewayOpcode.Identify,
214
217
  d: {
215
218
  token: this.token,
216
- intents: this.intents,
219
+ capabilities: USER_GATEWAY_CAPABILITIES,
220
+ intents: USER_GATEWAY_INTENTS,
217
221
  properties: {
218
- os: 'linux',
219
- browser: 'agent-messenger',
220
- device: 'agent-messenger',
222
+ os: 'Linux',
223
+ browser: 'Chrome',
224
+ device: '',
225
+ system_locale: 'en-US',
226
+ browser_user_agent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
227
+ browser_version: '131.0.0.0',
228
+ os_version: '',
229
+ referrer: '',
230
+ referring_domain: '',
231
+ referrer_current: '',
232
+ referring_domain_current: '',
233
+ release_channel: 'stable',
234
+ client_build_number: USER_GATEWAY_BUILD_NUMBER,
235
+ client_event_source: null,
221
236
  },
237
+ presence: { status: 'online', since: 0, activities: [], afk: false },
238
+ compress: false,
239
+ client_state: { guild_versions: {} },
222
240
  },
223
241
  }));
224
242
  }
@@ -1 +1 @@
1
- {"version":3,"file":"listener.js","sourceRoot":"","sources":["../../../../src/platforms/discord/listener.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,SAAS,MAAM,IAAI,CAAA;AAI1B,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE7D,MAAM,WAAW,GAAG,8CAA8C,CAAA;AAClE,MAAM,aAAa,GAAG,qBAAqB,CAAA;AAC3C,MAAM,oBAAoB,GAAG,KAAK,CAAA;AAClC,MAAM,mBAAmB,GAAG,MAAM,CAAA;AAClC,MAAM,2BAA2B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AACxE,MAAM,yBAAyB,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AAE9C,MAAM,eAAe,GACnB,aAAa,CAAC,MAAM;IACpB,aAAa,CAAC,aAAa;IAC3B,aAAa,CAAC,qBAAqB;IACnC,aAAa,CAAC,kBAAkB;IAChC,aAAa,CAAC,cAAc;IAC5B,aAAa,CAAC,sBAAsB;IACpC,aAAa,CAAC,mBAAmB,CAAA;AAInC,MAAM,OAAO,eAAe;IAClB,MAAM,CAAe;IACrB,OAAO,CAAQ;IACf,OAAO,GAAG,KAAK,CAAA;IACf,EAAE,GAAqB,IAAI,CAAA;IAC3B,OAAO,GAAG,IAAI,YAAY,EAAE,CAAA;IAC5B,cAAc,GAA0C,IAAI,CAAA;IAC5D,oBAAoB,GAAG,IAAI,CAAA;IAC3B,oBAAoB,GAAyC,IAAI,CAAA;IACjE,mBAAmB,GAAyC,IAAI,CAAA;IAChE,cAAc,GAAyC,IAAI,CAAA;IAC3D,iBAAiB,GAAG,CAAC,CAAA;IACrB,QAAQ,GAAkB,IAAI,CAAA;IAC9B,SAAS,GAAkB,IAAI,CAAA;IAC/B,gBAAgB,GAAkB,IAAI,CAAA;IACtC,KAAK,GAAkB,IAAI,CAAA;IAC3B,UAAU,GAA4C,IAAI,CAAA;IAC1D,UAAU,GAAG,CAAC,CAAA;IAEtB,YAAY,MAAqB,EAAE,OAA8B;QAC/D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,eAAe,CAAA;IACpD,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO;YAAE,OAAM;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAA;QAC1B,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACrC,CAAC;IAED,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,IAAI,CAAC,WAAW,EAAE,CAAA;QAClB,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;YACf,IAAI,CAAC,EAAE,GAAG,IAAI,CAAA;QAChB,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;IACxB,CAAC;IAED,EAAE,CAAqB,KAAQ,EAAE,QAAuD;QACtF,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAoC,CAAC,CAAA;QAC5D,OAAO,IAAI,CAAA;IACb,CAAC;IAED,GAAG,CAAqB,KAAQ,EAAE,QAAuD;QACvF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAoC,CAAC,CAAA;QAC7D,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAqB,KAAQ,EAAE,QAAuD;QACxF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,QAAoC,CAAC,CAAA;QAC9D,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,SAAS,CAAC,UAAkB,EAAE,EAAc;QAClD,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAA;QACjE,IAAI,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE;YAAE,OAAO,KAAK,CAAA;QACpD,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,UAAkB;QACtC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;YAAE,OAAM;QAEvC,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAA;YACpD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;gBAAE,OAAM;YAEvC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;YAElB,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,gBAAgB,GAAG,aAAa,EAAE,CAAC,CAAC,CAAC,WAAW,CAAA;YAC5F,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAA;YAC7B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;YAEZ,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC;oBACpC,EAAE,CAAC,KAAK,EAAE,CAAA;oBACV,OAAM;gBACR,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;gBACvB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC;oBAAE,OAAM;gBAC3C,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAA;oBACvC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;gBAC1C,CAAC;gBAAC,MAAM,CAAC;oBACP,oEAAoE;gBACtE,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACtB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC;oBAAE,OAAM;gBAC3C,IAAI,CAAC,WAAW,EAAE,CAAA;gBAClB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAA;gBACd,IAAI,2BAA2B,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,oDAAoD,IAAI,EAAE,CAAC,CAAC,CAAA;oBACjG,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;oBACpB,OAAM;gBACR,CAAC;gBACD,IAAI,yBAAyB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC7C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;oBACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;oBACrB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;gBAC9B,CAAC;gBACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACjB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;oBACjC,IAAI,CAAC,iBAAiB,EAAE,CAAA;gBAC1B,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACrB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC;oBAAE,OAAM;gBAC3C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACjF,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;gBAAE,OAAM;YACvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACrF,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,IAAoD,EAAE,UAAkB,EAAE,EAAa;QAC3G,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC;YAAE,OAAM;QAC3C,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAA;QAE5B,QAAQ,EAAE,EAAE,CAAC;YACX,KAAK,oBAAoB,CAAC,KAAK;gBAC7B,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,kBAAkB,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;gBACzD,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;oBACnB,IAAI,CAAC,UAAU,EAAE,CAAA;gBACnB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,YAAY,EAAE,CAAA;gBACrB,CAAC;gBACD,MAAK;YAEP,KAAK,oBAAoB,CAAC,YAAY;gBACpC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAA;gBAChC,MAAK;YAEP,KAAK,oBAAoB,CAAC,QAAQ;gBAChC,IAAI,OAAO,CAAC,KAAK,QAAQ;oBAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;gBAC5C,IAAI,CAAC;oBAAE,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;gBAChC,MAAK;YAEP,KAAK,oBAAoB,CAAC,SAAS;gBACjC,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAA;gBAC1B,EAAE,CAAC,KAAK,EAAE,CAAA;gBACV,MAAK;YAEP,KAAK,oBAAoB,CAAC,cAAc,CAAC,CAAC,CAAC;gBACzC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;oBACf,MAAM,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAA;oBACzC,IAAI,CAAC,mBAAmB,GAAG,UAAU,CAAC,GAAG,EAAE;wBACzC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAA;wBAC/B,IAAI,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC;4BAAE,EAAE,CAAC,KAAK,EAAE,CAAA;oBAChD,CAAC,EAAE,KAAK,CAAC,CAAA;gBACX,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;oBACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;oBACrB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;oBAC5B,EAAE,CAAC,KAAK,EAAE,CAAA;gBACZ,CAAC;gBACD,MAAK;YACP,CAAC;YAED,KAAK,oBAAoB,CAAC,SAAS;gBACjC,IAAI,CAAC,aAAa,EAAE,CAAA;gBACpB,MAAK;QACT,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,CAAS,EAAE,CAAM;QACtC,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,UAAU,CAAA;YAC7B,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,kBAAkB,CAAA;YAC5C,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,CAAA;YACxB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAA;YACzE,OAAM;QACR,CAAC;QAED,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAA;YAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,UAAW,EAAE,SAAS,EAAE,IAAI,CAAC,SAAU,EAAE,CAAC,CAAA;YACtF,OAAM;QACR,CAAC;QAED,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;QACjC,MAAM,KAAK,GAA+B,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;QAC3D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QACnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,CAAA;IAC3C,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,EAAE,EAAE,IAAI,CACX,IAAI,CAAC,SAAS,CAAC;YACb,EAAE,EAAE,oBAAoB,CAAC,QAAQ;YACjC,CAAC,EAAE;gBACD,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,UAAU,EAAE;oBACV,EAAE,EAAE,OAAO;oBACX,OAAO,EAAE,iBAAiB;oBAC1B,MAAM,EAAE,iBAAiB;iBAC1B;aACF;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,EAAE,EAAE,IAAI,CACX,IAAI,CAAC,SAAS,CAAC;YACb,EAAE,EAAE,oBAAoB,CAAC,MAAM;YAC/B,CAAC,EAAE;gBACD,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,UAAU,EAAE,IAAI,CAAC,SAAS;gBAC1B,GAAG,EAAE,IAAI,CAAC,QAAQ;aACnB;SACF,CAAC,CACH,CAAA;IACH,CAAC;IAEO,aAAa;QACnB,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,oBAAoB,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IACzF,CAAC;IAEO,cAAc,CAAC,QAAgB,EAAE,UAAkB,EAAE,EAAa;QACxE,IAAI,CAAC,oBAAoB,EAAE,CAAA;QAC3B,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAA;QAEhC,IAAI,CAAC,oBAAoB,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1C,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAA;YAChC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC;gBAAE,OAAM;YAC3C,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAA;YACjC,IAAI,CAAC,aAAa,EAAE,CAAA;YAEpB,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;gBACrC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC;oBACpC,IAAI,CAAC,oBAAoB,EAAE,CAAA;oBAC3B,OAAM;gBACR,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,CAAC;oBAC/B,EAAE,CAAC,KAAK,EAAE,CAAA;oBACV,OAAM;gBACR,CAAC;gBACD,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAA;gBACjC,IAAI,CAAC,aAAa,EAAE,CAAA;YACtB,CAAC,EAAE,QAAQ,CAAC,CAAA;QACd,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAA;IAC9B,CAAC;IAEO,iBAAiB;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,oBAAoB,GAAG,CAAC,IAAI,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAA;QAC/F,IAAI,CAAC,iBAAiB,EAAE,CAAA;QACxB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAClC,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;YAC1B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAC1B,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAEO,oBAAoB;QAC1B,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC5B,CAAC;QACD,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC9B,YAAY,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YACvC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAA;QAClC,CAAC;IACH,CAAC;IAEO,WAAW;QACjB,IAAI,CAAC,oBAAoB,EAAE,CAAA;QAC3B,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,YAAY,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;YACtC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAA;QACjC,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YACjC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC5B,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"listener.js","sourceRoot":"","sources":["../../../../src/platforms/discord/listener.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"qr-http-login.d.ts","sourceRoot":"","sources":["../../../../src/platforms/slack/qr-http-login.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAMD,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAwBnG;AAqDD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAQnD;AAED,wBAAgB,YAAY,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGnE"}
1
+ {"version":3,"file":"qr-http-login.d.ts","sourceRoot":"","sources":["../../../../src/platforms/slack/qr-http-login.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAMD,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAwBnG;AA8JD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAQnD;AAED,wBAAgB,YAAY,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGnE"}
@@ -7,9 +7,9 @@ export async function loginWithQr(dataUrl, options = {}) {
7
7
  const login = decodeSlackQr(dataUrl.trim());
8
8
  const debug = options.debug;
9
9
  debug?.(`Decoded QR for workspace ${login.workspace}`);
10
- const cookie = await captureDCookie(login.url, options);
10
+ const { cookie, denialReason, ssoProvider } = await captureDCookie(login.url, options);
11
11
  if (!cookie) {
12
- throw new SlackError('Could not establish a Slack session from the QR code. The link may have expired — generate a new QR code and try again.', 'qr_session_failed');
12
+ throw new SlackError(qrSessionFailureMessage(denialReason, { workspace: login.workspace, ssoProvider }), 'qr_session_failed');
13
13
  }
14
14
  debug?.('Captured session cookie');
15
15
  const token = await refreshTokenFromWeb(login.workspace, cookie, options.fetchImpl ?? fetch);
@@ -19,12 +19,60 @@ export async function loginWithQr(dataUrl, options = {}) {
19
19
  debug?.('Retrieved client token');
20
20
  return { token, cookie, workspace: login.workspace };
21
21
  }
22
+ function qrSessionFailureMessage(denialReason, context = {}) {
23
+ const workspace = context.workspace ? `"${context.workspace}"` : 'this workspace';
24
+ if (denialReason === 'sso_required') {
25
+ const via = context.ssoProvider ? ` (via ${context.ssoProvider})` : '';
26
+ return [
27
+ `Slack workspace ${workspace} enforces SSO${via}, so QR sign-in cannot complete here.`,
28
+ '',
29
+ `Why: scanning the QR redirects the login to your identity provider${via}. agent-slack follows the`,
30
+ 'redirect chain over plain HTTP and never sends your session cookie off Slack-owned hosts, so it',
31
+ 'stops at the IdP and no Slack session (the "d" cookie) is ever issued. Completing SSO requires a',
32
+ 'real browser, which a headless environment does not have.',
33
+ '',
34
+ 'What to do instead:',
35
+ ' On a computer where you are already signed in to Slack (desktop app or a Chromium browser),',
36
+ ' run: agent-slack auth extract',
37
+ ' This reads your existing session locally — no SSO re-login, no DevTools.',
38
+ '',
39
+ 'Background: https://github.com/agent-messenger/agent-messenger/issues/260',
40
+ ].join('\n');
41
+ }
42
+ if (denialReason === 'link_expired') {
43
+ return [
44
+ `The Slack QR code for ${workspace} has expired or was already used.`,
45
+ 'The z-app sign-in link is single-use and short-lived: it is consumed the first time it is opened',
46
+ '(including previewing or scanning it), and re-using it returns "Link Expired".',
47
+ '',
48
+ 'Generate a fresh QR (your name → "Sign in on mobile") and pipe it in immediately, without',
49
+ 'previewing or scanning it first.',
50
+ ].join('\n');
51
+ }
52
+ if (denialReason === 'awaiting_device_confirmation') {
53
+ return [
54
+ `Slack requires this sign-in to ${workspace} to be confirmed on an already-authenticated device.`,
55
+ 'QR sign-in over HTTP cannot complete that approval step.',
56
+ '',
57
+ 'Use agent-slack auth extract on a computer already signed in to Slack instead.',
58
+ ].join('\n');
59
+ }
60
+ return [
61
+ `Could not establish a Slack session for ${workspace} from the QR code.`,
62
+ 'The link may have expired, or the workspace requires a sign-in step that QR-over-HTTP cannot complete.',
63
+ '',
64
+ 'Generate a fresh QR and try again, or run agent-slack auth extract on a computer already signed in',
65
+ 'to Slack.',
66
+ ].join('\n');
67
+ }
22
68
  async function captureDCookie(startUrl, options) {
23
69
  const doFetch = options.fetchImpl ?? fetch;
24
70
  const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
25
71
  const debug = options.debug;
26
72
  let url = startUrl;
27
73
  let dCookie = null;
74
+ let sessionDenialReason = null;
75
+ let ssoProvider = null;
28
76
  const cookieJar = [];
29
77
  for (let hop = 0; hop < maxRedirects; hop++) {
30
78
  // Only ever send captured cookies (including the d session cookie) to Slack
@@ -43,26 +91,77 @@ async function captureDCookie(startUrl, options) {
43
91
  ...(cookieJar.length ? { Cookie: cookieJar.join('; ') } : {}),
44
92
  },
45
93
  });
94
+ const cookieNames = [];
46
95
  for (const setCookie of getSetCookies(response)) {
47
96
  const value = parseDCookie(setCookie);
48
97
  if (value)
49
98
  dCookie = value;
99
+ const name = setCookie.split('=')[0]?.trim();
100
+ if (name)
101
+ cookieNames.push(name);
50
102
  const pair = setCookie.split(';')[0]?.trim();
51
103
  if (pair)
52
104
  cookieJar.push(pair);
53
105
  }
54
- debug?.(`hop ${hop}: ${response.status}`);
106
+ debug?.(`hop ${hop}: ${response.status} set-cookie=[${cookieNames.join(',') || 'none'}]`);
55
107
  const location = response.status >= 300 && response.status < 400 ? response.headers.get('location') : null;
56
- if (!location)
108
+ if (!location) {
109
+ if (!dCookie)
110
+ sessionDenialReason = await classifySessionDenial(response);
57
111
  break;
112
+ }
58
113
  const next = new URL(location, url).toString();
59
114
  if (!isSlackHost(next)) {
60
- debug?.(`hop ${hop}: redirect target is not a Slack host, stopping`);
115
+ // A no-cookie login QR that redirects off Slack is an SSO/IdP hand-off the
116
+ // HTTP flow cannot complete — classify it regardless of whether the IdP
117
+ // host is a recognized provider (custom/vanity SAML domains included).
118
+ if (!dCookie) {
119
+ sessionDenialReason = 'sso_required';
120
+ ssoProvider = ssoProviderFromUrl(next);
121
+ }
122
+ debug?.(`hop ${hop}: redirect target is not a Slack host (${new URL(next).hostname}), stopping`);
61
123
  break;
62
124
  }
63
125
  url = next;
64
126
  }
65
- return dCookie;
127
+ return { cookie: dCookie, denialReason: sessionDenialReason, ssoProvider };
128
+ }
129
+ function ssoProviderFromUrl(rawUrl) {
130
+ let hostname;
131
+ try {
132
+ hostname = new URL(rawUrl).hostname;
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ if (hostname === 'accounts.google.com')
138
+ return 'Google';
139
+ if (hostname.endsWith('.okta.com') || hostname.endsWith('.oktapreview.com'))
140
+ return 'Okta';
141
+ if (hostname === 'login.microsoftonline.com')
142
+ return 'Microsoft';
143
+ if (hostname.endsWith('.onelogin.com'))
144
+ return 'OneLogin';
145
+ if (hostname.endsWith('.pingidentity.com') || hostname.endsWith('.pingone.com'))
146
+ return 'Ping Identity';
147
+ if (hostname.endsWith('.auth0.com'))
148
+ return 'Auth0';
149
+ return null;
150
+ }
151
+ async function classifySessionDenial(response) {
152
+ try {
153
+ const body = await response.text();
154
+ if (/Link Expired/i.test(body) || /trouble signing you in/i.test(body)) {
155
+ return 'link_expired';
156
+ }
157
+ if (/sign in on your other device/i.test(body) || /open Slack/i.test(body) || /confirm/i.test(body)) {
158
+ return 'awaiting_device_confirmation';
159
+ }
160
+ return null;
161
+ }
162
+ catch {
163
+ return null;
164
+ }
66
165
  }
67
166
  export function isSlackHost(rawUrl) {
68
167
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"qr-http-login.js","sourceRoot":"","sources":["../../../../src/platforms/slack/qr-http-login.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"qr-http-login.js","sourceRoot":"","sources":["../../../../src/platforms/slack/qr-http-login.ts"],"names":[],"mappings":"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"}
@@ -257,15 +257,7 @@ await listener.start() // connects via Gateway WebSocket
257
257
  // listener.stop() // clean shutdown
258
258
  ```
259
259
 
260
- Custom intents (default includes non-privileged message/reaction/typing intents — add `MessageContent` to read message text):
261
-
262
- ```typescript
263
- import { DiscordIntent } from 'agent-messenger/discord'
264
-
265
- const listener = new DiscordListener(client, {
266
- intents: DiscordIntent.Guilds | DiscordIntent.GuildMessages | DiscordIntent.MessageContent, // MessageContent is privileged
267
- })
268
- ```
260
+ The user-account listener does not take intents — Gateway intents are a bot concept. It identifies as a user session and automatically receives full message content (including other users' messages), so no configuration is required.
269
261
 
270
262
  ### Available Events
271
263
 
package/e2e/README.md CHANGED
@@ -41,7 +41,7 @@ Before running E2E tests, you need:
41
41
 
42
42
  ## Running E2E Tests Locally
43
43
 
44
- ### Step 1: Extract Credentials
44
+ ### Step 1: Authenticate
45
45
 
46
46
  First, make sure the desktop apps (Slack/Discord) are running and logged into your **test** workspaces.
47
47
 
@@ -56,6 +56,8 @@ agent-discord auth extract
56
56
  agent-teams auth extract
57
57
  ```
58
58
 
59
+ > For local runs you can also use QR code sign-in for the **test** accounts (`agent-slack auth qr` / `agent-discord auth qr`) instead of extraction — it's the recommended auth method and doesn't require the desktop app. CI uses the environment variables below (extracted token/cookie values), since QR sign-in is interactive.
60
+
59
61
  ### Step 2: Switch to Test Workspace
60
62
 
61
63
  Ensure you're targeting the correct test workspace:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.26.0",
3
+ "version": "2.27.0",
4
4
  "description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, Instagram, KakaoTalk, Channel Talk)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-channeltalk
3
3
  description: Interact with Channel Talk using extracted desktop app or browser credentials - read chats, send messages, search messages, manage groups
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-channeltalk:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-channeltalkbot
3
3
  description: Interact with Channel Talk workspaces using API credentials - send messages, read chats, manage groups and bots
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-channeltalkbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-discord
3
3
  description: Interact with Discord servers - send messages, read channels, manage reactions
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-discord:*)
6
6
  metadata:
7
7
  openclaw:
@@ -39,8 +39,20 @@ On macOS, the system may prompt for your Keychain password the first time (requi
39
39
 
40
40
  **Obtaining tokens** — two options:
41
41
 
42
- - **`agent-discord auth extract`** (default): extracts from the Discord desktop app first, falling back to Chromium browsers if the app isn't installed. Best for automated/headless use.
43
- - **`agent-discord auth qr`**: signs in by scanning a QR code with the Discord mobile app (Settings Scan QR Code). Use this when there is no desktop app or browser session to extract from. Requires a phone, so it cannot run headlessly.
42
+ - **`agent-discord auth qr` recommended**: signs in by scanning a QR code with the Discord mobile app (Settings Scan QR Code). This is the safest and most reliable method — it authenticates through Discord's official Remote Auth flow instead of reading credentials off disk, and needs no desktop app or browser. Requires a phone to scan, so it cannot run headlessly.
43
+ - **`agent-discord auth extract`**: extracts from the Discord desktop app first, falling back to Chromium browsers if the app isn't installed. Best for automated/headless use where no phone is available to scan.
44
+
45
+ ### QR Code Sign-In (Recommended)
46
+
47
+ ```bash
48
+ # Generate a QR code and wait for you to scan it with the Discord mobile app
49
+ agent-discord auth qr
50
+
51
+ # Show protocol details while debugging
52
+ agent-discord auth qr --debug
53
+ ```
54
+
55
+ Open the Discord mobile app → **Settings → Scan QR Code**, scan the printed code, and confirm on your phone. The token (plus all discovered servers) is validated and stored like `auth extract`. The QR code expires after ~150 seconds — re-run the command to generate a fresh one. See [references/authentication.md](references/authentication.md) for the full flow.
44
56
 
45
57
  ### Multi-Server Support
46
58
 
@@ -418,7 +430,7 @@ All commands return consistent error format:
418
430
 
419
431
  Common errors:
420
432
 
421
- - `Not authenticated`: No valid token (auto-extraction failed — see Troubleshooting)
433
+ - `Not authenticated`: No valid token (auto-extraction failed — run `auth qr` to sign in, or see Troubleshooting)
422
434
  - `No current server set`: Run `server switch <id>` first
423
435
  - `Message not found`: Invalid message ID
424
436
  - `Unknown Channel`: Invalid channel ID
@@ -452,6 +464,25 @@ if (!token) {
452
464
  const client = await new DiscordClient().login({ token })
453
465
  ```
454
466
 
467
+ ### QR Code Login (Recommended)
468
+
469
+ `loginWithRemoteAuth` signs in by scanning a QR code with the Discord mobile app — no desktop app or token extraction required. It runs Discord's Remote Auth protocol and hands you a QR URL to display; once the user scans and confirms on their phone, you receive the user token.
470
+
471
+ ```typescript
472
+ import { DiscordClient, loginWithRemoteAuth } from 'agent-messenger/discord'
473
+
474
+ const session = await loginWithRemoteAuth({
475
+ onQrUrl: (url) => {
476
+ // Render this URL as a QR code (e.g. with the `qrcode` package)
477
+ console.log('Scan this with the Discord mobile app:', url)
478
+ },
479
+ })
480
+
481
+ const client = await new DiscordClient().login({ token: session.token })
482
+ ```
483
+
484
+ `session` is `{ token, user }`, where `user` may be `null` if the gateway skips the user payload — call `await client.testAuth()` after login if you need the identity. If Discord requires a captcha on the final token exchange, the call rejects with a `DiscordError` of code `remote_auth_captcha`; fall back to `auth extract` in that case. Requires a phone with the Discord app, so it cannot run headlessly.
485
+
455
486
  ### Example
456
487
 
457
488
  ```typescript
@@ -478,12 +509,10 @@ await client.sendMessage(thread.id, 'First message in thread')
478
509
  `DiscordListener` connects to Discord's Gateway WebSocket for instant event streaming:
479
510
 
480
511
  ```typescript
481
- import { DiscordClient, DiscordListener, DiscordIntent } from 'agent-messenger/discord'
512
+ import { DiscordClient, DiscordListener } from 'agent-messenger/discord'
482
513
 
483
514
  const client = await new DiscordClient().login()
484
- const listener = new DiscordListener(client, {
485
- intents: DiscordIntent.Guilds | DiscordIntent.GuildMessages | DiscordIntent.MessageContent,
486
- })
515
+ const listener = new DiscordListener(client)
487
516
 
488
517
  listener.on('message_create', (event) => {
489
518
  console.log(`${event.author.username}: ${event.content}`)
@@ -2,7 +2,40 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- agent-discord uses Discord's user token extracted from the Discord desktop application, with automatic fallback to Chromium browser profiles (Chrome, Chrome Canary, Edge, Arc, Brave, Vivaldi, Chromium) when the desktop app isn't installed.
5
+ agent-discord supports two ways to authenticate:
6
+
7
+ - **QR code sign-in (`auth qr`) — recommended.** Scan a QR code with the Discord mobile app to sign in through Discord's own Remote Auth flow. This is the safest and most reliable method: it never reads stored credentials off disk, works without a desktop app or browser, and uses Discord's official login confirmation on your phone.
8
+ - **Token extraction (`auth extract`).** Reads Discord's user token from the desktop application, with automatic fallback to Chromium browser profiles (Chrome, Chrome Canary, Edge, Arc, Brave, Vivaldi, Chromium). Best for automated/headless environments where no phone is available to scan.
9
+
10
+ > **Recommendation:** Prefer `agent-discord auth qr` whenever you can scan a QR code. Use `auth extract` for headless/CI setups where interactive scanning isn't possible.
11
+
12
+ ## QR Code Sign-In (Recommended)
13
+
14
+ Sign in by scanning a QR code with the Discord mobile app — no desktop app or browser token extraction required. This runs Discord's official Remote Auth protocol, so you authenticate through Discord's own login confirmation rather than by reading stored credentials.
15
+
16
+ ```bash
17
+ # Generate a QR code and wait for you to scan it
18
+ agent-discord auth qr
19
+
20
+ # Show protocol details while debugging
21
+ agent-discord auth qr --debug
22
+
23
+ # Human-readable output
24
+ agent-discord auth qr --pretty
25
+ ```
26
+
27
+ How it works:
28
+
29
+ 1. Opens a WebSocket to Discord's remote-auth gateway and performs an RSA key exchange
30
+ 2. Renders a QR code in your terminal (and opens it in the browser as a fallback)
31
+ 3. You scan it with the Discord mobile app: **Settings → Scan QR Code**, then confirm on your phone
32
+ 4. Discord returns an encrypted ticket, which is exchanged for your user token, validated, and stored like `auth extract` — along with all discovered servers
33
+
34
+ Notes:
35
+
36
+ - **Requires the Discord mobile app** (logged in) to scan. Because it's interactive, it cannot run headlessly — use `auth extract` for CI.
37
+ - The QR code expires after about 150 seconds. Re-run `agent-discord auth qr` to generate a fresh one.
38
+ - If Discord presents a captcha challenge during the token exchange, the CLI surfaces a typed error directing you to fall back to `auth extract`.
6
39
 
7
40
  ## Token Extraction
8
41
 
@@ -186,7 +219,10 @@ Discord user tokens can be invalidated when:
186
219
  If commands start failing with auth errors:
187
220
 
188
221
  ```bash
189
- # Re-extract credentials
222
+ # Recommended: re-authenticate with a QR code
223
+ agent-discord auth qr
224
+
225
+ # Or re-extract credentials from the desktop app / browser
190
226
  agent-discord auth extract
191
227
 
192
228
  # Verify it worked
@@ -216,8 +252,9 @@ This shows:
216
252
 
217
253
  **Solution**:
218
254
 
219
- 1. Log in to discord.com in a Chromium browser (Chrome, Edge, Arc, Brave)the CLI will extract from browser automatically
220
- 2. Or install the Discord desktop app, log in, and run `agent-discord auth extract` again
255
+ 1. Recommended: run `agent-discord auth qr` and scan the QR code with the Discord mobile app no desktop app or browser required
256
+ 2. Or log in to discord.com in a Chromium browser (Chrome, Edge, Arc, Brave) the CLI will extract from browser automatically
257
+ 3. Or install the Discord desktop app, log in, and run `agent-discord auth extract` again
221
258
 
222
259
  ### "No Discord token found"
223
260
 
@@ -275,10 +312,11 @@ With extracted credentials, agent-discord has the same permissions as you in Dis
275
312
 
276
313
  ### Best Practices
277
314
 
278
- 1. **Protect credentials.json**: Never commit to version control
279
- 2. **Use server switching**: Keep different contexts separate
280
- 3. **Re-extract periodically**: Keep tokens fresh
281
- 4. **Revoke if compromised**: Change your Discord password to invalidate tokens
315
+ 1. **Prefer QR sign-in**: Use `auth qr` when possible — it authenticates through Discord's official flow instead of reading credentials off disk
316
+ 2. **Protect credentials.json**: Never commit to version control
317
+ 3. **Use server switching**: Keep different contexts separate
318
+ 4. **Re-authenticate periodically**: Keep tokens fresh
319
+ 5. **Revoke if compromised**: Change your Discord password to invalidate tokens
282
320
 
283
321
  ## Manual Token Management (Advanced)
284
322
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-discordbot
3
3
  description: Interact with Discord servers using bot tokens - send messages, read channels, manage reactions
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-discordbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-instagram
3
3
  description: Interact with Instagram DMs - send messages, read conversations, manage accounts
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-instagram:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-kakaotalk
3
3
  description: Interact with KakaoTalk - send messages, read chats, manage conversations
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-kakaotalk:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-line
3
3
  description: Interact with LINE - send messages, read chats, manage conversations
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-line:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-slack
3
3
  description: Interact with Slack workspaces - send messages, read channels, manage reactions
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-slack:*)
6
6
  metadata:
7
7
  openclaw:
@@ -37,11 +37,14 @@ Credentials are extracted automatically from the Slack desktop app (or Chromium
37
37
 
38
38
  On macOS, the system may prompt for your Keychain password the first time (required to decrypt Slack's stored token). This is a one-time prompt.
39
39
 
40
- **IMPORTANT**: Always use `agent-slack auth extract` to obtain tokens. The CLI extracts from the desktop app first, falling back to Chromium browsers if the app isn't installed.
40
+ **Two ways to obtain tokens:**
41
41
 
42
- ### QR Code Sign-In (No Desktop App Required)
42
+ - **`agent-slack auth qr` — recommended.** Sign in with a QR code from Slack's "Sign in on mobile" screen. It's the safest and most reliable method: it authenticates through Slack's own login flow over plain HTTP instead of reading credentials off disk, and needs no desktop app or browser.
43
+ - **`agent-slack auth extract`.** Extracts tokens from the Slack desktop app first, falling back to Chromium browsers if the app isn't installed. Best for automated/headless environments.
43
44
 
44
- If the Slack desktop app isn't installed (or extraction fails), you can sign in with a QR code from any device where you're already logged into Slack — no browser automation, fully over HTTP:
45
+ ### QR Code Sign-In (Recommended)
46
+
47
+ Sign in with a QR code from any device where you're already logged into Slack — no desktop app, no browser automation, fully over HTTP:
45
48
 
46
49
  ```bash
47
50
  # In Slack (desktop or web): your name (top-left) → "Sign in on mobile".
@@ -2,7 +2,12 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- agent-slack uses Slack's web client credentials (xoxc token + xoxd cookie) extracted from the Slack desktop application, with automatic fallback to Chromium browser profiles (Chrome, Chrome Canary, Edge, Arc, Brave, Vivaldi, Chromium) when the desktop app isn't installed.
5
+ agent-slack supports two ways to authenticate:
6
+
7
+ - **QR code sign-in (`auth qr`) — recommended.** Sign in with a QR code from Slack's "Sign in on mobile" screen. This is the safest and most reliable method: it authenticates through Slack's own login flow over plain HTTP instead of reading stored credentials off disk, and works without a desktop app or browser.
8
+ - **Token extraction (`auth extract`).** Reads Slack's web client credentials (xoxc token + xoxd cookie) from the desktop application, with automatic fallback to Chromium browser profiles (Chrome, Chrome Canary, Edge, Arc, Brave, Vivaldi, Chromium). Best for automated/headless environments.
9
+
10
+ > **Recommendation:** Prefer `agent-slack auth qr`. Use `auth extract` for headless/CI setups or when you'd rather reuse an existing desktop/browser session.
6
11
 
7
12
  ## Token Extraction
8
13
 
@@ -34,9 +39,9 @@ This command:
34
39
 
35
40
  Use `--browser-profile <path>` for agent-browser profiles, custom Chrome user data dirs, or portable browser profiles. The option can be repeated or given comma-separated paths, and explicit paths are included even when desktop credentials are also present.
36
41
 
37
- ### QR Code Sign-In
42
+ ### QR Code Sign-In (Recommended)
38
43
 
39
- When the desktop app isn't installed and browser extraction isn't an option, you can sign in with a QR code from a device where you're already logged into Slack. This runs entirely over HTTP — no browser automation:
44
+ The recommended way to authenticate: sign in with a QR code from a device where you're already logged into Slack. This runs entirely over HTTP — no browser automation, and no desktop app required:
40
45
 
41
46
  ```bash
42
47
  # In Slack (desktop or web): your name (top-left) → "Sign in on mobile".
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-slackbot
3
3
  description: Interact with Slack workspaces using bot tokens - send messages, read channels, manage reactions
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-slackbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-teams
3
3
  description: Interact with Microsoft Teams - send messages, read channels, manage reactions
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-teams:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-telegram
3
3
  description: Interact with Telegram through TDLib - authenticate, inspect chats, and send messages
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-telegram:*)
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-telegrambot
3
3
  description: Interact with Telegram using bot tokens - send messages, read chats, manage reactions
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-telegrambot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-webex
3
3
  description: Interact with Cisco Webex - send messages, read spaces, manage memberships
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-webex:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-webexbot
3
3
  description: Interact with Cisco Webex using bot tokens - send messages, reply in threads, upload and download files, look up people, read spaces, manage memberships, stream real-time events
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-webexbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-wechatbot
3
3
  description: Interact with WeChat Official Account using API credentials - send messages, manage templates, list followers
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-wechatbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsapp
3
3
  description: Interact with WhatsApp - send messages, read chats, manage conversations
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-whatsapp:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsappbot
3
3
  description: Interact with WhatsApp using Cloud API credentials - send messages, manage templates
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  allowed-tools: Bash(agent-whatsappbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -171,10 +171,9 @@ describe('DiscordListener', () => {
171
171
  expect(identifyMsg.d.token).toBe('fake-token')
172
172
  })
173
173
 
174
- it('sends Identify with custom intents', async () => {
174
+ it('sends a user-account Identify with the load-bearing default shape', async () => {
175
175
  const client = createMockClient()
176
- const customIntents = 1 << 9
177
- listener = new DiscordListener(client, { intents: customIntents })
176
+ listener = new DiscordListener(client)
178
177
 
179
178
  await listener.start()
180
179
  mockWsInstance.simulateOpen()
@@ -184,7 +183,16 @@ describe('DiscordListener', () => {
184
183
  const identifyMsg = sentMessages.find((m) => m.op === 2)
185
184
 
186
185
  expect(identifyMsg).toBeDefined()
187
- expect(identifyMsg.d.intents).toBe(customIntents)
186
+ expect(identifyMsg.d.capabilities).toBe(16381)
187
+ expect(identifyMsg.d.client_state).toEqual({ guild_versions: {} })
188
+ expect(identifyMsg.d.compress).toBe(false)
189
+ expect(identifyMsg.d.presence).toEqual({ status: 'online', since: 0, activities: [], afk: false })
190
+ expect(identifyMsg.d.properties.browser).toBe('Chrome')
191
+ expect(identifyMsg.d.properties.client_build_number).toBe(648814)
192
+
193
+ // All intents incl. MESSAGE_CONTENT (1<<15); user sessions blank other users' content without it
194
+ expect(identifyMsg.d.intents).toBe(33_554_431)
195
+ expect(identifyMsg.d.intents & (1 << 15)).toBe(1 << 15)
188
196
  })
189
197
  })
190
198
 
@@ -4,7 +4,7 @@ import WebSocket from 'ws'
4
4
 
5
5
  import type { DiscordClient } from './client'
6
6
  import type { DiscordListenerEventMap, DiscordGatewayGenericEvent } from './types'
7
- import { DiscordGatewayOpcode, DiscordIntent } from './types'
7
+ import { DiscordGatewayOpcode } from './types'
8
8
 
9
9
  const GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json'
10
10
  const GATEWAY_QUERY = '?v=10&encoding=json'
@@ -13,20 +13,25 @@ const RECONNECT_MAX_DELAY = 30_000
13
13
  const NON_RECOVERABLE_CLOSE_CODES = [4004, 4010, 4011, 4012, 4013, 4014]
14
14
  const SESSION_RESET_CLOSE_CODES = [4007, 4009]
15
15
 
16
- const DEFAULT_INTENTS =
17
- DiscordIntent.Guilds |
18
- DiscordIntent.GuildMessages |
19
- DiscordIntent.GuildMessageReactions |
20
- DiscordIntent.GuildMessageTyping |
21
- DiscordIntent.DirectMessages |
22
- DiscordIntent.DirectMessageReactions |
23
- DiscordIntent.DirectMessageTyping
16
+ // User (non-bot) gateway capabilities bitmask, mirroring discord.py-self's default set.
17
+ // Capabilities shape the READY payload; bit 10 (client_state_v2) requires client_state.guild_versions.
18
+ const USER_GATEWAY_CAPABILITIES = 16381
19
+
20
+ // Without MESSAGE_CONTENT (1<<15), Discord blanks `content`/`embeds`/`attachments` on messages
21
+ // from OTHER users (self/DM/mention content still arrives). User sessions get all intents only
22
+ // when `intents` is omitted OR explicitly set, so we send an all-intents value to guarantee content.
23
+ const USER_GATEWAY_INTENTS = 33_554_431
24
+
25
+ // Discord validates client_build_number against recent web-client builds; a stale value can
26
+ // yield a connected-but-degraded session that fires READY yet delivers no message events.
27
+ // Overridable via env so it can be refreshed without a release when it eventually goes stale.
28
+ const DEFAULT_CLIENT_BUILD_NUMBER = 648814
29
+ const USER_GATEWAY_BUILD_NUMBER = Number(process.env.AGENT_DISCORD_BUILD_NUMBER) || DEFAULT_CLIENT_BUILD_NUMBER
24
30
 
25
31
  type EventKey = keyof DiscordListenerEventMap
26
32
 
27
33
  export class DiscordListener {
28
34
  private client: DiscordClient
29
- private intents: number
30
35
  private running = false
31
36
  private ws: WebSocket | null = null
32
37
  private emitter = new EventEmitter()
@@ -43,9 +48,8 @@ export class DiscordListener {
43
48
  private cachedUser: { id: string; username: string } | null = null
44
49
  private generation = 0
45
50
 
46
- constructor(client: DiscordClient, options?: { intents?: number }) {
51
+ constructor(client: DiscordClient) {
47
52
  this.client = client
48
- this.intents = options?.intents ?? DEFAULT_INTENTS
49
53
  }
50
54
 
51
55
  async start(): Promise<void> {
@@ -233,12 +237,28 @@ export class DiscordListener {
233
237
  op: DiscordGatewayOpcode.Identify,
234
238
  d: {
235
239
  token: this.token,
236
- intents: this.intents,
240
+ capabilities: USER_GATEWAY_CAPABILITIES,
241
+ intents: USER_GATEWAY_INTENTS,
237
242
  properties: {
238
- os: 'linux',
239
- browser: 'agent-messenger',
240
- device: 'agent-messenger',
243
+ os: 'Linux',
244
+ browser: 'Chrome',
245
+ device: '',
246
+ system_locale: 'en-US',
247
+ browser_user_agent:
248
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
249
+ browser_version: '131.0.0.0',
250
+ os_version: '',
251
+ referrer: '',
252
+ referring_domain: '',
253
+ referrer_current: '',
254
+ referring_domain_current: '',
255
+ release_channel: 'stable',
256
+ client_build_number: USER_GATEWAY_BUILD_NUMBER,
257
+ client_event_source: null,
241
258
  },
259
+ presence: { status: 'online', since: 0, activities: [], afk: false },
260
+ compress: false,
261
+ client_state: { guild_versions: {} },
242
262
  },
243
263
  }),
244
264
  )
@@ -132,6 +132,53 @@ describe('loginWithQr', () => {
132
132
  await expect(promise).rejects.toThrow(/expired/)
133
133
  })
134
134
 
135
+ it('reports SSO enforcement when the z-app hop redirects to an identity provider', async () => {
136
+ // Given a workspace whose z-app hop redirects off Slack to an SSO IdP (no d cookie issued)
137
+ const dataUrl = await qrDataUrl(ZAPP_URL)
138
+ const fetchImpl = (async (input: string | URL) => {
139
+ const url = typeof input === 'string' ? input : input.toString()
140
+ if (url.startsWith('https://app.slack.com/t/')) {
141
+ return redirect(`https://${WORKSPACE}.slack.com/z-app-secret`)
142
+ }
143
+ return redirect('https://accounts.google.com/o/oauth2/auth?redirect_uri=https://slack.com/sso/google')
144
+ }) as typeof fetch
145
+
146
+ // When logging in, the error names SSO + the detected provider and points to a working auth method
147
+ await expect(loginWithQr(dataUrl, { fetchImpl })).rejects.toThrow(/enforces SSO \(via Google\)/)
148
+ await expect(loginWithQr(dataUrl, { fetchImpl })).rejects.toThrow(/auth extract/)
149
+ })
150
+
151
+ it('reports SSO enforcement for an unrecognized/vanity IdP host', async () => {
152
+ // Given a z-app hop that redirects off Slack to a custom SAML IdP not in the known-provider list
153
+ const dataUrl = await qrDataUrl(ZAPP_URL)
154
+ const fetchImpl = (async (input: string | URL) => {
155
+ const url = typeof input === 'string' ? input : input.toString()
156
+ if (url.startsWith('https://app.slack.com/t/')) {
157
+ return redirect(`https://${WORKSPACE}.slack.com/z-app-secret`)
158
+ }
159
+ return redirect('https://login.acme.example/saml')
160
+ }) as typeof fetch
161
+
162
+ // When logging in, it is still classified as SSO (without naming a provider) and routes to auth extract
163
+ await expect(loginWithQr(dataUrl, { fetchImpl })).rejects.toThrow(/enforces SSO/)
164
+ await expect(loginWithQr(dataUrl, { fetchImpl })).rejects.toThrow(/auth extract/)
165
+ })
166
+
167
+ it('reports an expired link when the final page is Slack\u2019s "Link Expired" page', async () => {
168
+ // Given a z-app chain that completes on a Slack host but returns the expired page with no d cookie
169
+ const dataUrl = await qrDataUrl(ZAPP_URL)
170
+ const fetchImpl = (async (input: string | URL) => {
171
+ const url = typeof input === 'string' ? input : input.toString()
172
+ if (url.startsWith('https://app.slack.com/t/')) {
173
+ return redirect(`https://${WORKSPACE}.slack.com/z-app-secret`)
174
+ }
175
+ return new Response('<title>Link Expired | Slack</title>', { status: 200 })
176
+ }) as typeof fetch
177
+
178
+ // When logging in, the error names the expiry explicitly
179
+ await expect(loginWithQr(dataUrl, { fetchImpl })).rejects.toThrow(/has expired or was already used/)
180
+ })
181
+
135
182
  it('fails with qr_token_failed when the token cannot be retrieved', async () => {
136
183
  const dataUrl = await qrDataUrl(ZAPP_URL)
137
184
 
@@ -23,10 +23,10 @@ export async function loginWithQr(dataUrl: string, options: QrLoginOptions = {})
23
23
  const debug = options.debug
24
24
  debug?.(`Decoded QR for workspace ${login.workspace}`)
25
25
 
26
- const cookie = await captureDCookie(login.url, options)
26
+ const { cookie, denialReason, ssoProvider } = await captureDCookie(login.url, options)
27
27
  if (!cookie) {
28
28
  throw new SlackError(
29
- 'Could not establish a Slack session from the QR code. The link may have expired — generate a new QR code and try again.',
29
+ qrSessionFailureMessage(denialReason, { workspace: login.workspace, ssoProvider }),
30
30
  'qr_session_failed',
31
31
  )
32
32
  }
@@ -44,13 +44,74 @@ export async function loginWithQr(dataUrl: string, options: QrLoginOptions = {})
44
44
  return { token, cookie, workspace: login.workspace }
45
45
  }
46
46
 
47
- async function captureDCookie(startUrl: string, options: QrLoginOptions): Promise<string | null> {
47
+ interface QrFailureContext {
48
+ workspace?: string
49
+ ssoProvider?: string | null
50
+ }
51
+
52
+ function qrSessionFailureMessage(denialReason: string | null, context: QrFailureContext = {}): string {
53
+ const workspace = context.workspace ? `"${context.workspace}"` : 'this workspace'
54
+
55
+ if (denialReason === 'sso_required') {
56
+ const via = context.ssoProvider ? ` (via ${context.ssoProvider})` : ''
57
+ return [
58
+ `Slack workspace ${workspace} enforces SSO${via}, so QR sign-in cannot complete here.`,
59
+ '',
60
+ `Why: scanning the QR redirects the login to your identity provider${via}. agent-slack follows the`,
61
+ 'redirect chain over plain HTTP and never sends your session cookie off Slack-owned hosts, so it',
62
+ 'stops at the IdP and no Slack session (the "d" cookie) is ever issued. Completing SSO requires a',
63
+ 'real browser, which a headless environment does not have.',
64
+ '',
65
+ 'What to do instead:',
66
+ ' On a computer where you are already signed in to Slack (desktop app or a Chromium browser),',
67
+ ' run: agent-slack auth extract',
68
+ ' This reads your existing session locally — no SSO re-login, no DevTools.',
69
+ '',
70
+ 'Background: https://github.com/agent-messenger/agent-messenger/issues/260',
71
+ ].join('\n')
72
+ }
73
+ if (denialReason === 'link_expired') {
74
+ return [
75
+ `The Slack QR code for ${workspace} has expired or was already used.`,
76
+ 'The z-app sign-in link is single-use and short-lived: it is consumed the first time it is opened',
77
+ '(including previewing or scanning it), and re-using it returns "Link Expired".',
78
+ '',
79
+ 'Generate a fresh QR (your name → "Sign in on mobile") and pipe it in immediately, without',
80
+ 'previewing or scanning it first.',
81
+ ].join('\n')
82
+ }
83
+ if (denialReason === 'awaiting_device_confirmation') {
84
+ return [
85
+ `Slack requires this sign-in to ${workspace} to be confirmed on an already-authenticated device.`,
86
+ 'QR sign-in over HTTP cannot complete that approval step.',
87
+ '',
88
+ 'Use agent-slack auth extract on a computer already signed in to Slack instead.',
89
+ ].join('\n')
90
+ }
91
+ return [
92
+ `Could not establish a Slack session for ${workspace} from the QR code.`,
93
+ 'The link may have expired, or the workspace requires a sign-in step that QR-over-HTTP cannot complete.',
94
+ '',
95
+ 'Generate a fresh QR and try again, or run agent-slack auth extract on a computer already signed in',
96
+ 'to Slack.',
97
+ ].join('\n')
98
+ }
99
+
100
+ interface CookieCapture {
101
+ cookie: string | null
102
+ denialReason: string | null
103
+ ssoProvider: string | null
104
+ }
105
+
106
+ async function captureDCookie(startUrl: string, options: QrLoginOptions): Promise<CookieCapture> {
48
107
  const doFetch = options.fetchImpl ?? fetch
49
108
  const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS
50
109
  const debug = options.debug
51
110
 
52
111
  let url = startUrl
53
112
  let dCookie: string | null = null
113
+ let sessionDenialReason: string | null = null
114
+ let ssoProvider: string | null = null
54
115
  const cookieJar: string[] = []
55
116
 
56
117
  for (let hop = 0; hop < maxRedirects; hop++) {
@@ -72,27 +133,71 @@ async function captureDCookie(startUrl: string, options: QrLoginOptions): Promis
72
133
  },
73
134
  })
74
135
 
136
+ const cookieNames: string[] = []
75
137
  for (const setCookie of getSetCookies(response)) {
76
138
  const value = parseDCookie(setCookie)
77
139
  if (value) dCookie = value
140
+ const name = setCookie.split('=')[0]?.trim()
141
+ if (name) cookieNames.push(name)
78
142
  const pair = setCookie.split(';')[0]?.trim()
79
143
  if (pair) cookieJar.push(pair)
80
144
  }
81
145
 
82
- debug?.(`hop ${hop}: ${response.status}`)
146
+ debug?.(`hop ${hop}: ${response.status} set-cookie=[${cookieNames.join(',') || 'none'}]`)
83
147
 
84
148
  const location = response.status >= 300 && response.status < 400 ? response.headers.get('location') : null
85
- if (!location) break
149
+ if (!location) {
150
+ if (!dCookie) sessionDenialReason = await classifySessionDenial(response)
151
+ break
152
+ }
86
153
 
87
154
  const next = new URL(location, url).toString()
88
155
  if (!isSlackHost(next)) {
89
- debug?.(`hop ${hop}: redirect target is not a Slack host, stopping`)
156
+ // A no-cookie login QR that redirects off Slack is an SSO/IdP hand-off the
157
+ // HTTP flow cannot complete — classify it regardless of whether the IdP
158
+ // host is a recognized provider (custom/vanity SAML domains included).
159
+ if (!dCookie) {
160
+ sessionDenialReason = 'sso_required'
161
+ ssoProvider = ssoProviderFromUrl(next)
162
+ }
163
+ debug?.(`hop ${hop}: redirect target is not a Slack host (${new URL(next).hostname}), stopping`)
90
164
  break
91
165
  }
92
166
  url = next
93
167
  }
94
168
 
95
- return dCookie
169
+ return { cookie: dCookie, denialReason: sessionDenialReason, ssoProvider }
170
+ }
171
+
172
+ function ssoProviderFromUrl(rawUrl: string): string | null {
173
+ let hostname: string
174
+ try {
175
+ hostname = new URL(rawUrl).hostname
176
+ } catch {
177
+ return null
178
+ }
179
+ if (hostname === 'accounts.google.com') return 'Google'
180
+ if (hostname.endsWith('.okta.com') || hostname.endsWith('.oktapreview.com')) return 'Okta'
181
+ if (hostname === 'login.microsoftonline.com') return 'Microsoft'
182
+ if (hostname.endsWith('.onelogin.com')) return 'OneLogin'
183
+ if (hostname.endsWith('.pingidentity.com') || hostname.endsWith('.pingone.com')) return 'Ping Identity'
184
+ if (hostname.endsWith('.auth0.com')) return 'Auth0'
185
+ return null
186
+ }
187
+
188
+ async function classifySessionDenial(response: Response): Promise<string | null> {
189
+ try {
190
+ const body = await response.text()
191
+ if (/Link Expired/i.test(body) || /trouble signing you in/i.test(body)) {
192
+ return 'link_expired'
193
+ }
194
+ if (/sign in on your other device/i.test(body) || /open Slack/i.test(body) || /confirm/i.test(body)) {
195
+ return 'awaiting_device_confirmation'
196
+ }
197
+ return null
198
+ } catch {
199
+ return null
200
+ }
96
201
  }
97
202
 
98
203
  export function isSlackHost(rawUrl: string): boolean {