agent-messenger 2.23.2 → 2.23.3

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 (83) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/package.json +1 -1
  3. package/dist/src/platforms/webex/client.d.ts.map +1 -1
  4. package/dist/src/platforms/webex/client.js +28 -16
  5. package/dist/src/platforms/webex/client.js.map +1 -1
  6. package/dist/src/platforms/webex/commands/auth.js +1 -2
  7. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  8. package/dist/src/platforms/webex/commands/member.d.ts.map +1 -1
  9. package/dist/src/platforms/webex/commands/member.js +2 -3
  10. package/dist/src/platforms/webex/commands/member.js.map +1 -1
  11. package/dist/src/platforms/webex/commands/message.d.ts.map +1 -1
  12. package/dist/src/platforms/webex/commands/message.js +2 -3
  13. package/dist/src/platforms/webex/commands/message.js.map +1 -1
  14. package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -1
  15. package/dist/src/platforms/webex/commands/whoami.js +2 -3
  16. package/dist/src/platforms/webex/commands/whoami.js.map +1 -1
  17. package/dist/src/platforms/webex/id-normalizer.d.ts +4 -0
  18. package/dist/src/platforms/webex/id-normalizer.d.ts.map +1 -1
  19. package/dist/src/platforms/webex/id-normalizer.js +87 -21
  20. package/dist/src/platforms/webex/id-normalizer.js.map +1 -1
  21. package/dist/src/platforms/webex/types.d.ts +20 -0
  22. package/dist/src/platforms/webex/types.d.ts.map +1 -1
  23. package/dist/src/platforms/webex/types.js +10 -0
  24. package/dist/src/platforms/webex/types.js.map +1 -1
  25. package/dist/src/platforms/webexbot/commands/file.d.ts.map +1 -1
  26. package/dist/src/platforms/webexbot/commands/file.js +2 -3
  27. package/dist/src/platforms/webexbot/commands/file.js.map +1 -1
  28. package/dist/src/platforms/webexbot/commands/member.d.ts.map +1 -1
  29. package/dist/src/platforms/webexbot/commands/member.js +2 -3
  30. package/dist/src/platforms/webexbot/commands/member.js.map +1 -1
  31. package/dist/src/platforms/webexbot/commands/message.d.ts.map +1 -1
  32. package/dist/src/platforms/webexbot/commands/message.js +6 -7
  33. package/dist/src/platforms/webexbot/commands/message.js.map +1 -1
  34. package/dist/src/platforms/webexbot/commands/snapshot.js +1 -1
  35. package/dist/src/platforms/webexbot/commands/snapshot.js.map +1 -1
  36. package/dist/src/platforms/webexbot/commands/user.d.ts.map +1 -1
  37. package/dist/src/platforms/webexbot/commands/user.js +3 -4
  38. package/dist/src/platforms/webexbot/commands/user.js.map +1 -1
  39. package/dist/src/platforms/webexbot/commands/whoami.d.ts.map +1 -1
  40. package/dist/src/platforms/webexbot/commands/whoami.js +2 -3
  41. package/dist/src/platforms/webexbot/commands/whoami.js.map +1 -1
  42. package/dist/src/tui/adapters/webex-adapter.js +2 -2
  43. package/dist/src/tui/adapters/webex-adapter.js.map +1 -1
  44. package/package.json +1 -1
  45. package/skills/agent-channeltalk/SKILL.md +1 -1
  46. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  47. package/skills/agent-discord/SKILL.md +1 -1
  48. package/skills/agent-discordbot/SKILL.md +1 -1
  49. package/skills/agent-instagram/SKILL.md +1 -1
  50. package/skills/agent-kakaotalk/SKILL.md +1 -1
  51. package/skills/agent-line/SKILL.md +1 -1
  52. package/skills/agent-slack/SKILL.md +1 -1
  53. package/skills/agent-slackbot/SKILL.md +1 -1
  54. package/skills/agent-teams/SKILL.md +1 -1
  55. package/skills/agent-telegram/SKILL.md +1 -1
  56. package/skills/agent-telegrambot/SKILL.md +1 -1
  57. package/skills/agent-webex/SKILL.md +1 -1
  58. package/skills/agent-webexbot/SKILL.md +1 -1
  59. package/skills/agent-wechatbot/SKILL.md +1 -1
  60. package/skills/agent-whatsapp/SKILL.md +1 -1
  61. package/skills/agent-whatsappbot/SKILL.md +1 -1
  62. package/src/platforms/webex/client.ts +36 -16
  63. package/src/platforms/webex/commands/auth.ts +3 -3
  64. package/src/platforms/webex/commands/member.test.ts +6 -0
  65. package/src/platforms/webex/commands/member.ts +2 -3
  66. package/src/platforms/webex/commands/message.test.ts +6 -0
  67. package/src/platforms/webex/commands/message.ts +2 -3
  68. package/src/platforms/webex/commands/whoami.test.ts +2 -0
  69. package/src/platforms/webex/commands/whoami.ts +2 -3
  70. package/src/platforms/webex/id-normalizer.test.ts +245 -2
  71. package/src/platforms/webex/id-normalizer.ts +92 -21
  72. package/src/platforms/webex/listener.test.ts +3 -0
  73. package/src/platforms/webex/types.test.ts +20 -0
  74. package/src/platforms/webex/types.ts +20 -0
  75. package/src/platforms/webex/typings/webex-message-handler.d.ts +40 -2
  76. package/src/platforms/webexbot/commands/file.ts +2 -3
  77. package/src/platforms/webexbot/commands/member.ts +2 -3
  78. package/src/platforms/webexbot/commands/message.ts +6 -7
  79. package/src/platforms/webexbot/commands/snapshot.ts +1 -1
  80. package/src/platforms/webexbot/commands/user.test.ts +4 -0
  81. package/src/platforms/webexbot/commands/user.ts +3 -4
  82. package/src/platforms/webexbot/commands/whoami.ts +2 -3
  83. package/src/tui/adapters/webex-adapter.ts +2 -2
