agent-messenger 2.36.0 → 2.37.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 (53) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/package.json +1 -1
  3. package/dist/src/platforms/kakaotalk/chat-classifier.d.ts +2 -1
  4. package/dist/src/platforms/kakaotalk/chat-classifier.d.ts.map +1 -1
  5. package/dist/src/platforms/kakaotalk/chat-classifier.js +8 -6
  6. package/dist/src/platforms/kakaotalk/chat-classifier.js.map +1 -1
  7. package/dist/src/platforms/kakaotalk/client.d.ts +21 -1
  8. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  9. package/dist/src/platforms/kakaotalk/client.js +272 -32
  10. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  11. package/dist/src/platforms/kakaotalk/index.d.ts +3 -3
  12. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  13. package/dist/src/platforms/kakaotalk/index.js +2 -2
  14. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  15. package/dist/src/platforms/kakaotalk/types.d.ts +48 -2
  16. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  17. package/dist/src/platforms/kakaotalk/types.js +13 -1
  18. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  19. package/dist/src/platforms/telegram/tdlib.d.ts.map +1 -1
  20. package/dist/src/platforms/telegram/tdlib.js +5 -2
  21. package/dist/src/platforms/telegram/tdlib.js.map +1 -1
  22. package/docs/content/docs/cli/kakaotalk.mdx +7 -1
  23. package/docs/content/docs/sdk/kakaotalk.mdx +29 -1
  24. package/package.json +1 -1
  25. package/skills/agent-channeltalk/SKILL.md +1 -1
  26. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  27. package/skills/agent-discord/SKILL.md +1 -1
  28. package/skills/agent-discordbot/SKILL.md +1 -1
  29. package/skills/agent-imessage/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-kakaotalk/references/common-patterns.md +36 -0
  33. package/skills/agent-line/SKILL.md +1 -1
  34. package/skills/agent-slack/SKILL.md +1 -1
  35. package/skills/agent-slackbot/SKILL.md +1 -1
  36. package/skills/agent-teams/SKILL.md +1 -1
  37. package/skills/agent-telegram/SKILL.md +1 -1
  38. package/skills/agent-telegrambot/SKILL.md +1 -1
  39. package/skills/agent-webex/SKILL.md +1 -1
  40. package/skills/agent-webexbot/SKILL.md +1 -1
  41. package/skills/agent-wechatbot/SKILL.md +1 -1
  42. package/skills/agent-whatsapp/SKILL.md +1 -1
  43. package/skills/agent-whatsappbot/SKILL.md +1 -1
  44. package/src/platforms/kakaotalk/chat-classifier.test.ts +1 -1
  45. package/src/platforms/kakaotalk/chat-classifier.ts +10 -7
  46. package/src/platforms/kakaotalk/client.test.ts +593 -24
  47. package/src/platforms/kakaotalk/client.ts +337 -35
  48. package/src/platforms/kakaotalk/index.test.ts +12 -0
  49. package/src/platforms/kakaotalk/index.ts +7 -1
  50. package/src/platforms/kakaotalk/types.ts +33 -2
  51. package/src/platforms/telegram/tdlib-node-test.ts +19 -0
  52. package/src/platforms/telegram/tdlib-node.test.ts +11 -0
  53. package/src/platforms/telegram/tdlib.ts +6 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.36.0",
3
+ "version": "2.37.0",
4
4
  "description": "Messaging platform interaction skills for AI agents. Interact with Slack, Discord, Microsoft Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, iMessage, 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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.36.0",
3
+ "version": "2.37.0",
4
4
  "description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, iMessage, Instagram, KakaoTalk, Channel Talk)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,6 @@
1
- import type { KakaoChat } from './types.js';
1
+ import type { KakaoChat, KakaoOpenChatType } from './types.js';
2
2
  export type KakaoChatKind = 'dm' | 'group' | 'open' | 'unknown';
