agent-messenger 2.12.1 → 2.13.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 (87) 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 +18 -0
  4. package/dist/src/platforms/kakaotalk/chat-classifier.d.ts.map +1 -0
  5. package/dist/src/platforms/kakaotalk/chat-classifier.js +29 -0
  6. package/dist/src/platforms/kakaotalk/chat-classifier.js.map +1 -0
  7. package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
  8. package/dist/src/platforms/kakaotalk/cli.js +2 -1
  9. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  10. package/dist/src/platforms/kakaotalk/client.d.ts +35 -1
  11. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  12. package/dist/src/platforms/kakaotalk/client.js +318 -15
  13. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  14. package/dist/src/platforms/kakaotalk/commands/chat.d.ts.map +1 -1
  15. package/dist/src/platforms/kakaotalk/commands/chat.js +2 -1
  16. package/dist/src/platforms/kakaotalk/commands/chat.js.map +1 -1
  17. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
  18. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  19. package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
  20. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  21. package/dist/src/platforms/kakaotalk/commands/member.d.ts +3 -0
  22. package/dist/src/platforms/kakaotalk/commands/member.d.ts.map +1 -0
  23. package/dist/src/platforms/kakaotalk/commands/member.js +22 -0
  24. package/dist/src/platforms/kakaotalk/commands/member.js.map +1 -0
  25. package/dist/src/platforms/kakaotalk/index.d.ts +4 -2
  26. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  27. package/dist/src/platforms/kakaotalk/index.js +2 -1
  28. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  29. package/dist/src/platforms/kakaotalk/listener.d.ts +4 -7
  30. package/dist/src/platforms/kakaotalk/listener.d.ts.map +1 -1
  31. package/dist/src/platforms/kakaotalk/listener.js +48 -74
  32. package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
  33. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +28 -0
  34. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  35. package/dist/src/platforms/kakaotalk/protocol/session.js +44 -0
  36. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  37. package/dist/src/platforms/kakaotalk/types.d.ts +37 -0
  38. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  39. package/dist/src/platforms/kakaotalk/types.js +17 -0
  40. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  41. package/dist/src/platforms/slackbot/client.d.ts +5 -0
  42. package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
  43. package/dist/src/platforms/slackbot/client.js +5 -0
  44. package/dist/src/platforms/slackbot/client.js.map +1 -1
  45. package/dist/src/tui/adapters/kakaotalk-adapter.js +3 -3
  46. package/dist/src/tui/adapters/kakaotalk-adapter.js.map +1 -1
  47. package/docs/content/docs/cli/kakaotalk.mdx +26 -1
  48. package/docs/content/docs/sdk/kakaotalk.mdx +45 -13
  49. package/docs/content/docs/sdk/slackbot.mdx +11 -0
  50. package/package.json +1 -1
  51. package/scripts/kakao-loco-capture.ts +466 -0
  52. package/skills/agent-channeltalk/SKILL.md +1 -1
  53. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  54. package/skills/agent-discord/SKILL.md +1 -1
  55. package/skills/agent-discordbot/SKILL.md +1 -1
  56. package/skills/agent-instagram/SKILL.md +1 -1
  57. package/skills/agent-kakaotalk/SKILL.md +30 -3
  58. package/skills/agent-kakaotalk/references/common-patterns.md +49 -1
  59. package/skills/agent-line/SKILL.md +1 -1
  60. package/skills/agent-slack/SKILL.md +1 -1
  61. package/skills/agent-slackbot/SKILL.md +1 -2
  62. package/skills/agent-teams/SKILL.md +1 -1
  63. package/skills/agent-telegram/SKILL.md +1 -1
  64. package/skills/agent-telegrambot/SKILL.md +1 -1
  65. package/skills/agent-webex/SKILL.md +1 -1
  66. package/skills/agent-wechatbot/SKILL.md +1 -1
  67. package/skills/agent-whatsapp/SKILL.md +1 -1
  68. package/skills/agent-whatsappbot/SKILL.md +1 -1
  69. package/src/platforms/kakaotalk/chat-classifier.test.ts +33 -0
  70. package/src/platforms/kakaotalk/chat-classifier.ts +31 -0
  71. package/src/platforms/kakaotalk/cli.ts +2 -1
  72. package/src/platforms/kakaotalk/client-listener-integration.test.ts +411 -0
  73. package/src/platforms/kakaotalk/client.test.ts +785 -1
  74. package/src/platforms/kakaotalk/client.ts +369 -18
  75. package/src/platforms/kakaotalk/commands/chat.ts +3 -1
  76. package/src/platforms/kakaotalk/commands/index.ts +1 -0
  77. package/src/platforms/kakaotalk/commands/member.test.ts +102 -0
  78. package/src/platforms/kakaotalk/commands/member.ts +32 -0
  79. package/src/platforms/kakaotalk/index.test.ts +5 -0
  80. package/src/platforms/kakaotalk/index.ts +4 -0
  81. package/src/platforms/kakaotalk/listener.test.ts +184 -149
  82. package/src/platforms/kakaotalk/listener.ts +51 -82
  83. package/src/platforms/kakaotalk/protocol/session.ts +44 -0
  84. package/src/platforms/kakaotalk/types.ts +39 -0
  85. package/src/platforms/slackbot/client.test.ts +67 -0
  86. package/src/platforms/slackbot/client.ts +17 -1
  87. package/src/tui/adapters/kakaotalk-adapter.ts +3 -3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.12.1",