@@ -16,9 +16,13 @@ import {
16
16
  normalizeMembership,
17
17
  normalizeMessage,
18
18
  normalizeRoomActivity,
19
+ normalizeSdkMembership,
20
+ normalizeSdkMessage,
21
+ normalizeSdkPerson,
19
22
  toRestId,
20
23
  toRef,
21
24
  } from './id-normalizer'
25
+ import type { WebexMembership, WebexMessage, WebexPerson } from './types'
22
26
 
23
27
  const RAW: MercuryActivity = {
24
28
  id: 'activity-uuid',
@@ -122,6 +126,23 @@ describe('normalizeMessage', () => {
122
126
  expect(result.mentionedPeople).toEqual([toRestId('mention-uuid-1', 'PEOPLE'), toRestId('mention-uuid-2', 'PEOPLE')])
123
127
  })
124
128
 
129
+ it('adds a raw uuid ref alongside every id', () => {
130
+ const result = normalizeMessage(message)
131
+
132
+ expect(result.ref).toBe('msg-uuid')
133
+ expect(result.parentRef).toBe('parent-uuid')
134
+ expect(result.roomRef).toBe('room-uuid')
135
+ expect(result.personRef).toBe('person-uuid')
136
+ expect(result.mentionedPeopleRefs).toEqual(['mention-uuid-1', 'mention-uuid-2'])
137
+ })
138
+
139
+ it('omits parentRef when parentId is absent', () => {
140
+ const { parentId: _omit, ...withoutParent } = message
141
+ const result = normalizeMessage(withoutParent)
142
+
143
+ expect(result.parentRef).toBeUndefined()
144
+ })
145
+
125
146
  it('leaves non-id fields and raw untouched', () => {
126
147
  const result = normalizeMessage(message)
127
148
 
@@ -157,8 +178,11 @@ describe('normalizeDeletedMessage', () => {
157
178
 
158
179
  expect(normalizeDeletedMessage(deleted)).toEqual({
159
180
  messageId: toRestId('msg-uuid', 'MESSAGE'),
181
+ messageRef: 'msg-uuid',
160
182
  roomId: toRestId('room-uuid', 'ROOM'),
183
+ roomRef: 'room-uuid',
161
184
  personId: toRestId('person-uuid', 'PEOPLE'),
185
+ personRef: 'person-uuid',
162
186
  })
163
187
  })
164
188
  })
@@ -183,6 +207,25 @@ describe('normalizeMembership', () => {
183
207
  expect(result.roomId).toBe(toRestId('room-uuid', 'ROOM'))
184
208
  expect(result.raw).toBe(RAW)
185
209
  })
210
+
211
+ it('sets ref to the raw activity id and adds refs for the rest', () => {
212
+ const membership: MembershipActivity = {
213
+ id: 'activity-uuid',
214
+ actorId: 'actor-uuid',
215
+ personId: 'member-uuid',
216
+ roomId: 'room-uuid',
217
+ action: 'add',
218
+ created: '2024-01-01T00:00:00Z',
219
+ raw: RAW,
220
+ }
221
+
222
+ const result = normalizeMembership(membership)
223
+
224
+ expect(result.ref).toBe('activity-uuid')
225
+ expect(result.actorRef).toBe('actor-uuid')
226
+ expect(result.personRef).toBe('member-uuid')
227
+ expect(result.roomRef).toBe('room-uuid')
228
+ })
186
229
  })