3
+ export declare function isOpenKakaoChatType(type: unknown): type is KakaoOpenChatType;
3
4
  /**
4
5
  * Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
5
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"chat-classifier.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAQ/D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC,GAAG,aAAa,CAMjG"}
1
+ {"version":3,"file":"chat-classifier.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAE3D,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAO/D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,iBAAiB,CAE5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC,GAAG,aAAa,CAMjG"}
@@ -1,8 +1,10 @@
1
- // OpenChat-family `type` codes observed on the wire. KakaoTalk's LOCO
2
- // protocol exposes a numeric `type` field with no documented mapping; these
3
- // five codes consistently identify OpenChat rooms across normal OpenChat,
4
- // OpenChat DMs, and the various OpenChat sub-types seen in production.
5
- const OPEN_CHAT_TYPE_CODES = new Set([2, 13, 14, 15, 16]);
1
+ // OpenChat-family `type` values observed on the wire. Login/chat-list data
2
+ // uses numeric codes while CHATINFO may return the protocol string tags
3
+ // `OM` / `OD`; both forms must share this single classification owner.
4
+ const OPEN_CHAT_TYPES = new Set([2, 13, 14, 15, 16, 'OM', 'OD']);
5
+ export function isOpenKakaoChatType(type) {
6
+ return OPEN_CHAT_TYPES.has(type);
7
+ }
6
8
  /**
7
9
  * Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
8
10
  *
@@ -18,7 +20,7 @@ const OPEN_CHAT_TYPE_CODES = new Set([2, 13, 14, 15, 16]);
18
20
  * the case defensively.
19
21
  */
