agent-messenger 2.23.4 → 2.23.6

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 (50) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +2 -2
  3. package/dist/package.json +1 -1
  4. package/dist/src/platforms/webex/cli.d.ts.map +1 -1
  5. package/dist/src/platforms/webex/cli.js +2 -1
  6. package/dist/src/platforms/webex/cli.js.map +1 -1
  7. package/dist/src/platforms/webex/client.d.ts +5 -0
  8. package/dist/src/platforms/webex/client.d.ts.map +1 -1
  9. package/dist/src/platforms/webex/client.js +190 -16
  10. package/dist/src/platforms/webex/client.js.map +1 -1
  11. package/dist/src/platforms/webex/commands/file.d.ts +12 -0
  12. package/dist/src/platforms/webex/commands/file.d.ts.map +1 -0
  13. package/dist/src/platforms/webex/commands/file.js +64 -0
  14. package/dist/src/platforms/webex/commands/file.js.map +1 -0
  15. package/dist/src/platforms/webex/commands/index.d.ts +1 -0
  16. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
  17. package/dist/src/platforms/webex/commands/index.js +1 -0
  18. package/dist/src/platforms/webex/commands/index.js.map +1 -1
  19. package/dist/src/platforms/webex/encryption.d.ts +14 -0
  20. package/dist/src/platforms/webex/encryption.d.ts.map +1 -1
  21. package/dist/src/platforms/webex/encryption.js +36 -0
  22. package/dist/src/platforms/webex/encryption.js.map +1 -1
  23. package/docs/content/docs/cli/webex.mdx +13 -0
  24. package/docs/content/docs/sdk/webex.mdx +12 -0
  25. package/package.json +1 -1
  26. package/skills/agent-channeltalk/SKILL.md +1 -1
  27. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  28. package/skills/agent-discord/SKILL.md +1 -1
  29. package/skills/agent-discordbot/SKILL.md +1 -1
  30. package/skills/agent-instagram/SKILL.md +1 -1
  31. package/skills/agent-kakaotalk/SKILL.md +1 -1
  32. package/skills/agent-line/SKILL.md +1 -1
  33. package/skills/agent-slack/SKILL.md +1 -1
  34. package/skills/agent-slackbot/SKILL.md +1 -1
  35. package/skills/agent-teams/SKILL.md +1 -1
  36. package/skills/agent-telegram/SKILL.md +1 -1
  37. package/skills/agent-telegrambot/SKILL.md +1 -1
  38. package/skills/agent-webex/SKILL.md +14 -2
  39. package/skills/agent-webexbot/SKILL.md +1 -1
  40. package/skills/agent-wechatbot/SKILL.md +1 -1
  41. package/skills/agent-whatsapp/SKILL.md +1 -1
  42. package/skills/agent-whatsappbot/SKILL.md +1 -1
  43. package/src/platforms/webex/cli.ts +10 -1
  44. package/src/platforms/webex/client.test.ts +194 -1
  45. package/src/platforms/webex/client.ts +230 -17
  46. package/src/platforms/webex/commands/file.test.ts +96 -0
  47. package/src/platforms/webex/commands/file.ts +87 -0
  48. package/src/platforms/webex/commands/index.ts +1 -0
  49. package/src/platforms/webex/encryption.test.ts +38 -0
  50. package/src/platforms/webex/encryption.ts +59 -0
@@ -3,6 +3,18 @@ export interface WebexKeyProvider {
3
3
  fetchKey(keyUri: string): Promise<string | null>;
4
4
  close?(): Promise<void>;
5
5
  }