3
+ "version": "2.13.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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "2.12.1",
3
+ "version": "2.13.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",
@@ -0,0 +1,18 @@
1
+ import type { KakaoChat } from './types.js';
2
+ export type KakaoChatKind = 'dm' | 'group' | 'open' | 'unknown';
3
+ /**
4
+ * Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
5
+ *
6
+ * REGRESSION GUARD: An earlier implementation hard-coded `0=dm`, `1=group`,
7
+ * `2=open` on the raw `type` number. Modern KakaoTalk uses codes like `11`
8
+ * for normal DMs and `10` for normal groups, so the old mapping silently
9
+ * classified every real DM as `'unknown'` and bucketed it as a group. Do
10
+ * NOT "simplify" this back to a pure type-code mapping without verifying
11
+ * against a real KakaoTalk session.
12
+ *
13
+ * `'unknown'` is reserved for future protocol drift; the current heuristic
14
+ * never returns it, but it is part of the union so consumers can handle
15
+ * the case defensively.
16
+ */
17
+ export declare function classifyKakaoChat(chat: Pick<KakaoChat, 'type' | 'active_members'>): KakaoChatKind;
18
+ //# sourceMappingURL=chat-classifier.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,29 @@
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]);
6
+ /**
7
+ * Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
8
+ *
9
+ * REGRESSION GUARD: An earlier implementation hard-coded `0=dm`, `1=group`,
10
+ * `2=open` on the raw `type` number. Modern KakaoTalk uses codes like `11`
11
+ * for normal DMs and `10` for normal groups, so the old mapping silently
12
+ * classified every real DM as `'unknown'` and bucketed it as a group. Do
13
+ * NOT "simplify" this back to a pure type-code mapping without verifying
14
+ * against a real KakaoTalk session.
15
+ *
16
+ * `'unknown'` is reserved for future protocol drift; the current heuristic
17
+ * never returns it, but it is part of the union so consumers can handle
18
+ * the case defensively.
19
+ */
20
+ export function classifyKakaoChat(chat) {
21
+ if (OPEN_CHAT_TYPE_CODES.has(chat.type))
22
+ return 'open';
23
+ // active_members counts the logged-in user, so a 1:1 DM is exactly 2
24
+ // (self + one other) and a "lone" room with only self is 1.
25
+ if (chat.active_members <= 2)
26
+ return 'dm';
27
+ return 'group';
28
+ }
29
+ //# sourceMappingURL=chat-classifier.js.map
@@ -0,0 +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 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAgBvD,QAAA,MAAM,OAAO,aAAgB,CAAA;AAmB7B,eAAe,OAAO,CAAA"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAgBvD,QAAA,MAAM,OAAO,aAAgB,CAAA;AAoB7B,eAAe,OAAO,CAAA"}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import { Command } from 'commander';
3
3
  import pkg from '../../../package.json' with { type: 'json' };
4
- import { authCommand, chatCommand, messageCommand, whoamiCommand } from './commands/index.js';
4
+ import { authCommand, chatCommand, memberCommand, messageCommand, whoamiCommand } from './commands/index.js';
5
5
  import { ensureKakaoAuth } from './ensure-auth.js';