20
22
  export function classifyKakaoChat(chat) {
21
- if (OPEN_CHAT_TYPE_CODES.has(chat.type))
23
+ if (isOpenKakaoChatType(chat.type))
22
24
  return 'open';
23
25
  // active_members counts the logged-in user, so a 1:1 DM is exactly 2
24
26
  // (self + one other) and a "lone" room with only self is 1.
@@ -1 +1 @@
1
- {"version":3,"file":"chat-classifier.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAIA,sEAAsE;AACtE,4EAA4E;AAC5E,0EAA0E;AAC1E,uEAAuE;AACvE,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAgD;IAChF,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAA;IACtD,qEAAqE;IACrE,4DAA4D;IAC5D,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,OAAO,CAAA;AAChB,CAAC"}
1
+ {"version":3,"file":"chat-classifier.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAIA,2EAA2E;AAC3E,wEAAwE;AACxE,uEAAuE;AACvE,MAAM,eAAe,GAAmC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAEhG,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,OAAO,eAAe,CAAC,GAAG,CAAC,IAAyB,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAgD;IAChF,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAA;IACjD,qEAAqE;IACrE,4DAA4D;IAC5D,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { type AttachmentInput } from './attachment-router.js';
2
2
  import { LocoSession } from './protocol/session.js';
3
3
  import type { LocoPacket } from './protocol/types.js';
4
- import { type KakaoChat, type KakaoDeviceType, type KakaoLeaveChatResult, type KakaoMarkReadResult, type KakaoMember, type KakaoMessage, type KakaoProfile, type KakaoReplyTarget, type KakaoSendResult, type KakaoTypingResult } from './types.js';
4
+ import { type KakaoChat, type KakaoDeviceType, type KakaoLeaveChatResult, type KakaoMarkReadResult, type KakaoMember, type KakaoMemberSnapshot, type KakaoMessage, type KakaoMessagePage, type KakaoProfile, type KakaoReplyTarget, type KakaoSendResult, type KakaoTypingResult } from './types.js';
5
5
  export type KakaoSessionEvent = {
6
6
  type: 'connected';
7
7
  userId: string;
@@ -60,6 +60,14 @@ export declare class KakaoTalkClient {
60
60
  search?: string;
61
61
  resolveTitles?: boolean;
62
62
  }): Promise<KakaoChat[]>;
63
+ /**
64
+ * Fetch one chat directly by id via read-only CHATINFO.
65
+ *
66
+ * Unlike `getChats()`, this does not depend on the login-time snapshot or
67
+ * LCHATLIST pagination, so a room first observed from a live push can be
68
+ * resolved without interpreting protocol fields outside this SDK.
69
+ */
70
+ getChat(chatId: string): Promise<KakaoChat>;
63
71
  /**
64
72
  * Resolve the user-set room title via CHATINFO. Returns null on any error
65
73
  * (network, malformed response, or no TITLE meta present). Designed to be
@@ -67,10 +75,22 @@ export declare class KakaoTalkClient {
67
75
  */
68
76
  getChatTitle(chatId: string): Promise<string | null>;
69
77
  private fetchChatTitle;
78
+ /**
79
+ * Fetch one forward-only, lossless history page after `from`.
80
+ *
81
+ * The page never keeps only the newest `count` entries. If the protocol
82
+ * returns more entries than requested, the oldest entries are returned and
83
+ * `complete` remains false so callers can continue from `next_cursor`.
84
+ */
85
+ getMessagePage(chatId: string, options?: {
86
+ count?: number;
87
+ from?: string;
88
+ }): Promise<KakaoMessagePage>;
70
89
  getMessages(chatId: string, options?: {
71
90
  count?: number;
72
91
  from?: string;
73
92
  }): Promise<KakaoMessage[]>;
93
+ getMemberSnapshot(chatId: string): Promise<KakaoMemberSnapshot>;
74
94
  getMembers(chatId: string): Promise<KakaoMember[]>;
75
95
  getMembersByIds(chatId: string, userIds: string[]): Promise<KakaoMember[]>;
76
96
  sendMessage(chatId: string, text: string, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { Long } from 'bson';
5
5
  import { getConfigDir } from '../../shared/utils/config-dir.js';
6
6
  import { warn } from '../../shared/utils/stderr.js';
7
7
  import { planAttachments } from './attachment-router.js';
8
+ import { isOpenKakaoChatType } from './chat-classifier.js';
8
9
  import { detectImageDimensions } from './image-meta.js';
9
10
  import { sha1Hex } from './media-upload.js';
10
11
  import { LANG, PC_OS_NAME, getLocoDeviceConfig } from './protocol/config.js';
@@ -166,6 +167,64 @@ function extractTitle(body) {
166
167
  const content = titleMeta?.content;
167
168
  return typeof content === 'string' && content.length > 0 ? content : null;
168
169
  }
170
+ function extractChannelInfo(body, expectedChatId) {
171
+ const info = body.chatInfo;
172
+ if (!info || typeof info !== 'object') {
173
+ throw new Error('CHATINFO response missing chatInfo');
174
+ }
175
+ const responseChatId = longToString(info.chatId);
176
+ if (responseChatId !== expectedChatId) {
177
+ throw new Error('CHATINFO response chatId mismatch');
178
+ }
179
+ return info;
180
+ }
181
+ function requiredNonNegativeInteger(value, field) {
182
+ if (!Number.isInteger(value) || value < 0) {
183
+ throw new Error(`CHATINFO response invalid ${field}`);
184
+ }
185
+ return value;
186
+ }
187
+ function requiredChatType(value) {
188
+ if (typeof value === 'string' && isOpenKakaoChatType(value)) {
189
+ return value;
190
+ }
191
+ return requiredNonNegativeInteger(value, 'type');
192
+ }
193
+ function channelInfoToChatData(body, expectedChatId) {
194
+ const info = extractChannelInfo(body, expectedChatId);
195
+ const type = requiredChatType(info.type);
196
+ const activeMembers = requiredNonNegativeInteger(info.activeMembersCount, 'activeMembersCount');
197
+ const unreadCount = info.invalidNewMessageCount === true ? 0 : requiredNonNegativeInteger(info.newMessageCount, 'newMessageCount');
198
+ const displayMembers = Array.isArray(info.displayMembers) ? info.displayMembers : [];
199
+ const memberIds = [];
200
+ const memberNames = [];
201
+ for (const member of displayMembers) {
202
+ if (!member || typeof member !== 'object')
203
+ continue;
204
+ if (member.userId === undefined || typeof member.nickName !== 'string')
205
+ continue;
206
+ memberIds.push(member.userId);
207
+ memberNames.push(member.nickName);
208
+ }
209
+ return {
210
+ c: info.chatId,
211
+ t: type,
212
+ a: activeMembers,
213
+ n: unreadCount,
214
+ l: info.lastChatLog ?? null,
215
+ ll: info.lastLogId,
216
+ i: memberIds,
217
+ k: memberNames,
218
+ li: info.li,
219
+ };
220
+ }
221
+ function extractChannelMaxLogId(body, expectedChatId) {
222
+ const info = extractChannelInfo(body, expectedChatId);
223
+ const maxLogId = bsonToLong(info.lastLogId);
224
+ if (!maxLogId)
225
+ throw new Error('CHATINFO response missing lastLogId');
226
+ return maxLogId;
227
+ }
169
228
  function extractOpenLinkName(body) {
170
229
  const ols = body.ols;
171
230
  if (!Array.isArray(ols) || ols.length === 0)
@@ -173,9 +232,8 @@ function extractOpenLinkName(body) {
173
232
  const ln = ols[0]?.ln;
174
233
  return typeof ln === 'string' && ln.length > 0 ? ln : null;
175
234
  }
176
- const OPEN_CHAT_TYPES = new Set(['OM', 'OD']);
177
235
  function isOpenChat(chat) {
178
- return typeof chat.t === 'string' && OPEN_CHAT_TYPES.has(chat.t);
236
+ return isOpenKakaoChatType(chat.t);
179
237
  }
180
238
  function getOpenLinkId(chat) {
181
239
  return bsonToLong(chat.li) ?? null;
@@ -359,10 +417,24 @@ function formatMember(member) {
359
417
  };
360
418
  }
361
419
  function memberIdKey(member) {
362
- if (member.userId === undefined || member.userId === null)
420
+ const value = member.userId;
421
+ if (typeof value === 'number') {
422
+ return Number.isSafeInteger(value) && value > 0 ? String(value) : null;
423
+ }
424
+ if (!value || typeof value !== 'object' || !('low' in value) || !('high' in value)) {
363
425
  return null;
364
- const key = longToString(member.userId);
365
- return key === '0' ? null : key;
426
+ }
427
+ const { low, high } = value;
428
+ if (!Number.isInteger(low) ||
429
+ !Number.isInteger(high) ||
430
+ low < -0x80000000 ||
431
+ low > 0xffffffff ||
432
+ high < -0x80000000 ||
433
+ high > 0xffffffff) {
434
+ return null;
435
+ }
436
+ const numeric = (BigInt(high >>> 0) << 32n) | BigInt(low >>> 0);
437
+ return numeric > 0n && numeric <= BigInt(Number.MAX_SAFE_INTEGER) ? numeric.toString() : null;
366
438
  }
367
439
  function mergeMemberRecords(primaryMembers, fallbackMembers) {
368
440
  const merged = [];
@@ -385,18 +457,43 @@ function mergeMemberRecords(primaryMembers, fallbackMembers) {
385
457
  upsert(member);
386
458
  return merged;
387
459
  }
388
- function extractChatInfoMembers(body) {
389
- if (!body || typeof body !== 'object')
390
- return [];
391
- const record = body;
392
- if (Array.isArray(record.m)) {
393
- return record.m;
394
- }
395
- const chatInfo = record.chatInfo;
396
- if (!chatInfo || typeof chatInfo !== 'object')
397
- return [];
398
- const displayMembers = chatInfo.displayMembers;
399
- return Array.isArray(displayMembers) ? displayMembers : [];
460
+ function exactMemberRecords(value, source) {
461
+ if (!Array.isArray(value)) {
462
+ throw new Error(`${source} member snapshot missing`);
463
+ }
464
+ const records = [];
465
+ const ids = new Set();
466
+ for (const raw of value) {
467
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
468
+ throw new Error(`${source} member snapshot invalid`);
469
+ }
470
+ const record = raw;
471
+ const id = memberIdKey(record);
472
+ if (!id || ids.has(id)) {
473
+ throw new Error(`${source} member snapshot invalid`);
474
+ }
475
+ ids.add(id);
476
+ records.push(record);
477
+ }
478
+ return { records, ids };
479
+ }
480
+ function sameIds(left, right) {
481
+ return left.size === right.size && [...left].every((id) => right.has(id));
482
+ }
483
+ function subsetIds(subset, superset) {
484
+ return [...subset].every((id) => superset.has(id));
485
+ }
486
+ function channelMemberView(body, expectedChatId) {
487
+ const info = extractChannelInfo(body, expectedChatId);
488
+ const activeMembers = requiredNonNegativeInteger(info.activeMembersCount, 'activeMembersCount');
489
+ const displayMembers = exactMemberRecords(info.displayMembers ?? [], 'CHATINFO');
490
+ if (displayMembers.records.length > activeMembers) {
491
+ throw new Error('CHATINFO member snapshot invalid');
492
+ }
493
+ return {
494
+ activeMembers,
495
+ ...displayMembers,
496
+ };
400
497
  }
401
498
  function formatMessages(logs, count, chatId, nameCache) {
402
499
  logs.sort((a, b) => a.sendAt - b.sendAt);
@@ -410,6 +507,40 @@ function formatMessages(logs, count, chatId, nameCache) {
410
507
  sent_at: log.sendAt,
411
508
  }));
412
509
  }
510
+ function formatForwardMessage(log, chatId, nameCache) {
511
+ return {
512
+ log_id: longToString(log.logId),
513
+ type: log.type,
514
+ author_id: log.authorId,
515
+ author_name: nameCache.lookup(chatId, log.authorId),
516
+ message: log.message,
517
+ attachment: parseAttachmentJson(log.attachment),
518
+ sent_at: log.sendAt,
519
+ };
520
+ }
521
+ function buildMessagePage(logs, count, cursor, protocolComplete, chatId, nameCache) {
522
+ const cursorValue = BigInt(cursor.toString());
523
+ const unique = new Map();
524
+ for (const log of logs) {
525
+ const logId = longToString(log.logId);
526
+ if (BigInt(logId) <= cursorValue)
527
+ continue;
528
+ if (!unique.has(logId))
529
+ unique.set(logId, log);
530
+ }
531
+ const ordered = [...unique.entries()].sort(([left], [right]) => {
532
+ const a = BigInt(left);
533
+ const b = BigInt(right);
534
+ return a < b ? -1 : a > b ? 1 : 0;
535
+ });
536
+ const selected = ordered.slice(0, count);
537
+ const nextCursor = selected.at(-1)?.[0] ?? null;
538
+ return {
539
+ messages: selected.map(([, log]) => formatForwardMessage(log, chatId, nameCache)),
540
+ next_cursor: nextCursor,
541
+ complete: protocolComplete && ordered.length <= count,
542
+ };
543
+ }
413
544
  function buildReplyExtra(target) {
414
545
  return {
415
546
  attach_only: false,
@@ -672,6 +803,43 @@ export class KakaoTalkClient {
672
803
  }
673
804
  });
674
805
  }
806
+ /**
807
+ * Fetch one chat directly by id via read-only CHATINFO.
808
+ *
809
+ * Unlike `getChats()`, this does not depend on the login-time snapshot or
810
+ * LCHATLIST pagination, so a room first observed from a live push can be
811
+ * resolved without interpreting protocol fields outside this SDK.
812
+ */
813
+ async getChat(chatId) {
814
+ const parsedChatId = parseChatId(chatId);
815
+ const normalizedChatId = longToString(parsedChatId);
816
+ return this.executeWithReconnect(async ({ session }) => {
817
+ try {
818
+ const response = await session.getChannelInfo(parsedChatId);
819
+ assertLocoOk(response, 'CHATINFO');
820
+ const body = response.body;
821
+ const chat = channelInfoToChatData(body, normalizedChatId);
822
+ this.nameCache.ingest([chat]);
823
+ let title = extractTitle(body);
824
+ if (!title && isOpenChat(chat)) {
825
+ const linkId = getOpenLinkId(chat);
826
+ if (linkId) {
827
+ try {
828
+ const openLinkResponse = await session.getOpenLinkInfo([linkId]);
829
+ title = extractOpenLinkName(openLinkResponse.body);
830
+ }
831
+ catch {
832
+ title = null;
833
+ }
834
+ }
835
+ }
836
+ return formatChat(chat, title, this.nameCache);
837
+ }
838
+ catch (error) {
839
+ throw wrapError(error, 'get_chat_failed');
840
+ }
841
+ });
842
+ }
675
843
  /**
676
844
  * Resolve the user-set room title via CHATINFO. Returns null on any error
677
845
  * (network, malformed response, or no TITLE meta present). Designed to be
@@ -714,6 +882,50 @@ export class KakaoTalkClient {
714
882
  return null;
715
883
  }
716
884
  }
885
+ /**
886
+ * Fetch one forward-only, lossless history page after `from`.
887
+ *
888
+ * The page never keeps only the newest `count` entries. If the protocol
889
+ * returns more entries than requested, the oldest entries are returned and
890
+ * `complete` remains false so callers can continue from `next_cursor`.
891
+ */
892
+ async getMessagePage(chatId, options) {
893
+ const count = options?.count ?? 100;
894
+ if (!Number.isInteger(count) || count < 1 || count > 100) {
895
+ throw new KakaoTalkError('Message page count must be an integer between 1 and 100', 'invalid_message_page_count');
896
+ }
897
+ const parsedChatId = parseChatId(chatId);
898
+ const normalizedChatId = longToString(parsedChatId);
899
+ const cursor = options?.from ? parseLogId(options.from) : Long.fromNumber(0);
900
+ return this.executeWithReconnect(async ({ session }) => {
901
+ try {
902
+ const response = await session.getChatLogs([parsedChatId], [cursor]);
903
+ assertLocoOk(response, 'MCHATLOGS');
904
+ const batch = (response.body.chatLogs ?? []).filter((log) => longToString(log.chatId) === normalizedChatId);
905
+ if (batch.length > 0) {
906
+ return buildMessagePage(batch, count, cursor, response.body.eof === true, normalizedChatId, this.nameCache);
907
+ }
908
+ // CHATINFO is read-only and supplies a fresh lastLogId even for rooms
909
+ // absent from the login snapshot. This avoids CHATONROOM, which can
910
+ // advance server-side unread state.
911
+ const chatInfoResponse = await session.getChannelInfo(parsedChatId);
912
+ assertLocoOk(chatInfoResponse, 'CHATINFO');
913
+ const maxLogId = extractChannelMaxLogId(chatInfoResponse.body, normalizedChatId);
914
+ if (maxLogId.lessThanOrEqual(cursor)) {
915
+ return { messages: [], next_cursor: null, complete: true };
916
+ }
917
+ const syncResponse = await session.syncMessages(parsedChatId, Math.min(count, 80), cursor, maxLogId);
918
+ assertLocoOk(syncResponse, 'SYNCMSG');
919
+ const syncBatch = (syncResponse.body.chatLogs ?? []).filter((log) => longToString(log.chatId) === normalizedChatId);
920
+ const maxReturned = findMaxLogId(syncBatch, 'logId');
921
+ const protocolComplete = syncResponse.body.isOK === true || (maxReturned !== null && maxReturned.greaterThanOrEqual(maxLogId));
922
+ return buildMessagePage(syncBatch, count, cursor, protocolComplete, normalizedChatId, this.nameCache);
923
+ }
924
+ catch (error) {
925
+ throw wrapError(error, 'get_message_page_failed');
926
+ }
927
+ });
928
+ }
717
929
  async getMessages(chatId, options) {
718
930
  return this.executeWithReconnect(async ({ session, loginResult }) => {
719
931
  try {
@@ -798,30 +1010,58 @@ export class KakaoTalkClient {
798
1010
  }
799
1011
  });
800
1012
  }
801
- async getMembers(chatId) {
1013
+ async getMemberSnapshot(chatId) {
802
1014
  const parsedChatId = parseChatId(chatId);
1015
+ const normalizedChatId = longToString(parsedChatId);
803
1016
  return this.executeWithReconnect(async ({ session }) => {
804
1017
  try {
805
- const response = await session.getAllMembers(parsedChatId);
806
- assertLocoOk(response, 'GETMEM');
807
- const members = (response.body.members ?? []);
808
- let fallbackMembers = [];
809
- try {
810
- // Some KakaoTalk rooms return only a subset from GETMEM even though
811
- // CHATONROOM carries the full active member list in `m`.
812
- const chatInfo = await session.getChatInfo(parsedChatId);
813
- fallbackMembers = extractChatInfoMembers(chatInfo.body);
814
- }
815
- catch {
816
- fallbackMembers = [];
1018
+ const firstChatResponse = await session.getChannelInfo(parsedChatId);
1019
+ assertLocoOk(firstChatResponse, 'CHATINFO');
1020
+ const firstChat = channelMemberView(firstChatResponse.body, normalizedChatId);
1021
+ const firstMemberResponse = await session.getAllMembers(parsedChatId);
1022
+ assertLocoOk(firstMemberResponse, 'GETMEM');
1023
+ const firstMembers = exactMemberRecords(firstMemberResponse.body.members ?? [], 'GETMEM');
1024
+ const secondChatResponse = await session.getChannelInfo(parsedChatId);
1025
+ assertLocoOk(secondChatResponse, 'CHATINFO');
1026
+ const secondChat = channelMemberView(secondChatResponse.body, normalizedChatId);
1027
+ const secondMemberResponse = await session.getAllMembers(parsedChatId);
1028
+ assertLocoOk(secondMemberResponse, 'GETMEM');
1029
+ const secondMembers = exactMemberRecords(secondMemberResponse.body.members ?? [], 'GETMEM');
1030
+ if (firstChat.activeMembers !== secondChat.activeMembers ||
1031
+ firstMembers.records.length !== firstChat.activeMembers ||
1032
+ secondMembers.records.length !== secondChat.activeMembers ||
1033
+ !sameIds(firstChat.ids, secondChat.ids) ||
1034
+ !sameIds(firstMembers.ids, secondMembers.ids) ||
1035
+ !subsetIds(secondChat.ids, secondMembers.ids)) {
1036
+ throw new Error('member_snapshot_changed_or_incomplete');
817
1037
  }
818
- return mergeMemberRecords(members, fallbackMembers).map(formatMember);
1038
+ return {
1039
+ chat_id: normalizedChatId,
1040
+ active_members: secondChat.activeMembers,
1041
+ members: mergeMemberRecords(secondMembers.records, secondChat.records).map(formatMember),
1042
+ complete: true,
1043
+ consistency_basis: 'stable_double_read_chatinfo_getmem',
1044
+ };
819
1045
  }
820
1046
  catch (error) {
821
- throw wrapError(error, 'get_members_failed');
1047
+ throw wrapError(error, 'get_member_snapshot_failed');
822
1048
  }
823
1049
  });
824
1050
  }
1051
+ async getMembers(chatId) {
1052
+ try {
1053
+ return (await this.getMemberSnapshot(chatId)).members;
1054
+ }
1055
+ catch (error) {
1056
+ if (error instanceof KakaoTalkError && error.code === 'get_member_snapshot_failed') {
1057
+ throw new KakaoTalkError(error.message, 'get_members_failed', {
1058
+ cause: error,
1059
+ serverStatus: error.serverStatus,
1060
+ });
1061
+ }
1062
+ throw error;
1063
+ }
1064
+ }
825
1065
  async getMembersByIds(chatId, userIds) {
826
1066
  if (userIds.length === 0)
827
1067
  return [];