6
+ export interface WebexScr {
7
+ enc: 'A256GCM';
8
+ key: string;
9
+ iv: string;
10
+ aad: string;
11
+ loc?: string;
12
+ tag: string;
13
+ }
14
+ export interface WebexEncryptedBinary {
15
+ scr: WebexScr;
16
+ ciphertext: Uint8Array;
17
+ }
6
18
  export declare class WebexEncryptionService {
7
19
  private rawKeys;
8
20
  private keyCache;
@@ -13,5 +25,7 @@ export declare class WebexEncryptionService {
13
25
  getKey(keyUri: string): Promise<jose.JWK.Key | null>;
14
26
  encryptText(keyUri: string, plaintext: string): Promise<string | null>;
15
27
  decryptText(keyUri: string, ciphertext: string): Promise<string | null>;
28
+ encryptBinary(plaintext: Uint8Array): WebexEncryptedBinary;
29
+ encryptScr(keyUri: string, scr: WebexScr): Promise<string | null>;
16
30
  }
17
31
  //# sourceMappingURL=encryption.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../../../src/platforms/webex/encryption.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAChD,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB;AAED,qBAAa,sBAAsB;IACjC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,WAAW,CAAgC;gBAEvC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAI/C,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAI1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC;IAqBpD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgBtE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAW9E"}
1
+ {"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../../../src/platforms/webex/encryption.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAChD,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB;AAID,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,SAAS,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,QAAQ,CAAA;IACb,UAAU,EAAE,UAAU,CAAA;CACvB;AAMD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,WAAW,CAAgC;gBAEvC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAI/C,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAI1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC;IAqBpD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgBtE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAY7E,aAAa,CAAC,SAAS,EAAE,UAAU,GAAG,oBAAoB;IAsBpD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAcxE"}
@@ -1,4 +1,8 @@
1
+ import { createCipheriv, randomBytes } from 'node:crypto';
1
2
  import * as jose from 'node-jose';
3
+ function toBase64Url(buffer) {
4
+ return buffer.toString('base64url');
5
+ }
2
6
  export class WebexEncryptionService {
3
7
  rawKeys;
4
8
  keyCache = new Map();
@@ -58,5 +62,37 @@ export class WebexEncryptionService {
58
62
  return null;
59
63
  }
60
64
  }
65
+ encryptBinary(plaintext) {
66
+ const key = randomBytes(32);
67
+ const iv = randomBytes(12);
68
+ const aad = new Date().toISOString();
69
+ const cipher = createCipheriv('aes-256-gcm', key, iv);
70
+ cipher.setAAD(Buffer.from(aad, 'utf8'));
71
+ const ciphertext = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
72
+ const tag = cipher.getAuthTag();
73
+ return {
74
+ scr: {
75
+ enc: 'A256GCM',
76
+ key: toBase64Url(key),
77
+ iv: toBase64Url(iv),
78
+ aad,
79
+ tag: toBase64Url(tag),
80
+ },
81
+ ciphertext,
82
+ };
83
+ }
84
+ async encryptScr(keyUri, scr) {
85
+ if (!scr.loc)
86
+ return null;
87
+ const key = await this.getKey(keyUri);
88
+ if (!key)
89
+ return null;
90
+ try {
91
+ return await jose.JWE.createEncrypt({ format: 'compact', contentAlg: 'A256GCM' }, { key, header: { alg: 'dir', kid: keyUri }, reference: null }).final(JSON.stringify(scr), 'utf8');
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
61
97
  }
62
98
  //# sourceMappingURL=encryption.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../../../src/platforms/webex/encryption.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAOjC,MAAM,OAAO,sBAAsB;IACzB,OAAO,CAAqB;IAC5B,QAAQ,GAA8B,IAAI,GAAG,EAAE,CAAA;IAC/C,WAAW,GAA4B,IAAI,CAAA;IAEnD,YAAY,cAAmC;QAC7C,IAAI,CAAC,OAAO,GAAG,cAAc,CAAA;IAC/B,CAAC;IAED,cAAc,CAAC,QAA0B;QACvC,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAA;IAC7B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,CAAA;IACnC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAc;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACxC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAA;QAEzB,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC7B,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,SAAS,CAAA;QAC9D,CAAC;QACD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QAErB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAA;YACjD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;YAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YAClC,OAAO,OAAO,CAAA;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,SAAiB;QACjD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACrC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QAErB,IAAI,CAAC;YACH,gFAAgF;YAChF,8EAA8E;YAC9E,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,CACjC,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,EAC5C,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAC9D,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,UAAkB;QAClD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACrC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QAErB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;YACpE,OAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../../../src/platforms/webex/encryption.ts"],"names":[],"mappings":"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"}
@@ -203,6 +203,19 @@ agent-webex member list <space-id>
203
203
  agent-webex member list abc123 --limit 100
204
204
  ```
205
205
 
206
+ ### File Commands
207
+
208
+ ```bash
209
+ # Upload a local file to a space
210
+ agent-webex file upload <space-id> <path>
211
+ agent-webex file upload abc123 ./report.pdf --text "Latest report"
212
+ agent-webex file upload abc123 ./image.png --text "**Done**" --markdown
213
+
214
+ # Download a file attachment by content URL or ID
215
+ agent-webex file download <content-url-or-id>
216
+ agent-webex file download <content-url-or-id> ./out.pdf
217
+ ```
218
+
206
219
  ### Snapshot Command
207
220
 
208
221
  Get workspace overview for AI agents (brief by default):
@@ -104,6 +104,18 @@ const limited = await client.listMemberships(roomId, { max: 100 })
104
104
  // → WebexMembership[]
105
105
  ```
106
106
 
107
+ ### Files
108
+
109
+ ```typescript
110
+ // Upload a file to a space (optionally with a text comment)
111
+ const msg = await client.uploadFile(roomId, { content: blob, filename: 'report.pdf' }, { text: 'Latest report' })
112
+ // → WebexMessage { id, roomId, files, ... }
113
+
114
+ // Download a file attachment by content URL or ID
115
+ const { data, filename, contentType } = await client.downloadContent(contentUrlOrId)
116
+ // → { data: ArrayBuffer, filename: string, contentType: string }
117
+ ```
118
+
107
119
  ## WebexCredentialManager
108
120
 
109
121
  Manages Webex credentials stored at `~/.config/agent-messenger/webex-credentials.json`. Files are written with `0o600` permissions. Supports OAuth Device Grant flow, bot tokens, and PATs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.23.4",
3
+ "version": "2.23.6",
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
5
5
  allowed-tools: Bash(agent-discord:*)
6
6
  metadata:
7
7
  openclaw:
@@ -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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
5
5
  allowed-tools: Bash(agent-slack:*)
6
6
  metadata:
7
7
  openclaw:
@@ -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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
5
5
  allowed-tools: Bash(agent-webex:*)
6
6
  metadata:
7
7
  openclaw:
@@ -314,6 +314,19 @@ agent-webex member list <space-id>
314
314
  agent-webex member list <space-id> --limit 100
315
315
  ```
316
316
 
317
+ ### File Commands
318
+
319
+ ```bash
320
+ # Upload a local file to a space
321
+ agent-webex file upload <space-id> <path>
322
+ agent-webex file upload <space-id> ./report.pdf --text "Latest report"
323
+ agent-webex file upload <space-id> ./image.png --text "**Done**" --markdown
324
+
325
+ # Download a file attachment by content URL or ID
326
+ agent-webex file download <content-url-or-id>
327
+ agent-webex file download <content-url-or-id> ./out.pdf
328
+ ```
329
+
317
330
  ### Snapshot Command
318
331
 
319
332
  Get workspace overview for AI agents (brief by default):
@@ -451,7 +464,6 @@ See the [Webex SDK documentation](https://agent-messenger.dev/docs/sdk/webex) fo
451
464
 
452
465
  ## Limitations
453
466
 
454
- - No file upload or download
455
467
  - No reactions / emoji support
456
468
  - No thread support
457
469
  - No message search
@@ -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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
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.23.4
4
+ version: 2.23.6
5
5
  allowed-tools: Bash(agent-whatsappbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -4,7 +4,15 @@ import type { Command as CommandType } from 'commander'
4
4
  import { Command } from 'commander'
5
5
 
6
6
  import pkg from '../../../package.json' with { type: 'json' }
7
- import { authCommand, memberCommand, messageCommand, snapshotCommand, spaceCommand, whoamiCommand } from './commands'
7
+ import {
8
+ authCommand,
9
+ fileCommand,
10
+ memberCommand,
11
+ messageCommand,
12
+ snapshotCommand,
13
+ spaceCommand,
14
+ whoamiCommand,
15
+ } from './commands'
8
16
  import { ensureWebexAuth } from './ensure-auth'
9
17
 
10
18
  function isAuthCommand(command: CommandType): boolean {
@@ -26,6 +34,7 @@ program.hook('preAction', async (_thisCommand, actionCommand) => {
26
34
  })
27
35
 
28
36
  program.addCommand(authCommand)
37
+ program.addCommand(fileCommand)
29
38
  program.addCommand(memberCommand)
30
39
  program.addCommand(messageCommand)
31
40
  program.addCommand(snapshotCommand)
@@ -81,6 +81,38 @@ describe('WebexClient', () => {
81
81
  expect((client as any).deviceUrl).toBe('https://wdm-r.wbx2.com/wdm/api/v1/devices/dev-1')
82
82
  expect((client as any).tokenType).toBe('extracted')
83
83
  })
84
+
85
+ const DEVICE_URL = 'https://wdm-r.wbx2.com/wdm/api/v1/devices/dev-1'
86
+ const encryptionOf = (client: WebexClient) =>
87
+ (client as unknown as { encryption: WebexEncryptionService | null }).encryption
88
+
89
+ it('initializes encryption for explicit extracted credentials with a device URL', async () => {
90
+ const client = await new WebexClient().login({
91
+ token: 'extracted-token',
92
+ deviceUrl: DEVICE_URL,
93
+ tokenType: 'extracted',
94
+ })
95
+ expect(encryptionOf(client)).toBeInstanceOf(WebexEncryptionService)
96
+ })
97
+
98
+ it('initializes encryption for explicit password credentials with a device URL', async () => {
99
+ const client = await new WebexClient().login({
100
+ token: 'password-token',
101
+ deviceUrl: DEVICE_URL,
102
+ tokenType: 'password',
103
+ })
104
+ expect(encryptionOf(client)).toBeInstanceOf(WebexEncryptionService)
105
+ })
106
+
107
+ it('does not initialize encryption for a plain token without device URL', async () => {
108
+ const client = await new WebexClient().login({ token: 'plain-token' })
109
+ expect(encryptionOf(client)).toBeNull()
110
+ })
111
+
112
+ it('does not initialize encryption when device URL is absent', async () => {
113
+ const client = await new WebexClient().login({ token: 'extracted-token', tokenType: 'extracted' })
114
+ expect(encryptionOf(client)).toBeNull()
115
+ })
84
116
  })
85
117
 
86
118
  describe('testAuth', () => {
@@ -623,12 +655,17 @@ describe('WebexClient', () => {
623
655
  activities: { items: activities },
624
656
  })
625
657
 
658
+ // These tests exercise the cleartext internal-API shape, so the KMS-backed
659
+ // encryption service is cleared after login; the encrypted path has its own
660
+ // createEncryptedClient that re-attaches a stub service.
626
661
  const createExtractedClient = async () => {
627
- return new WebexClient().login({
662
+ const client = await new WebexClient().login({
628
663
  token: 'extracted-token',
629
664
  deviceUrl: TEST_DEVICE_URL,
630
665
  tokenType: 'extracted',
631
666
  })
667
+ ;(client as unknown as { encryption: null }).encryption = null
668
+ return client
632
669
  }
633
670
 
634
671
  describe('sendMessage', () => {
@@ -988,6 +1025,31 @@ describe('WebexClient', () => {
988
1025
  expect(decodeJweHeader(body.object.displayName).kid).toBe(TEST_KEY_URI)
989
1026
  expect(decodeJweHeader(body.object.content).kid).toBe(TEST_KEY_URI)
990
1027
  })
1028
+
1029
+ it('explicit-credential login encrypts the send without manually attaching a service', async () => {
1030
+ const keystore = jose.JWK.createKeyStore()
1031
+ const key = await keystore.generate('oct', 256, { alg: 'A256GCM' })
1032
+ const serializedKey = JSON.stringify({ uri: TEST_KEY_URI, jwk: key.toJSON(true) })
1033
+
1034
+ const client = await new WebexClient().login({
1035
+ token: 'extracted-token',
1036
+ deviceUrl: TEST_DEVICE_URL,
1037
+ tokenType: 'extracted',
1038
+ })
1039
+ const service = (client as unknown as { encryption: WebexEncryptionService | null }).encryption
1040
+ expect(service).toBeInstanceOf(WebexEncryptionService)
1041
+ service?.setKeyProvider({ fetchKey: async () => serializedKey })
1042
+
1043
+ mockResponse({ id: TEST_CONV_UUID, defaultActivityEncryptionKeyUrl: TEST_KEY_URI })
1044
+ mockResponse(mockActivity('Hello world'))
1045
+
1046
+ await client.sendMessage(TEST_ROOM_ID, 'Hello world')
1047
+
1048
+ const body = JSON.parse(fetchCalls[1].options?.body as string)
1049
+ expect(body.object.displayName.startsWith('eyJ')).toBe(true)
1050
+ expect(body.encryptionKeyUrl).toBe(TEST_KEY_URI)
1051
+ expect(decodeJweHeader(body.object.displayName).kid).toBe(TEST_KEY_URI)
1052
+ })
991
1053
  })
992
1054
 
993
1055
  describe('sendDirectMessage', () => {
@@ -1018,6 +1080,137 @@ describe('WebexClient', () => {
1018
1080
  })
1019
1081
  })
1020
1082
 
1083
+ describe('uploadFile', () => {
1084
+ const mockUploadFlow = () => {
1085
+ // given: the full internal share flow — conv lookup, space, session, PUT, finish, content
1086
+ mockResponse({ id: TEST_CONV_UUID })
1087
+ mockResponse({ spaceUrl: 'https://files.wbx2.com/spaces/sp1' })
1088
+ mockResponse({
1089
+ uploadUrl: 'https://up.wbx2.com/upload/sess1',
1090
+ finishUploadUrl: 'https://up.wbx2.com/upload/sess1/finish',
1091
+ })
1092
+ mockResponse({}, 200)
1093
+ mockResponse({ downloadUrl: 'https://files.wbx2.com/files/f1' })
1094
+ mockResponse({ ...mockActivity(''), verb: 'share' })
1095
+ }
1096
+
1097
+ const file = () => ({ content: new Blob(['hello world']), filename: 'note.txt' })
1098
+
1099
+ it('routes to the internal conversation API instead of the public messages endpoint', async () => {
1100
+ mockUploadFlow()
1101
+
1102
+ const client = await createExtractedClient()
1103
+ await client.uploadFile(TEST_ROOM_ID, file())
1104
+
1105
+ expect(fetchCalls.every((c) => !c.url.includes('webexapis.com/v1/messages'))).toBe(true)
1106
+ expect(fetchCalls.at(-1)?.url).toBe(`${CONV_BASE}/conversations/${TEST_CONV_UUID}/content`)
1107
+ expect(fetchCalls.at(-1)?.options?.method).toBe('POST')
1108
+ })
1109
+
1110
+ it('requests a space, opens an upload session, PUTs the bytes, then finalizes', async () => {
1111
+ mockUploadFlow()
1112
+
1113
+ const client = await createExtractedClient()
1114
+ await client.uploadFile(TEST_ROOM_ID, file())
1115
+
1116
+ expect(fetchCalls[1].url).toBe(`${CONV_BASE}/conversations/${TEST_CONV_UUID}/space`)
1117
+ expect(fetchCalls[1].options?.method).toBe('PUT')
1118
+ expect(fetchCalls[2].url).toBe('https://files.wbx2.com/spaces/sp1/upload_sessions')
1119
+ expect(fetchCalls[3].url).toBe('https://up.wbx2.com/upload/sess1')
1120
+ expect(fetchCalls[3].options?.method).toBe('PUT')
1121
+ expect(fetchCalls[4].url).toBe('https://up.wbx2.com/upload/sess1/finish')
1122
+ })
1123
+
1124
+ it('finalize body carries fileSize and a sha256 fileHash of the uploaded bytes', async () => {
1125
+ mockUploadFlow()
1126
+
1127
+ const client = await createExtractedClient()
1128
+ await client.uploadFile(TEST_ROOM_ID, file())
1129
+
1130
+ const body = JSON.parse(fetchCalls[4].options?.body as string)
1131
+ expect(body.fileSize).toBe(11)
1132
+ expect(body.fileHash).toMatch(/^[0-9a-f]{64}$/)
1133
+ })
1134
+
1135
+ it('share activity references the uploaded file with download url and metadata', async () => {
1136
+ mockUploadFlow()
1137
+
1138
+ const client = await createExtractedClient()
1139
+ await client.uploadFile(TEST_ROOM_ID, file())
1140
+
1141
+ const body = JSON.parse(fetchCalls.at(-1)?.options?.body as string)
1142
+ expect(body.verb).toBe('share')
1143
+ expect(body.object.objectType).toBe('content')
1144
+ expect(body.object.contentCategory).toBe('documents')
1145
+ const item = body.object.files.items[0]
1146
+ expect(item.objectType).toBe('file')
1147
+ expect(item.url).toBe('https://files.wbx2.com/files/f1')
1148
+ expect(item.fileSize).toBe(11)
1149
+ expect(item.mimeType).toBe('text/plain')
1150
+ expect(item.displayName).toBe('note.txt')
1151
+ })
1152
+
1153
+ it('attaches an optional text comment to the share activity', async () => {
1154
+ mockUploadFlow()
1155
+
1156
+ const client = await createExtractedClient()
1157
+ await client.uploadFile(TEST_ROOM_ID, file(), { text: 'see attached' })
1158
+
1159
+ const body = JSON.parse(fetchCalls.at(-1)?.options?.body as string)
1160
+ expect(body.object.displayName).toBe('see attached')
1161
+ })
1162
+
1163
+ it('categorizes images by mime type', async () => {
1164
+ mockUploadFlow()
1165
+
1166
+ const client = await createExtractedClient()
1167
+ await client.uploadFile(TEST_ROOM_ID, { content: new Blob(['x']), filename: 'photo.png' })
1168
+
1169
+ const body = JSON.parse(fetchCalls.at(-1)?.options?.body as string)
1170
+ expect(body.object.contentCategory).toBe('images')
1171
+ expect(body.object.files.items[0].mimeType).toBe('image/png')
1172
+ })
1173
+
1174
+ it('refuses to upload when the server returns an untrusted space url', async () => {
1175
+ mockResponse({ id: TEST_CONV_UUID })
1176
+ mockResponse({ spaceUrl: 'https://evil.example.com/spaces/sp1' })
1177
+
1178
+ const client = await createExtractedClient()
1179
+
1180
+ await expect(client.uploadFile(TEST_ROOM_ID, file())).rejects.toThrow('untrusted host')
1181
+ expect(fetchCalls.every((c) => !c.url.includes('evil.example.com'))).toBe(true)
1182
+ })
1183
+
1184
+ it('refuses to upload when the server returns a non-https upload url', async () => {
1185
+ mockResponse({ id: TEST_CONV_UUID })
1186
+ mockResponse({ spaceUrl: 'https://files.wbx2.com/spaces/sp1' })
1187
+ mockResponse({
1188
+ uploadUrl: 'http://up.wbx2.com/upload/sess1',
1189
+ finishUploadUrl: 'https://up.wbx2.com/upload/sess1/finish',
1190
+ })
1191
+
1192
+ const client = await createExtractedClient()
1193
+
1194
+ await expect(client.uploadFile(TEST_ROOM_ID, file())).rejects.toThrow('untrusted host')
1195
+ })
1196
+
1197
+ it('accepts trusted Webex urls that carry an explicit port', async () => {
1198
+ mockResponse({ id: TEST_CONV_UUID })
1199
+ mockResponse({ spaceUrl: 'https://files.wbx2.com:443/spaces/sp1' })
1200
+ mockResponse({
1201
+ uploadUrl: 'https://up.wbx2.com:443/upload/sess1',
1202
+ finishUploadUrl: 'https://up.wbx2.com:443/upload/sess1/finish',
1203
+ })
1204
+ mockResponse({}, 200)
1205
+ mockResponse({ downloadUrl: 'https://files.wbx2.com/files/f1' })
1206
+ mockResponse({ ...mockActivity(''), verb: 'share' })
1207
+
1208
+ const client = await createExtractedClient()
1209
+
1210
+ await expect(client.uploadFile(TEST_ROOM_ID, file())).resolves.toBeDefined()
1211
+ })
1212
+ })
1213
+
1021
1214
  describe('error handling', () => {
1022
1215
  it('throws WebexError when internal API returns non-OK response', async () => {
1023
1216
  fetchResponses.push(