6
6
  function isAuthCommand(command) {
7
7
  let cmd = command;
@@ -24,6 +24,7 @@ program.hook('preAction', async (_thisCommand, actionCommand) => {
24
24
  });
25
25
  program.addCommand(authCommand);
26
26
  program.addCommand(chatCommand);
27
+ program.addCommand(memberCommand);
27
28
  program.addCommand(messageCommand);
28
29
  program.addCommand(whoamiCommand);
29
30
  program.parse(process.argv);
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,GAAG,MAAM,uBAAuB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC1F,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE/C,SAAS,aAAa,CAAC,OAAoB;IACzC,IAAI,GAAG,GAAuB,OAAO,CAAA;IACrC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,MAAM;YAAE,OAAO,IAAI,CAAA;QACtC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAA;IAClB,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,iBAAiB,CAAC;KACvB,WAAW,CAAC,8EAA8E,CAAC;KAC3F,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AAEvB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE;IAC9D,IAAI,aAAa,CAAC,aAAa,CAAC;QAAE,OAAM;IACxC,MAAM,eAAe,EAAE,CAAA;AACzB,CAAC,CAAC,CAAA;AAEF,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAA;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AAEjC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAE3B,eAAe,OAAO,CAAA"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,GAAG,MAAM,uBAAuB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACzG,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE/C,SAAS,aAAa,CAAC,OAAoB;IACzC,IAAI,GAAG,GAAuB,OAAO,CAAA;IACrC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,MAAM;YAAE,OAAO,IAAI,CAAA;QACtC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAA;IAClB,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,iBAAiB,CAAC;KACvB,WAAW,CAAC,8EAA8E,CAAC;KAC3F,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AAEvB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE;IAC9D,IAAI,aAAa,CAAC,aAAa,CAAC;QAAE,OAAM;IACxC,MAAM,eAAe,EAAE,CAAA;AACzB,CAAC,CAAC,CAAA;AAEF,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AACjC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAA;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AAEjC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAE3B,eAAe,OAAO,CAAA"}
@@ -1,4 +1,17 @@
1
- import type { KakaoChat, KakaoDeviceType, KakaoMessage, KakaoProfile, KakaoSendResult } from './types.js';
1
+ import { LocoSession } from './protocol/session.js';
2
+ import type { LocoPacket } from './protocol/types.js';
3
+ import type { KakaoChat, KakaoDeviceType, KakaoMember, KakaoMessage, KakaoProfile, KakaoSendResult } from './types.js';
4
+ export type KakaoSessionEvent = {
5
+ type: 'connected';
6
+ userId: string;
7
+ } | {
8
+ type: 'disconnected';
9
+ } | {
10
+ type: 'kicked';
11
+ reason: string;
12
+ };
13
+ export type KakaoPushHandler = (packet: LocoPacket) => void;
14
+ export type KakaoSessionEventHandler = (event: KakaoSessionEvent) => void;
2
15
  export declare class KakaoTalkError extends Error {
3
16
  code: string;
4
17
  constructor(message: string, code: string, options?: {
@@ -13,6 +26,9 @@ export declare class KakaoTalkClient {
13
26
  private state;
14
27
  private initPromise;
15
28
  private closed;
29
+ private pushHandlers;
30
+ private sessionEventHandlers;
31
+ private nameCache;
16
32
  login(credentials?: {
17
33
  oauthToken: string;
18
34
  userId: string;
@@ -27,18 +43,36 @@ export declare class KakaoTalkClient {
27
43
  };
28
44
  private ensureAuth;
29
45
  private ensureSession;
46
+ acquireSession(): Promise<LocoSession>;
47
+ onPush(handler: KakaoPushHandler): () => void;
48
+ onSessionEvent(handler: KakaoSessionEventHandler): () => void;
49
+ isConnected(): boolean;
30
50
  private executeWithReconnect;
31
51
  private connect;
52
+ private dispatchPush;
53
+ private invalidateSession;
54
+ private emitSessionEvent;
32
55
  getChats(options?: {
33
56
  all?: boolean;
34
57
  search?: string;
58
+ resolveTitles?: boolean;
35
59
  }): Promise<KakaoChat[]>;
60
+ /**
61
+ * Resolve the user-set room title via CHATINFO. Returns null on any error
62
+ * (network, malformed response, or no TITLE meta present). Designed to be
63
+ * fire-and-forget per chat — failures don't poison the whole `getChats` call.
64
+ */
65
+ getChatTitle(chatId: string): Promise<string | null>;
66
+ private fetchChatTitle;
36
67
  getMessages(chatId: string, options?: {
37
68
  count?: number;
38
69
  from?: string;
39
70
  }): Promise<KakaoMessage[]>;
71
+ getMembers(chatId: string): Promise<KakaoMember[]>;
72
+ getMembersByIds(chatId: string, userIds: string[]): Promise<KakaoMember[]>;
40
73
  sendMessage(chatId: string, text: string): Promise<KakaoSendResult>;
41
74
  getProfile(): Promise<KakaoProfile>;
42
75
  close(): void;
76
+ lookupAuthorName(chatId: string, authorId: number): string | null;
43
77
  }
44
78
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEtG,qBAAa,cAAe,SAAQ,KAAK;IACvC,IAAI,EAAE,MAAM,CAAA;gBAEA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAKzE;AA8MD,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,WAAW,CAAqC;IACxD,OAAO,CAAC,MAAM,CAAQ;IAEhB,KAAK,CACT,WAAW,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,eAAe,CAAA;KAAE,EACvG,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAoBhB,cAAc,IAAI;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,eAAe,CAAA;KAAE;IAUzG,OAAO,CAAC,UAAU;YAMJ,aAAa;YA2Bb,oBAAoB;YAmBpB,OAAO;IA6Bf,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAqD5E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA8FjG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAkBnE,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC;IA6DzC,KAAK,IAAI,IAAI;CAUd"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,53 @@ export class KakaoTalkError extends Error {
14
14
  this.code = code;
15
15
  }
16
16
  }
17
+ class MemberNameCache {
18
+ byChatId = new Map();
19
+ ingest(chatDatas) {
20
+ for (const chat of chatDatas) {
21
+ const ids = chat.i;
22
+ const names = chat.k;
23
+ if (!Array.isArray(ids) || !Array.isArray(names))
24
+ continue;
25
+ const chatId = String(chat.c);
26
+ let map = this.byChatId.get(chatId);
27
+ if (!map) {
28
+ map = new Map();
29
+ this.byChatId.set(chatId, map);
30
+ }
31
+ const len = Math.min(ids.length, names.length);
32
+ for (let i = 0; i < len; i++) {
33
+ const numericId = toNumericUserId(ids[i]);
34
+ if (numericId === null)
35
+ continue;
36
+ const name = names[i];
37
+ if (typeof name === 'string' && name.length > 0) {
38
+ map.set(numericId, name);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ lookup(chatId, userId) {
44
+ return this.byChatId.get(chatId)?.get(userId) ?? null;
45
+ }
46
+ forget(chatId) {
47
+ this.byChatId.delete(chatId);
48
+ }
49
+ clear() {
50
+ this.byChatId.clear();
51
+ }
52
+ }
53
+ function toNumericUserId(v) {
54
+ if (typeof v === 'number')
55
+ return Number.isFinite(v) ? v : null;
56
+ if (v && typeof v === 'object' && 'low' in v && 'high' in v) {
57
+ const { low, high } = v;
58
+ // chatDatas[].i entries are member user IDs. KakaoTalk user IDs fit in
59
+ // 53 bits — safe to flatten the BSON Long pair to a JS number for keying.
60
+ return (high >>> 0) * 0x100000000 + (low >>> 0);
61
+ }
62
+ return null;
63
+ }
17
64
  function bsonToLong(v) {
18
65
  if (v && typeof v === 'object' && 'high' in v && 'low' in v) {
19
66
  const { high, low } = v;
@@ -34,25 +81,68 @@ function parseLong(s) {
34
81
  const high = Number((big >> 32n) & 0xffffffffn);
35
82
  return new Long(low, high);
36
83
  }
37
- function formatChat(chat) {
84
+ function parseChatId(chatId) {
85
+ try {
86
+ return parseLong(chatId);
87
+ }
88
+ catch (cause) {
89
+ throw new KakaoTalkError(`Invalid chatId: ${chatId}`, 'invalid_chat_id', { cause });
90
+ }
91
+ }
92
+ function parseUserId(userId) {
93
+ try {
94
+ return parseLong(userId);
95
+ }
96
+ catch (cause) {
97
+ throw new KakaoTalkError(`Invalid userId: ${userId}`, 'invalid_user_id', { cause });
98
+ }
99
+ }
100
+ function formatChat(chat, title, nameCache) {
38
101
  const memberNames = (chat.k ?? []);
39
102
  const lastLog = chat.l;
40
103
  const displayName = memberNames.join(', ') || null;
104
+ const chatId = String(chat.c);
41
105
  return {
42
- chat_id: String(chat.c),
106
+ chat_id: chatId,
43
107
  type: chat.t,
44
108
  display_name: displayName,
109
+ title,
45
110
  active_members: chat.a,
46
111
  unread_count: chat.n,
47
112
  last_message: lastLog
48
113
  ? {
49
114
  author_id: lastLog.authorId,
115
+ author_name: nameCache.lookup(chatId, lastLog.authorId),
50
116
  message: lastLog.message,
51
117
  sent_at: lastLog.sendAt,
52
118
  }
53
119
  : null,
54
120
  };
55
121
  }
122
+ const META_TYPE_TITLE = 3;
123
+ function extractTitle(body) {
124
+ const info = body.chatInfo;
125
+ const metas = info?.chatMetas;
126
+ if (!Array.isArray(metas))
127
+ return null;
128
+ const titleMeta = metas.find((m) => m?.type === META_TYPE_TITLE);
129
+ const content = titleMeta?.content;
130
+ return typeof content === 'string' && content.length > 0 ? content : null;
131
+ }
132
+ function extractOpenLinkName(body) {
133
+ const ols = body.ols;
134
+ if (!Array.isArray(ols) || ols.length === 0)
135
+ return null;
136
+ const ln = ols[0]?.ln;
137
+ return typeof ln === 'string' && ln.length > 0 ? ln : null;
138
+ }
139
+ const OPEN_CHAT_TYPES = new Set(['OM', 'OD']);
140
+ function isOpenChat(chat) {
141
+ return typeof chat.t === 'string' && OPEN_CHAT_TYPES.has(chat.t);
142
+ }
143
+ function getOpenLinkId(chat) {
144
+ return bsonToLong(chat.li) ?? null;
145
+ }
56
146
  function matchesSearch(chat, term) {
57
147
  const names = (chat.k ?? []);
58
148
  const lower = term.toLowerCase();
@@ -174,12 +264,56 @@ function mergeSyncState(previous, loginResult) {
174
264
  }
175
265
  return next;
176
266
  }
177
- function formatMessages(logs, count) {
267
+ function nullableString(v) {
268
+ return typeof v === 'string' && v.length > 0 ? v : null;
269
+ }
270
+ function nullableNumber(v) {
271
+ return typeof v === 'number' && Number.isFinite(v) ? v : null;
272
+ }
273
+ function isNonZeroLong(v) {
274
+ if (typeof v === 'number')
275
+ return v !== 0;
276
+ if (v && typeof v === 'object' && 'low' in v && 'high' in v) {
277
+ const { low, high } = v;
278
+ return low !== 0 || high !== 0;
279
+ }
280
+ return v !== undefined && v !== null;
281
+ }
282
+ // Reject synthetic LocoConnection close packets ({ statusCode: -1, body.error: 'connection closed' })
283
+ // and explicit body-level failures. Required for any SDK method whose response body has no
284
+ // caller-visible error channel (e.g. GETMEM/MEMBER return `[]` for both empty rooms and dead
285
+ // sockets). Throwing here lets executeWithReconnect detect session death and reconnect.
286
+ function assertLocoOk(response, command) {
287
+ if (response.statusCode !== 0) {
288
+ throw new Error(`${command} failed: statusCode=${response.statusCode}`);
289
+ }
290
+ const bodyStatus = response.body.status;
291
+ if (typeof bodyStatus === 'number' && bodyStatus !== 0) {
292
+ throw new Error(`${command} failed: body.status=${bodyStatus}`);
293
+ }
294
+ }
295
+ function formatMember(member) {
296
+ return {
297
+ user_id: longToString(member.userId),
298
+ nickname: typeof member.nickName === 'string' ? member.nickName : '',
299
+ profile_image_url: nullableString(member.profileImageUrl ?? member.pi),
300
+ full_profile_image_url: nullableString(member.fullProfileImageUrl ?? member.fpi),
301
+ original_profile_image_url: nullableString(member.originalProfileImageUrl ?? member.opi),
302
+ status_message: nullableString(member.statusMessage),
303
+ country_iso: nullableString(member.countryIso),
304
+ user_type: nullableNumber(member.type),
305
+ open_token: nullableNumber(member.opt),
306
+ open_profile_link_id: isNonZeroLong(member.pli) ? longToString(member.pli) : null,
307
+ open_permission: nullableNumber(member.mt),
308
+ };
309
+ }
310
+ function formatMessages(logs, count, chatId, nameCache) {
178
311
  logs.sort((a, b) => a.sendAt - b.sendAt);
179
312
  return logs.slice(-count).map((log) => ({
180
313
  log_id: longToString(log.logId),
181
314
  type: log.type,
182
315
  author_id: log.authorId,
316
+ author_name: nameCache.lookup(chatId, log.authorId),
183
317
  message: log.message,
184
318
  sent_at: log.sendAt,
185
319
  }));
@@ -192,6 +326,9 @@ export class KakaoTalkClient {
192
326
  state = null;
193
327
  initPromise = null;
194
328
  closed = false;
329
+ pushHandlers = new Set();
330
+ sessionEventHandlers = new Set();
331
+ nameCache = new MemberNameCache();
195
332
  async login(credentials, accountId) {
196
333
  if (credentials) {
197
334
  if (!credentials.oauthToken)
@@ -234,6 +371,7 @@ export class KakaoTalkClient {
234
371
  if (this.state)
235
372
  return this.state;
236
373
  // Guard against concurrent init — reuse the in-flight promise
374
+ const isOwner = !this.initPromise;
237
375
  if (!this.initPromise) {
238
376
  this.initPromise = this.connect();
239
377
  }
@@ -244,7 +382,11 @@ export class KakaoTalkClient {
244
382
  state.session.close();
245
383
  throw new KakaoTalkError('Client is closed', 'client_closed');
246
384
  }
385
+ const wasNew = this.state !== state;
247
386
  this.state = state;
387
+ if (isOwner && wasNew) {
388
+ this.emitSessionEvent({ type: 'connected', userId: this.userId });
389
+ }
248
390
  return state;
249
391
  }
250
392
  catch (error) {
@@ -254,6 +396,25 @@ export class KakaoTalkClient {
254
396
  throw error;
255
397
  }
256
398
  }
399
+ async acquireSession() {
400
+ const state = await this.ensureSession();
401
+ return state.session;
402
+ }
403
+ onPush(handler) {
404
+ this.pushHandlers.add(handler);
405
+ return () => {
406
+ this.pushHandlers.delete(handler);
407
+ };
408
+ }
409
+ onSessionEvent(handler) {
410
+ this.sessionEventHandlers.add(handler);
411
+ return () => {
412
+ this.sessionEventHandlers.delete(handler);
413
+ };
414
+ }
415
+ isConnected() {
416
+ return this.state !== null && !this.closed;
417
+ }
257
418
  async executeWithReconnect(operation) {
258
419
  let state = await this.ensureSession();
259
420
  try {
@@ -269,24 +430,30 @@ export class KakaoTalkClient {
269
430
  state.session.close();
270
431
  }
271
432
  catch { }
272
- this.initPromise = null;
433
+ // initPromise is intentionally NOT cleared here: a concurrent caller may already
434
+ // be awaiting an in-flight replacement, and starting a parallel one would send a
435
+ // second LOGINLIST with the same duuid — re-introducing the very self-eviction
436
+ // this layer prevents. Lifecycle paths (onClose / invalidateSession) own that field.
273
437
  state = await this.ensureSession();
274
438
  return operation(state);
275
439
  }
276
440
  }
277
441
  async connect() {
278
442
  const session = new LocoSession();
443
+ session.onPush((packet) => this.dispatchPush(session, packet));
444
+ session.onClose(() => {
445
+ if (this.state?.session === session) {
446
+ this.state = null;
447
+ this.initPromise = null;
448
+ this.emitSessionEvent({ type: 'disconnected' });
449
+ }
450
+ });
279
451
  try {
280
452
  const syncState = await loadSyncState(this.deviceUuid);
281
453
  const loginResult = await session.login(this.oauthToken, this.userId, this.deviceUuid, syncState, this.deviceType);
282
454
  const newSyncState = mergeSyncState(syncState, loginResult);
283
455
  await saveSyncState(this.deviceUuid, newSyncState);
284
- session.onClose(() => {
285
- if (this.state?.session === session) {
286
- this.state = null;
287
- this.initPromise = null;
288
- }
289
- });
456
+ this.nameCache.ingest((loginResult.chatDatas ?? []));
290
457
  return { session, loginResult };
291
458
  }
292
459
  catch (error) {
@@ -294,6 +461,59 @@ export class KakaoTalkClient {
294
461
  throw new KakaoTalkError(error instanceof Error ? error.message : String(error), 'login_failed', { cause: error });
295
462
  }
296
463
  }
464
+ dispatchPush(session, packet) {
465
+ // Only fan out pushes from the currently adopted session. While state is null
466
+ // (pre-adoption during connect, or post-invalidation during reconnect) the
467
+ // packet is discarded — we never want a not-yet-adopted or already-dead session
468
+ // to reach subscribers and look "live".
469
+ if (this.state?.session !== session)
470
+ return;
471
+ if (packet.method === 'KICKOUT') {
472
+ this.emitSessionEvent({ type: 'kicked', reason: 'Session kicked — another device logged in' });
473
+ this.invalidateSession(session);
474
+ return;
475
+ }
476
+ if (packet.method === 'CHANGESVR') {
477
+ for (const handler of this.pushHandlers) {
478
+ try {
479
+ handler(packet);
480
+ }
481
+ catch { }
482
+ }
483
+ this.invalidateSession(session);
484
+ this.emitSessionEvent({ type: 'disconnected' });
485
+ this.ensureSession().catch(() => {
486
+ // ensureSession already cleared state on failure; subsequent API calls will retry
487
+ // and surface the error. Listeners do not receive 'connected' until a reconnect
488
+ // succeeds, which is the correct outcome.
489
+ });
490
+ return;
491
+ }
492
+ for (const handler of this.pushHandlers) {
493
+ try {
494
+ handler(packet);
495
+ }
496
+ catch { }
497
+ }
498
+ }
499
+ invalidateSession(session) {
500
+ if (this.state?.session === session) {
501
+ this.state = null;
502
+ this.initPromise = null;
503
+ }
504
+ try {
505
+ session.close();
506
+ }
507
+ catch { }
508
+ }
509
+ emitSessionEvent(event) {
510
+ for (const handler of this.sessionEventHandlers) {
511
+ try {
512
+ handler(event);
513
+ }
514
+ catch { }
515
+ }
516
+ }
297
517
  async getChats(options) {
298
518
  return this.executeWithReconnect(async ({ session, loginResult }) => {
299
519
  try {
@@ -324,6 +544,7 @@ export class KakaoTalkClient {
324
544
  if (chatDatas.length === 0)
325
545
  break;
326
546
  collectChats(chatDatas, allChats, seenChatIds);
547
+ this.nameCache.ingest(chatDatas);
327
548
  cursor = body;
328
549
  pages++;
329
550
  }
@@ -333,13 +554,58 @@ export class KakaoTalkClient {
333
554
  if (options?.search) {
334
555
  results = allChats.filter((c) => matchesSearch(c, options.search));
335
556
  }
336
- return results.map(formatChat);
557
+ const titles = options?.resolveTitles
558
+ ? await Promise.all(results.map((chat) => this.fetchChatTitle(session, parseLong(String(chat.c)), chat)))
559
+ : null;
560
+ return results.map((chat, i) => formatChat(chat, titles ? titles[i] : null, this.nameCache));
337
561
  }
338
562
  catch (error) {
339
563
  throw wrapError(error, 'get_chats_failed');
340
564
  }
341
565
  });
342
566
  }
567
+ /**
568
+ * Resolve the user-set room title via CHATINFO. Returns null on any error
569
+ * (network, malformed response, or no TITLE meta present). Designed to be
570
+ * fire-and-forget per chat — failures don't poison the whole `getChats` call.
571
+ */
572
+ async getChatTitle(chatId) {
573
+ let parsed;
574
+ try {
575
+ parsed = parseLong(chatId);
576
+ }
577
+ catch {
578
+ return null;
579
+ }
580
+ return this.executeWithReconnect(async ({ session, loginResult }) => {
581
+ const chat = (loginResult.chatDatas ?? []).find((c) => String(c.c) === chatId);
582
+ return this.fetchChatTitle(session, parsed, chat);
583
+ });
584
+ }
585
+ async fetchChatTitle(session, chatId, chat) {
586
+ let title = null;
587
+ try {
588
+ const response = await session.getChannelInfo(chatId);
589
+ title = extractTitle(response.body);
590
+ }
591
+ catch {
592
+ title = null;
593
+ }
594
+ if (title)
595
+ return title;
596
+ if (!chat || !isOpenChat(chat))
597
+ return null;
598
+ const linkId = getOpenLinkId(chat);
599
+ if (!linkId)
600
+ return null;
601
+ try {
602
+ const response = await session.getOpenLinkInfo([linkId]);
603
+ return extractOpenLinkName(response.body);
604
+ }
605
+ catch {
606
+ return null;
607
+ }
608
+ }
343
609
  async getMessages(chatId, options) {
344
610
  return this.executeWithReconnect(async ({ session }) => {
345
611
  try {
@@ -358,7 +624,7 @@ export class KakaoTalkClient {
358
624
  }
359
625
  const batch = (response.body.chatLogs ?? []).filter((log) => longToString(log.chatId) === chatId);
360
626
  if (batch.length === 0) {
361
- return formatMessages(allMessages, count);
627
+ return formatMessages(allMessages, count, chatId, this.nameCache);
362
628
  }
363
629
  for (const log of batch) {
364
630
  const lid = longToString(log.logId);
@@ -369,7 +635,7 @@ export class KakaoTalkClient {
369
635
  }
370
636
  const maxLog = findMaxLogId(batch, 'logId');
371
637
  if (!maxLog || maxLog.equals(cur) || response.body.eof) {
372
- return formatMessages(allMessages, count);
638
+ return formatMessages(allMessages, count, chatId, this.nameCache);
373
639
  }
374
640
  cur = maxLog;
375
641
  }
@@ -381,7 +647,7 @@ export class KakaoTalkClient {
381
647
  }
382
648
  if (allMessages.length > 0) {
383
649
  warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`);
384
- return formatMessages(allMessages, count);
650
+ return formatMessages(allMessages, count, chatId, this.nameCache);
385
651
  }
386
652
  // Fetch fresh lastLogId via CHATONROOM (not the stale login-time snapshot)
387
653
  const chatInfo = await session.getChatInfo(cid);
@@ -412,13 +678,44 @@ export class KakaoTalkClient {
412
678
  if (!reachedEnd) {
413
679
  warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`);
414
680
  }
415
- return formatMessages(allMessages, count);
681
+ return formatMessages(allMessages, count, chatId, this.nameCache);
416
682
  }
417
683
  catch (error) {
418
684
  throw wrapError(error, 'get_messages_failed');
419
685
  }
420
686
  });
421
687
  }
688
+ async getMembers(chatId) {
689
+ const parsedChatId = parseChatId(chatId);
690
+ return this.executeWithReconnect(async ({ session }) => {
691
+ try {
692
+ const response = await session.getAllMembers(parsedChatId);
693
+ assertLocoOk(response, 'GETMEM');
694
+ const members = (response.body.members ?? []);
695
+ return members.map(formatMember);
696
+ }
697
+ catch (error) {
698
+ throw wrapError(error, 'get_members_failed');
699
+ }
700
+ });
701
+ }
702
+ async getMembersByIds(chatId, userIds) {
703
+ if (userIds.length === 0)
704
+ return [];
705
+ const parsedChatId = parseChatId(chatId);
706
+ const memberIds = userIds.map((id) => parseUserId(id));
707
+ return this.executeWithReconnect(async ({ session }) => {
708
+ try {
709
+ const response = await session.getMembersByIds(parsedChatId, memberIds);
710
+ assertLocoOk(response, 'MEMBER');
711
+ const members = (response.body.members ?? []);
712
+ return members.map(formatMember);
713
+ }
714
+ catch (error) {
715
+ throw wrapError(error, 'get_members_failed');
716
+ }
717
+ });
718
+ }
422
719
  async sendMessage(chatId, text) {
423
720
  return this.executeWithReconnect(async ({ session }) => {
424
721
  try {
@@ -501,6 +798,12 @@ export class KakaoTalkClient {
501
798
  }
502
799
  this.state = null;
503
800
  this.initPromise = null;
801
+ this.pushHandlers.clear();
802
+ this.sessionEventHandlers.clear();
803
+ this.nameCache.clear();
804
+ }
805
+ lookupAuthorName(chatId, authorId) {
806
+ return this.nameCache.lookup(chatId, authorId);
504
807
  }
505
808
  }
506
809
  //# sourceMappingURL=client.js.map