187
230
 
188
231
  describe('normalizeAttachmentAction', () => {
@@ -207,7 +250,27 @@ describe('normalizeAttachmentAction', () => {
207
250
  expect(result.inputs).toEqual({ choice: 'yes' })
208
251
  })
209
252
 
210
- it('preserves an empty messageId', () => {
253
+ it('adds a raw uuid ref alongside every id', () => {
254
+ const action: AttachmentAction = {
255
+ id: 'action-uuid',
256
+ messageId: 'msg-uuid',
257
+ personId: 'person-uuid',
258
+ personEmail: 'user@example.com',
259
+ roomId: 'room-uuid',
260
+ inputs: { choice: 'yes' },
261
+ created: '2024-01-01T00:00:00Z',
262
+ raw: RAW,
263
+ }
264
+
265
+ const result = normalizeAttachmentAction(action)
266
+
267
+ expect(result.ref).toBe('action-uuid')
268
+ expect(result.messageRef).toBe('msg-uuid')
269
+ expect(result.personRef).toBe('person-uuid')
270
+ expect(result.roomRef).toBe('room-uuid')
271
+ })
272
+
273
+ it('preserves an empty messageId and its ref', () => {
211
274
  const action: AttachmentAction = {
212
275
  id: 'action-uuid',
213
276
  messageId: '',
@@ -219,7 +282,9 @@ describe('normalizeAttachmentAction', () => {
219
282
  raw: RAW,
220
283
  }
221
284
 
222
- expect(normalizeAttachmentAction(action).messageId).toBe('')
285
+ const result = normalizeAttachmentAction(action)
286
+ expect(result.messageId).toBe('')
287
+ expect(result.messageRef).toBe('')
223
288
  })
224
289
  })
225
290
 
@@ -241,4 +306,182 @@ describe('normalizeRoomActivity', () => {
241
306
  expect(result.actorId).toBe(toRestId('actor-uuid', 'PEOPLE'))
242
307
  expect(result.raw).toBe(RAW)
243
308
  })
309
+
310
+ it('sets ref to the raw activity id and adds refs for the rest', () => {
311
+ const room: RoomActivity = {
312
+ id: 'activity-uuid',
313
+ roomId: 'room-uuid',
314
+ actorId: 'actor-uuid',
315
+ action: 'created',
316
+ created: '2024-01-01T00:00:00Z',
317
+ raw: RAW,
318
+ }
319
+
320
+ const result = normalizeRoomActivity(room)
321
+
322
+ expect(result.ref).toBe('activity-uuid')
323
+ expect(result.roomRef).toBe('room-uuid')
324
+ expect(result.actorRef).toBe('actor-uuid')
325
+ })
326
+ })
327
+
328
+ describe('normalizeSdkPerson', () => {
329
+ const person: WebexPerson = {
330
+ id: restId('PEOPLE', 'person-uuid'),
331
+ ref: '',
332
+ emails: ['user@example.com'],
333
+ displayName: 'User',
334
+ orgId: restId('ORGANIZATION', 'org-uuid'),
335
+ orgRef: '',
336
+ type: 'person',
337
+ created: '2024-01-01T00:00:00Z',
338
+ }
339
+
340
+ it('decodes the bot identity id and orgId into raw uuid refs', () => {
341
+ const result = normalizeSdkPerson(person)
342
+
343
+ expect(result.ref).toBe('person-uuid')
344
+ expect(result.orgRef).toBe('org-uuid')
345
+ })
346
+
347
+ it('does not re-encode the already-REST ids', () => {
348
+ const result = normalizeSdkPerson(person)
349
+
350
+ expect(result.id).toBe(restId('PEOPLE', 'person-uuid'))
351
+ expect(result.orgId).toBe(restId('ORGANIZATION', 'org-uuid'))
352
+ })
353
+
354
+ it('leaves empty ids and refs empty', () => {
355
+ const result = normalizeSdkPerson({ ...person, id: '', orgId: '' })
356
+
357
+ expect(result.ref).toBe('')
358
+ expect(result.orgRef).toBe('')
359
+ })
360
+
361
+ it('returns a new object without mutating the input', () => {
362
+ const result = normalizeSdkPerson(person)
363
+
364
+ expect(result).not.toBe(person)
365
+ expect(person.ref).toBe('')
366
+ })
367
+ })
368
+
369
+ describe('normalizeSdkMessage', () => {
370
+ const message: WebexMessage = {
371
+ id: restId('MESSAGE', 'msg-uuid'),
372
+ ref: '',
373
+ roomId: restId('ROOM', 'room-uuid'),
374
+ roomRef: '',
375
+ roomType: 'group',
376
+ text: 'hello',
377
+ personId: restId('PEOPLE', 'person-uuid'),
378
+ personRef: '',
379
+ personEmail: 'user@example.com',
380
+ created: '2024-01-01T00:00:00Z',
381
+ parentId: restId('MESSAGE', 'parent-uuid'),
382
+ mentionedPeople: [restId('PEOPLE', 'mention-1'), restId('PEOPLE', 'mention-2')],
383
+ }
384
+
385
+ it('decodes id, roomId, personId and parentId into raw uuid refs', () => {
386
+ const result = normalizeSdkMessage(message)
387
+
388
+ expect(result.ref).toBe('msg-uuid')
389
+ expect(result.roomRef).toBe('room-uuid')
390
+ expect(result.personRef).toBe('person-uuid')
391
+ expect(result.parentRef).toBe('parent-uuid')
392
+ })
393
+
394
+ it('adds a mentionedPeopleRefs array of raw uuids', () => {
395
+ const result = normalizeSdkMessage(message)
396
+
397
+ expect(result.mentionedPeopleRefs).toEqual(['mention-1', 'mention-2'])
398
+ })
399
+
400
+ it('omits parentRef when parentId is absent', () => {
401
+ const { parentId: _omit, ...withoutParent } = message
402
+ const result = normalizeSdkMessage(withoutParent)
403
+
404
+ expect(result.parentRef).toBeUndefined()
405
+ })
406
+
407
+ it('omits mentionedPeopleRefs when mentionedPeople is absent', () => {
408
+ const { mentionedPeople: _omit, ...withoutMentions } = message
409
+ const result = normalizeSdkMessage(withoutMentions)
410
+
411
+ expect(result.mentionedPeopleRefs).toBeUndefined()
412
+ })
413
+
414
+ it('does not re-encode the already-REST ids', () => {
415
+ const result = normalizeSdkMessage(message)
416
+
417
+ expect(result.id).toBe(restId('MESSAGE', 'msg-uuid'))
418
+ expect(result.roomId).toBe(restId('ROOM', 'room-uuid'))
419
+ })
420
+ })
421
+
422
+ describe('normalizeSdkMembership', () => {
423
+ const membership: WebexMembership = {
424
+ id: restId('MEMBERSHIP', 'membership-uuid'),
425
+ ref: '',
426
+ roomId: restId('ROOM', 'room-uuid'),
427
+ roomRef: '',
428
+ personId: restId('PEOPLE', 'person-uuid'),
429
+ personRef: '',
430
+ personEmail: 'user@example.com',
431
+ personDisplayName: 'User',
432
+ isModerator: false,
433
+ created: '2024-01-01T00:00:00Z',
434
+ }
435
+
436
+ it('decodes id, roomId and personId into raw uuid refs', () => {
437
+ const result = normalizeSdkMembership(membership)
438
+
439
+ expect(result.ref).toBe('membership-uuid')
440
+ expect(result.roomRef).toBe('room-uuid')
441
+ expect(result.personRef).toBe('person-uuid')
442
+ })
443
+
444
+ it('does not re-encode the already-REST ids', () => {
445
+ const result = normalizeSdkMembership(membership)
446
+
447
+ expect(result.id).toBe(restId('MEMBERSHIP', 'membership-uuid'))
448
+ expect(result.personId).toBe(restId('PEOPLE', 'person-uuid'))
449
+ })
450
+ })
451
+
452
+ describe('SDK and event refs agree for the same person', () => {
453
+ it('matches the bot identity ref against an event mention ref so self/mention detection holds', () => {
454
+ const personUuid = 'bot-person-uuid'
455
+
456
+ // given: the bot identity from a REST response (testAuth path)
457
+ const bot = normalizeSdkPerson({
458
+ id: restId('PEOPLE', personUuid),
459
+ ref: '',
460
+ emails: ['bot@webex.bot'],
461
+ displayName: 'Bot',
462
+ orgId: restId('ORGANIZATION', 'org-uuid'),
463
+ orgRef: '',
464
+ type: 'bot',
465
+ created: '2024-01-01T00:00:00Z',
466
+ })
467
+
468
+ // when: an event carries the same person as a raw Mercury uuid
469
+ const event = normalizeMessage({
470
+ id: 'msg-uuid',
471
+ roomId: 'room-uuid',
472
+ personId: personUuid,
473
+ personEmail: 'bot@webex.bot',
474
+ text: 'hi',
475
+ created: '2024-01-01T00:00:00Z',
476
+ mentionedPeople: [personUuid],
477
+ mentionedGroups: [],
478
+ files: [],
479
+ raw: RAW,
480
+ })
481
+
482
+ // then: both paths decode to the same raw uuid ref
483
+ expect(bot.ref).toBe(personUuid)
484
+ expect(event.personRef).toBe(personUuid)
485
+ expect(event.mentionedPeopleRefs.includes(bot.ref)).toBe(true)
486
+ })
244
487
  })
@@ -7,6 +7,8 @@ import type {
7
7
  RoomActivity,
8
8
  } from 'webex-message-handler'
9
9
 
10
+ import type { WebexMembership, WebexMessage, WebexPerson } from './types'
11
+
10
12
  export { fromRestId }
11
13
 
12
14
  // Superset of webex-message-handler's toRestId union, which omits ATTACHMENT_ACTION
@@ -20,7 +22,9 @@ export interface DecodedWebexId {
20
22
  }
21
23
 
22
24
  export function toRef(id: string): string {
23
- if (!id) return id
25
+ // fromRestId throws on values that are not base64-encoded ciscospark ids; fail
26
+ // open so a non-REST id (legacy/sentinel) yields the id itself instead of crashing.
27
+ if (!id || !decodeWebexId(id)) return id
24
28
  return fromRestId(id)
25
29
  }
26
30
 
@@ -48,50 +52,117 @@ export function toRestId(uuid: string, type: WebexRestIdType): string {
48
52
  }
49
53
 
50
54
  export function normalizeMessage(message: DecryptedMessage): DecryptedMessage {
55
+ const id = toRestId(message.id, 'MESSAGE')
56
+ const parentId = message.parentId ? toRestId(message.parentId, 'MESSAGE') : message.parentId
57
+ const roomId = toRestId(message.roomId, 'ROOM')
58
+ const personId = toRestId(message.personId, 'PEOPLE')
59
+ const mentionedPeople = message.mentionedPeople.map((person) => toRestId(person, 'PEOPLE'))
51
60
  return {
52
61
  ...message,
53
- id: toRestId(message.id, 'MESSAGE'),
54
- parentId: message.parentId ? toRestId(message.parentId, 'MESSAGE') : message.parentId,
55
- roomId: toRestId(message.roomId, 'ROOM'),
56
- personId: toRestId(message.personId, 'PEOPLE'),
57
- mentionedPeople: message.mentionedPeople.map((id) => toRestId(id, 'PEOPLE')),
62
+ id,
63
+ ref: toRef(id),
64
+ parentId,
65
+ parentRef: parentId ? toRef(parentId) : parentId,
66
+ roomId,
67
+ roomRef: toRef(roomId),
68
+ personId,
69
+ personRef: toRef(personId),
70
+ mentionedPeople,
71
+ mentionedPeopleRefs: mentionedPeople.map(toRef),
58
72
  }
59
73
  }
60
74
 
61
75
  export function normalizeDeletedMessage(message: DeletedMessage): DeletedMessage {
76
+ const messageId = toRestId(message.messageId, 'MESSAGE')
77
+ const roomId = toRestId(message.roomId, 'ROOM')
78
+ const personId = toRestId(message.personId, 'PEOPLE')
62
79
  return {
63
- messageId: toRestId(message.messageId, 'MESSAGE'),
64
- roomId: toRestId(message.roomId, 'ROOM'),
65
- personId: toRestId(message.personId, 'PEOPLE'),
80
+ messageId,
81
+ messageRef: toRef(messageId),
82
+ roomId,
83
+ roomRef: toRef(roomId),
84
+ personId,
85
+ personRef: toRef(personId),
66
86
  }
67
87
  }
68
88
 
69
89
  export function normalizeMembership(activity: MembershipActivity): MembershipActivity {
70
- // `id` stays raw: it is a Mercury activity UUID, not a REST membership ID.
90
+ // `id` stays raw: it is a Mercury activity UUID, not a REST membership ID, so
91
+ // its ref is the id itself rather than a decoded REST id.
92
+ const actorId = toRestId(activity.actorId, 'PEOPLE')
93
+ const personId = toRestId(activity.personId, 'PEOPLE')
94
+ const roomId = toRestId(activity.roomId, 'ROOM')
71
95
  return {
72
96
  ...activity,
73
- actorId: toRestId(activity.actorId, 'PEOPLE'),
74
- personId: toRestId(activity.personId, 'PEOPLE'),
75
- roomId: toRestId(activity.roomId, 'ROOM'),
97
+ ref: activity.id,
98
+ actorId,
99
+ actorRef: toRef(actorId),
100
+ personId,
101
+ personRef: toRef(personId),
102
+ roomId,
103
+ roomRef: toRef(roomId),
76
104
  }
77
105
  }
78
106
 
79
107
  export function normalizeAttachmentAction(action: AttachmentAction): AttachmentAction {
108
+ const id = toRestId(action.id, 'ATTACHMENT_ACTION')
109
+ const messageId = action.messageId ? toRestId(action.messageId, 'MESSAGE') : action.messageId
110
+ const personId = toRestId(action.personId, 'PEOPLE')
111
+ const roomId = toRestId(action.roomId, 'ROOM')
80
112
  return {
81
113
  ...action,
82
- id: toRestId(action.id, 'ATTACHMENT_ACTION'),
83
- messageId: action.messageId ? toRestId(action.messageId, 'MESSAGE') : action.messageId,
84
- personId: toRestId(action.personId, 'PEOPLE'),
85
- roomId: toRestId(action.roomId, 'ROOM'),
114
+ id,
115
+ ref: toRef(id),
116
+ messageId,
117
+ messageRef: toRef(messageId),
118
+ personId,
119
+ personRef: toRef(personId),
120
+ roomId,
121
+ roomRef: toRef(roomId),
86
122
  }
87
123
  }
88
124
 
89
125
  export function normalizeRoomActivity(activity: RoomActivity): RoomActivity {
90
- // `id` stays raw: the Mercury conversation activity UUID has no
91
- // consumer-facing REST resource (the comparable REST ID is `roomId`).
126
+ // `id` stays raw: the Mercury conversation activity UUID has no consumer-facing
127
+ // REST resource, so its ref is the id itself (the comparable REST id is `roomId`).
128
+ const roomId = toRestId(activity.roomId, 'ROOM')
129
+ const actorId = toRestId(activity.actorId, 'PEOPLE')
92
130
  return {
93
131
  ...activity,
94
- roomId: toRestId(activity.roomId, 'ROOM'),
95
- actorId: toRestId(activity.actorId, 'PEOPLE'),
132
+ ref: activity.id,
133
+ roomId,
134
+ roomRef: toRef(roomId),
135
+ actorId,
136
+ actorRef: toRef(actorId),
137
+ }
138
+ }
139
+
140
+ // SDK REST responses (people/messages/memberships) already carry REST-encoded ids,
141
+ // so unlike the event normalizers above we only attach raw uuid refs — no re-encoding.
142
+ export function normalizeSdkPerson(person: WebexPerson): WebexPerson {
143
+ return {
144
+ ...person,
145
+ ref: toRef(person.id),
146
+ orgRef: toRef(person.orgId),
147
+ }
148
+ }
149
+
150
+ export function normalizeSdkMessage(message: WebexMessage): WebexMessage {
151
+ return {
152
+ ...message,
153
+ ref: toRef(message.id),
154
+ roomRef: toRef(message.roomId),
155
+ personRef: toRef(message.personId),
156
+ parentRef: message.parentId ? toRef(message.parentId) : message.parentId,
157
+ mentionedPeopleRefs: message.mentionedPeople?.map(toRef),
158
+ }
159
+ }
160
+
161
+ export function normalizeSdkMembership(membership: WebexMembership): WebexMembership {
162
+ return {
163
+ ...membership,
164
+ ref: toRef(membership.id),
165
+ roomRef: toRef(membership.roomId),
166
+ personRef: toRef(membership.personId),
96
167
  }
97
168
  }
@@ -81,8 +81,11 @@ describe('WebexListener', () => {
81
81
 
82
82
  const expected = expect.objectContaining({
83
83
  id: toRestId('message-123', 'MESSAGE'),
84
+ ref: 'message-123',
84
85
  roomId: toRestId('room-123', 'ROOM'),
86
+ roomRef: 'room-123',
85
87
  personId: toRestId('person-123', 'PEOPLE'),
88
+ personRef: 'person-123',
86
89
  personEmail: 'user@example.com',
87
90
  text: 'hello',
88
91
  raw: RAW_ACTIVITY,
@@ -73,10 +73,13 @@ it('WebexSpaceSchema rejects invalid type', () => {
73
73
  it('WebexMessageSchema validates valid message', () => {
74
74
  const result = WebexMessageSchema.safeParse({
75
75
  id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
76
+ ref: 'msg',
76
77
  roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
78
+ roomRef: 'abc',
77
79
  roomType: 'group',
78
80
  text: 'Hello world',
79
81
  personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
82
+ personRef: 'abc',
80
83
  personEmail: 'user@example.com',
81
84
  created: '2024-01-15T10:30:00.000Z',
82
85
  })
@@ -86,17 +89,22 @@ it('WebexMessageSchema validates valid message', () => {
86
89
  it('WebexMessageSchema validates message with optional fields', () => {
87
90
  const result = WebexMessageSchema.safeParse({
88
91
  id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
92
+ ref: 'msg',
89
93
  roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
94
+ roomRef: 'abc',
90
95
  roomType: 'group',
91
96
  text: 'Hello world',
92
97
  markdown: '**Hello world**',
93
98
  html: '<strong>Hello world</strong>',
94
99
  files: ['https://webexapis.com/v1/contents/file1'],
95
100
  personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
101
+ personRef: 'abc',
96
102
  personEmail: 'user@example.com',
97
103
  created: '2024-01-15T10:30:00.000Z',
98
104
  parentId: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvcGFyZW50',
105
+ parentRef: 'parent',
99
106
  mentionedPeople: ['Y2lzY29zcGFyazovL3VzL1BFT1BMRS9tZW50aW9u'],
107
+ mentionedPeopleRefs: ['mention'],
100
108
  })
101
109
  expect(result.success).toBe(true)
102
110
  })
@@ -125,9 +133,11 @@ it('WebexMessageSchema rejects invalid roomType', () => {
125
133
  it('WebexPersonSchema validates valid person', () => {
126
134
  const result = WebexPersonSchema.safeParse({
127
135
  id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
136
+ ref: 'abc',
128
137
  emails: ['user@example.com'],
129
138
  displayName: 'Test User',
130
139
  orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
140
+ orgRef: 'org',
131
141
  type: 'person',
132
142
  created: '2024-01-01T00:00:00.000Z',
133
143
  })
@@ -137,6 +147,7 @@ it('WebexPersonSchema validates valid person', () => {
137
147
  it('WebexPersonSchema validates person with optional fields', () => {
138
148
  const result = WebexPersonSchema.safeParse({
139
149
  id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
150
+ ref: 'abc',
140
151
  emails: ['user@example.com', 'user@work.com'],
141
152
  displayName: 'Test User',
142
153
  nickName: 'Tester',
@@ -144,6 +155,7 @@ it('WebexPersonSchema validates person with optional fields', () => {
144
155
  lastName: 'User',
145
156
  avatar: 'https://example.com/avatar.jpg',
146
157
  orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
158
+ orgRef: 'org',
147
159
  type: 'person',
148
160
  created: '2024-01-01T00:00:00.000Z',
149
161
  })
@@ -153,9 +165,11 @@ it('WebexPersonSchema validates person with optional fields', () => {
153
165
  it('WebexPersonSchema validates bot type', () => {
154
166
  const result = WebexPersonSchema.safeParse({
155
167
  id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9ib3Q',
168
+ ref: 'bot',
156
169
  emails: ['bot@webex.bot'],
157
170
  displayName: 'My Bot',
158
171
  orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
172
+ orgRef: 'org',
159
173
  type: 'bot',
160
174
  created: '2024-01-01T00:00:00.000Z',
161
175
  })
@@ -185,8 +199,11 @@ it('WebexPersonSchema rejects invalid type', () => {
185
199
  it('WebexMembershipSchema validates valid membership', () => {
186
200
  const result = WebexMembershipSchema.safeParse({
187
201
  id: 'Y2lzY29zcGFyazovL3VzL01FTUJFUlNISVAvbWVt',
202
+ ref: 'mem',
188
203
  roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
204
+ roomRef: 'abc',
189
205
  personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
206
+ personRef: 'abc',
190
207
  personEmail: 'user@example.com',
191
208
  personDisplayName: 'Test User',
192
209
  isModerator: false,
@@ -198,8 +215,11 @@ it('WebexMembershipSchema validates valid membership', () => {
198
215
  it('WebexMembershipSchema validates moderator membership', () => {
199
216
  const result = WebexMembershipSchema.safeParse({
200
217
  id: 'Y2lzY29zcGFyazovL3VzL01FTUJFUlNISVAvbWVt',
218
+ ref: 'mem',
201
219
  roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
220
+ roomRef: 'abc',
202
221
  personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
222
+ personRef: 'abc',
203
223
  personEmail: 'moderator@example.com',
204
224
  personDisplayName: 'Moderator User',
205
225
  isModerator: true,
@@ -13,21 +13,27 @@ export interface WebexSpace {
13
13
 
14
14
  export interface WebexMessage {
15
15
  id: string
16
+ ref: string
16
17
  roomId: string
18
+ roomRef: string
17
19
  roomType: 'group' | 'direct'
18
20
  text?: string
19
21
  markdown?: string
20
22
  html?: string
21
23
  files?: string[]
22
24
  personId: string
25
+ personRef: string
23
26
  personEmail: string
24
27
  created: string
25
28
  parentId?: string
29
+ parentRef?: string
26
30
  mentionedPeople?: string[]
31
+ mentionedPeopleRefs?: string[]
27
32
  }
28
33
 
29
34
  export interface WebexPerson {
30
35
  id: string
36
+ ref: string
31
37
  emails: string[]
32
38
  displayName: string
33
39
  nickName?: string
@@ -35,14 +41,18 @@ export interface WebexPerson {
35
41
  lastName?: string
36
42
  avatar?: string
37
43
  orgId: string
44
+ orgRef: string
38
45
  type: 'person' | 'bot'
39
46
  created: string
40
47
  }
41
48
 
42
49
  export interface WebexMembership {
43
50
  id: string
51
+ ref: string
44
52
  roomId: string
53
+ roomRef: string
45
54
  personId: string
55
+ personRef: string
46
56
  personEmail: string
47
57
  personDisplayName: string
48
58
  isModerator: boolean
@@ -84,21 +94,27 @@ export const WebexSpaceSchema = z.object({
84
94
 
85
95
  export const WebexMessageSchema = z.object({
86
96
  id: z.string(),
97
+ ref: z.string(),
87
98
  roomId: z.string(),
99
+ roomRef: z.string(),
88
100
  roomType: z.enum(['group', 'direct']),
89
101
  text: z.string().optional(),
90
102
  markdown: z.string().optional(),
91
103
  html: z.string().optional(),
92
104
  files: z.array(z.string()).optional(),
93
105
  personId: z.string(),
106
+ personRef: z.string(),
94
107
  personEmail: z.string(),
95
108
  created: z.string(),
96
109
  parentId: z.string().optional(),
110
+ parentRef: z.string().optional(),
97
111
  mentionedPeople: z.array(z.string()).optional(),
112
+ mentionedPeopleRefs: z.array(z.string()).optional(),
98
113
  })
99
114
 
100
115
  export const WebexPersonSchema = z.object({
101
116
  id: z.string(),
117
+ ref: z.string(),
102
118
  emails: z.array(z.string()),
103
119
  displayName: z.string(),
104
120
  nickName: z.string().optional(),
@@ -106,14 +122,18 @@ export const WebexPersonSchema = z.object({
106
122
  lastName: z.string().optional(),
107
123
  avatar: z.string().optional(),
108
124
  orgId: z.string(),
125
+ orgRef: z.string(),
109
126
  type: z.enum(['person', 'bot']),
110
127
  created: z.string(),
111
128
  })
112
129
 
113
130
  export const WebexMembershipSchema = z.object({
114
131
  id: z.string(),
132
+ ref: z.string(),
115
133
  roomId: z.string(),
134
+ roomRef: z.string(),
116
135
  personId: z.string(),
136
+ personRef: z.string(),
117
137
  personEmail: z.string(),
118
138
  personDisplayName: z.string(),
119
139
  isModerator: z.boolean(),