agent-messenger 2.15.1 → 2.17.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 (78) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -1
  3. package/dist/package.json +1 -1
  4. package/dist/src/platforms/kakaotalk/attachment-router.d.ts +25 -0
  5. package/dist/src/platforms/kakaotalk/attachment-router.d.ts.map +1 -0
  6. package/dist/src/platforms/kakaotalk/attachment-router.js +29 -0
  7. package/dist/src/platforms/kakaotalk/attachment-router.js.map +1 -0
  8. package/dist/src/platforms/kakaotalk/client.d.ts +14 -1
  9. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  10. package/dist/src/platforms/kakaotalk/client.js +216 -0
  11. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  12. package/dist/src/platforms/kakaotalk/commands/message.d.ts.map +1 -1
  13. package/dist/src/platforms/kakaotalk/commands/message.js +49 -0
  14. package/dist/src/platforms/kakaotalk/commands/message.js.map +1 -1
  15. package/dist/src/platforms/kakaotalk/image-meta.d.ts +7 -0
  16. package/dist/src/platforms/kakaotalk/image-meta.d.ts.map +1 -0
  17. package/dist/src/platforms/kakaotalk/image-meta.js +153 -0
  18. package/dist/src/platforms/kakaotalk/image-meta.js.map +1 -0
  19. package/dist/src/platforms/kakaotalk/index.d.ts +8 -2
  20. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  21. package/dist/src/platforms/kakaotalk/index.js +5 -1
  22. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  23. package/dist/src/platforms/kakaotalk/media-upload.d.ts +3 -0
  24. package/dist/src/platforms/kakaotalk/media-upload.d.ts.map +1 -0
  25. package/dist/src/platforms/kakaotalk/media-upload.js +44 -0
  26. package/dist/src/platforms/kakaotalk/media-upload.js.map +1 -0
  27. package/dist/src/platforms/kakaotalk/protocol/connection.d.ts +1 -0
  28. package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
  29. package/dist/src/platforms/kakaotalk/protocol/connection.js +11 -0
  30. package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
  31. package/dist/src/platforms/kakaotalk/protocol/media-uploader.d.ts +25 -0
  32. package/dist/src/platforms/kakaotalk/protocol/media-uploader.d.ts.map +1 -0
  33. package/dist/src/platforms/kakaotalk/protocol/media-uploader.js +99 -0
  34. package/dist/src/platforms/kakaotalk/protocol/media-uploader.js.map +1 -0
  35. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +6 -0
  36. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  37. package/dist/src/platforms/kakaotalk/protocol/session.js +61 -0
  38. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  39. package/dist/src/platforms/kakaotalk/types.d.ts +44 -0
  40. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  41. package/dist/src/platforms/kakaotalk/types.js +9 -0
  42. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  43. package/dist/src/platforms/slackbot/types.d.ts +4 -0
  44. package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
  45. package/docs/content/docs/cli/kakaotalk.mdx +47 -2
  46. package/docs/content/docs/sdk/kakaotalk.mdx +32 -0
  47. package/package.json +1 -1
  48. package/skills/agent-channeltalk/SKILL.md +1 -1
  49. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  50. package/skills/agent-discord/SKILL.md +1 -1
  51. package/skills/agent-discordbot/SKILL.md +1 -1
  52. package/skills/agent-instagram/SKILL.md +1 -1
  53. package/skills/agent-kakaotalk/SKILL.md +62 -4
  54. package/skills/agent-kakaotalk/references/common-patterns.md +50 -11
  55. package/skills/agent-line/SKILL.md +1 -1
  56. package/skills/agent-slack/SKILL.md +1 -1
  57. package/skills/agent-slackbot/SKILL.md +1 -1
  58. package/skills/agent-teams/SKILL.md +1 -1
  59. package/skills/agent-telegram/SKILL.md +1 -1
  60. package/skills/agent-telegrambot/SKILL.md +1 -1
  61. package/skills/agent-webex/SKILL.md +1 -1
  62. package/skills/agent-wechatbot/SKILL.md +1 -1
  63. package/skills/agent-whatsapp/SKILL.md +1 -1
  64. package/skills/agent-whatsappbot/SKILL.md +1 -1
  65. package/src/platforms/kakaotalk/attachment-router.test.ts +102 -0
  66. package/src/platforms/kakaotalk/attachment-router.ts +50 -0
  67. package/src/platforms/kakaotalk/client.ts +315 -8
  68. package/src/platforms/kakaotalk/commands/message.ts +66 -0
  69. package/src/platforms/kakaotalk/image-meta.test.ts +90 -0
  70. package/src/platforms/kakaotalk/image-meta.ts +176 -0
  71. package/src/platforms/kakaotalk/index.ts +13 -0
  72. package/src/platforms/kakaotalk/media-upload.ts +44 -0
  73. package/src/platforms/kakaotalk/protocol/connection.ts +11 -0
  74. package/src/platforms/kakaotalk/protocol/media-uploader.ts +129 -0
  75. package/src/platforms/kakaotalk/protocol/session.ts +67 -0
  76. package/src/platforms/kakaotalk/types.ts +57 -0
  77. package/src/platforms/slackbot/types.ts +16 -0
  78. package/src/platforms/telegrambot/cli.ts +0 -0
@@ -12,8 +12,13 @@ export type {
12
12
  KakaoDeviceType,
13
13
  KakaoEmoticonKind,
14
14
  KakaoEmoticonMessageType,
15
+ KakaoFileExtra,
16
+ KakaoLoginResult,
17
+ KakaoMarkReadResult,
15
18
  KakaoMember,
16
19
  KakaoMessage,
20
+ KakaoMultiPhotoExtra,
21
+ KakaoPhotoExtra,
17
22
  KakaoProfile,
18
23
  KakaoSendResult,
19
24
  KakaoTalkListenerEventMap,
@@ -27,9 +32,11 @@ export type {
27
32
  export {
28
33
  KAKAO_EMOTICON_KIND_BY_TYPE,
29
34
  KAKAO_EMOTICON_MESSAGE_TYPES,
35
+ KAKAO_MESSAGE_TYPE,
30
36
  KakaoAccountCredentialsSchema,
31
37
  KakaoChatSchema,
32
38
  KakaoConfigSchema,
39
+ KakaoMarkReadResultSchema,
33
40
  KakaoMemberSchema,
34
41
  KakaoMessageSchema,
35
42
  KakaoProfileSchema,
@@ -39,3 +46,9 @@ export {
39
46
  KakaoTalkPushMessageEventSchema,
40
47
  KakaoTalkPushReadEventSchema,
41
48
  } from './types'
49
+ export { attemptLogin, generateDeviceUuid, loginFlow, registerDevice, requestPasscode } from './auth/kakao-login'
50
+ export type { LoginCredentials } from './auth/kakao-login'
51
+ export { sha1Hex } from './media-upload'
52
+ export { detectImageDimensions } from './image-meta'
53
+ export type { AttachmentInput, AttachmentPlan, ResolvedAttachment, SingleAttachmentKind } from './attachment-router'
54
+ export { planAttachments, resolveAttachment } from './attachment-router'
@@ -0,0 +1,44 @@
1
+ // SHA-1 hex (uppercase, 40 chars) — required as the `cs` (checksum) field
2
+ // on SHIP requests and on the inbound photo `extra` JSON. Verified by inbound
3
+ // capture of a real KakaoTalk client photo (2026-05).
4
+ export async function sha1Hex(data: Uint8Array): Promise<string> {
5
+ const hashBuf = await crypto.subtle.digest('SHA-1', data)
6
+ return Array.from(new Uint8Array(hashBuf))
7
+ .map((b) => b.toString(16).padStart(2, '0').toUpperCase())
8
+ .join('')
9
+ }
10
+
11
+ const MIME_BY_EXT: Record<string, string> = {
12
+ jpg: 'image/jpeg',
13
+ jpeg: 'image/jpeg',
14
+ png: 'image/png',
15
+ gif: 'image/gif',
16
+ webp: 'image/webp',
17
+ mp4: 'video/mp4',
18
+ mov: 'video/quicktime',
19
+ webm: 'video/webm',
20
+ mkv: 'video/x-matroska',
21
+ m4v: 'video/x-m4v',
22
+ m4a: 'audio/m4a',
23
+ mp3: 'audio/mpeg',
24
+ wav: 'audio/wav',
25
+ ogg: 'audio/ogg',
26
+ pdf: 'application/pdf',
27
+ csv: 'text/csv',
28
+ txt: 'text/plain',
29
+ json: 'application/json',
30
+ zip: 'application/zip',
31
+ doc: 'application/msword',
32
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
33
+ xls: 'application/vnd.ms-excel',
34
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
35
+ ppt: 'application/vnd.ms-powerpoint',
36
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
37
+ }
38
+
39
+ export function guessMimeFromFilename(filename: string): string {
40
+ const dot = filename.lastIndexOf('.')
41
+ if (dot < 0 || dot === filename.length - 1) return 'application/octet-stream'
42
+ const ext = filename.slice(dot + 1).toLowerCase()
43
+ return MIME_BY_EXT[ext] ?? 'application/octet-stream'
44
+ }
@@ -50,6 +50,17 @@ export class LocoConnection {
50
50
  await this.write(handshakePacket)
51
51
  }
52
52
 
53
+ // Writes raw bytes onto the LOCO stream after applying the same AES-128-GCM
54
+ // framing as encrypted packets — used by the media-upload POST step where
55
+ // the protocol expects the file payload to follow the POST request inside
56
+ // the same encrypted channel (chunked into N frames automatically by the
57
+ // crypto layer's 4-byte-size + nonce + ciphertext + tag wrapping).
58
+ async writeRaw(data: Buffer): Promise<void> {
59
+ if (!this.crypto) throw new Error('crypto not initialised')
60
+ const encrypted = this.crypto.encrypt(data)
61
+ await this.write(encrypted)
62
+ }
63
+
53
64
  async sendPacket(method: string, body: Record<string, unknown> = {}): Promise<LocoPacket> {
54
65
  const packetId = ++this.packetIdCounter
55
66
  const packet: LocoPacket = {
@@ -0,0 +1,129 @@
1
+ import { Long } from 'bson'
2
+
3
+ import type { KakaoDeviceType } from '../types'
4
+ import { MCCMNC, getLocoDeviceConfig } from './config'
5
+ import { LocoConnection } from './connection'
6
+ import type { LocoPacket } from './types'
7
+
8
+ export interface UploadToLocoOptions {
9
+ shipToken: string
10
+ shipHost: string
11
+ shipPort: number
12
+ chatId: Long
13
+ msgType: number
14
+ userId: string
15
+ filename: string
16
+ data: Uint8Array
17
+ width?: number
18
+ height?: number
19
+ deviceType: KakaoDeviceType
20
+ onProgress?: (sent: number, total: number) => void
21
+ }
22
+
23
+ export interface UploadToLocoResult {
24
+ completePacket: LocoPacket | null
25
+ postStatusCode: number
26
+ postOffset: number
27
+ }
28
+
29
+ const DEFAULT_NETWORK_TYPE = 0
30
+ const DEFAULT_COMPLETE_TIMEOUT_MS = 60_000
31
+
32
+ // Drives the SHIP → connect → POST → stream → COMPLETE pipeline that KakaoTalk
33
+ // uses for chat-media uploads. The caller has already done SHIP on the main
34
+ // session and received {k, vh, p}; this opens a dedicated TCP+LOCO connection
35
+ // to (vh, p), sends POST with the ticket + chat metadata, streams the raw file
36
+ // bytes, and waits for the server's COMPLETE push that triggers the actual
37
+ // chat-message registration.
38
+ //
39
+ // Field names (u/k/t/s/c/mid/w/h/mm/nt/os/av/f/ns) match the APK's
40
+ // `SR/g0.java` (PostJob) verbatim. The COMPLETE handshake is a server-pushed
41
+ // packet whose body contains the resulting chatLog struct.
42
+ export async function uploadMediaToLoco(opts: UploadToLocoOptions): Promise<UploadToLocoResult> {
43
+ return runPostStreamComplete(opts, 'POST')
44
+ }
45
+
46
+ // Single-entry MPOST upload — runs the same connect → POST → stream → COMPLETE
47
+ // pipeline as uploadMediaToLoco but with MPOST as the opcode (used after MSHIP
48
+ // for each entry in a multi-photo batch). Callers fan-out across all entries
49
+ // in parallel and then issue one FORWARD to register the gallery message.
50
+ export async function uploadMultiMediaEntry(opts: UploadToLocoOptions): Promise<UploadToLocoResult> {
51
+ return runPostStreamComplete(opts, 'MPOST')
52
+ }
53
+
54
+ async function runPostStreamComplete(opts: UploadToLocoOptions, opcode: 'POST' | 'MPOST'): Promise<UploadToLocoResult> {
55
+ const device = getLocoDeviceConfig(opts.deviceType)
56
+
57
+ const conn = new LocoConnection()
58
+ await conn.connectSecure(opts.shipHost, opts.shipPort)
59
+
60
+ const totalSize = opts.data.byteLength
61
+ let completeResolve: ((p: LocoPacket | null) => void) | null = null
62
+ let completeTimeout: ReturnType<typeof setTimeout> | null = null
63
+ const completePromise = new Promise<LocoPacket | null>((resolve) => {
64
+ completeResolve = resolve
65
+ completeTimeout = setTimeout(() => {
66
+ completeTimeout = null
67
+ resolve(null)
68
+ }, DEFAULT_COMPLETE_TIMEOUT_MS)
69
+ })
70
+ conn.onPush((push) => {
71
+ if (push.method === 'COMPLETE' && completeResolve) {
72
+ if (completeTimeout) {
73
+ clearTimeout(completeTimeout)
74
+ completeTimeout = null
75
+ }
76
+ completeResolve(push)
77
+ completeResolve = null
78
+ }
79
+ })
80
+
81
+ try {
82
+ const postBody: Record<string, unknown> = {
83
+ k: opts.shipToken,
84
+ s: totalSize,
85
+ t: opts.msgType,
86
+ u: Long.fromString(opts.userId),
87
+ os: device.os,
88
+ av: device.appVersion,
89
+ nt: DEFAULT_NETWORK_TYPE,
90
+ mm: MCCMNC,
91
+ }
92
+ if (opcode === 'POST') {
93
+ postBody.f = opts.filename
94
+ postBody.c = opts.chatId
95
+ postBody.mid = Long.ONE
96
+ postBody.ns = true
97
+ if (typeof opts.width === 'number') postBody.w = opts.width
98
+ if (typeof opts.height === 'number') postBody.h = opts.height
99
+ } else {
100
+ postBody.dt = 0
101
+ postBody.scp = 0
102
+ }
103
+
104
+ const postResp = await conn.sendPacket(opcode, postBody)
105
+ const postOffsetRaw = (postResp.body as Record<string, unknown>).o
106
+ const postOffset = typeof postOffsetRaw === 'number' ? postOffsetRaw : 0
107
+
108
+ const bytesToSend = opts.data.subarray(postOffset)
109
+ if (bytesToSend.length > 0) {
110
+ await conn.writeRaw(Buffer.from(bytesToSend))
111
+ opts.onProgress?.(totalSize, totalSize)
112
+ }
113
+
114
+ const completePacket = await completePromise
115
+ const postStatusCode = postResp.statusCode
116
+
117
+ return {
118
+ completePacket,
119
+ postStatusCode,
120
+ postOffset,
121
+ }
122
+ } finally {
123
+ if (completeTimeout) {
124
+ clearTimeout(completeTimeout)
125
+ completeTimeout = null
126
+ }
127
+ conn.close()
128
+ }
129
+ }
@@ -126,6 +126,73 @@ export class LocoSession {
126
126
  })
127
127
  }
128
128
 
129
+ // Sends a WRITE with non-text message_type plus the JSON-stringified `extra`
130
+ // payload that KakaoTalk clients render as the attachment (photo, file, etc).
131
+ // See types.ts → KakaoPhotoExtra / KakaoFileExtra for the per-type shape.
132
+ async sendAttachment(chatId: Long, type: number, extra: Record<string, unknown>, caption = ''): Promise<LocoPacket> {
133
+ if (!this.connection) throw new Error('Not connected')
134
+ return this.connection.sendPacket('WRITE', {
135
+ chatId,
136
+ msg: caption,
137
+ type,
138
+ noSeen: false,
139
+ extra: JSON.stringify(extra),
140
+ })
141
+ }
142
+
143
+ // SHIP — request a media-upload ticket. Reserves a slot on a media LOCO
144
+ // server and returns the token (k), host (vh), and port (p) the client must
145
+ // connect to next. Sent on the main session.
146
+ async shipMedia(chatId: Long, type: number, size: number, checksum: string, extension: string): Promise<LocoPacket> {
147
+ if (!this.connection) throw new Error('Not connected')
148
+ const body: Record<string, unknown> = {
149
+ c: chatId,
150
+ t: type,
151
+ s: Long.fromNumber(size),
152
+ cs: checksum,
153
+ }
154
+ if (extension.length > 0) body.e = extension
155
+ return this.connection.sendPacket('SHIP', body)
156
+ }
157
+
158
+ // MSHIP — multi-file equivalent of SHIP. Per-file fields become parallel
159
+ // arrays (sl/csl/el) and the response carries kl/vhl/pl arrays the caller
160
+ // must fan out across — one MPOST connection per entry.
161
+ async shipMultiMedia(
162
+ chatId: Long,
163
+ type: number,
164
+ sizes: number[],
165
+ checksums: string[],
166
+ extensions: string[],
167
+ ): Promise<LocoPacket> {
168
+ if (!this.connection) throw new Error('Not connected')
169
+ return this.connection.sendPacket('MSHIP', {
170
+ c: chatId,
171
+ t: type,
172
+ sl: sizes.map((s) => Long.fromNumber(s)),
173
+ csl: checksums,
174
+ el: extensions,
175
+ })
176
+ }
177
+
178
+ // FORWARD — used after MPOST: registers a multi-attachment chatlog as one
179
+ // message. Same shape as WRITE but the server routes the attachment to
180
+ // multi-media rendering (galleries, multi-photo posts).
181
+ async forwardChat(chatId: Long, type: number, extra: Record<string, unknown>, caption = ''): Promise<LocoPacket> {
182
+ if (!this.connection) throw new Error('Not connected')
183
+ return this.connection.sendPacket('FORWARD', {
184
+ chatId,
185
+ msg: caption,
186
+ type,
187
+ noSeen: false,
188
+ extra: JSON.stringify(extra),
189
+ })
190
+ }
191
+
192
+ getConnection(): LocoConnection | null {
193
+ return this.connection
194
+ }
195
+
129
196
  async syncMessages(chatId: Long, count = 20, cursor?: Long, maxLogId?: Long): Promise<LocoPacket> {
130
197
  if (!this.connection) throw new Error('Not connected')
131
198
  return this.connection.sendPacket('SYNCMSG', {
@@ -119,6 +119,63 @@ export interface KakaoSendResult {
119
119
  sent_at: number
120
120
  }
121
121
 
122
+ // LOCO message_type values. Source: KakaoTalk APK 26.4.2 + typeclaw inbound parser.
123
+ export const KAKAO_MESSAGE_TYPE = {
124
+ TEXT: 1,
125
+ PHOTO: 2,
126
+ VIDEO: 3,
127
+ AUDIO: 5,
128
+ FILE: 18,
129
+ MULTIPHOTO: 27,
130
+ } as const
131
+
132
+ // PHOTO `extra` keys verified by inbound capture of a real KakaoTalk client
133
+ // photo message (2026-05). The bare minimum the receiver needs to render
134
+ // the inline preview is k/s/w/h/mt/cs — url and thumbnailUrl are server-issued
135
+ // pre-signed URLs that the recipient regenerates locally from k, so we don't
136
+ // supply them on outbound.
137
+ export interface KakaoPhotoExtra {
138
+ k: string
139
+ s: number
140
+ w: number
141
+ h: number
142
+ mt: string
143
+ cs: string
144
+ url?: string
145
+ thumbnailUrl?: string
146
+ thumbnailWidth?: number
147
+ thumbnailHeight?: number
148
+ expire?: number
149
+ }
150
+
151
+ export interface KakaoFileExtra {
152
+ k: string
153
+ s: number
154
+ name: string
155
+ mt: string
156
+ cs: string
157
+ expire?: number
158
+ url?: string
159
+ }
160
+
161
+ // MULTIPHOTO extra — each per-file field of KakaoPhotoExtra becomes a
162
+ // parallel array. Verified by inbound capture of a real multi-photo message
163
+ // (2026-05). csl uses lowercase hex (unlike PHOTO's cs which is uppercase).
164
+ export interface KakaoMultiPhotoExtra {
165
+ kl: string[]
166
+ wl: number[]
167
+ hl: number[]
168
+ mtl: string[]
169
+ sl: number[]
170
+ csl: string[]
171
+ cmtl?: string[]
172
+ imageUrls?: string[]
173
+ thumbnailUrls?: string[]
174
+ thumbnailWidths?: number[]
175
+ thumbnailHeights?: number[]
176
+ expire?: number
177
+ }
178
+
122
179
  export interface KakaoMarkReadResult {
123
180
  success: boolean
124
181
  status_code: number
@@ -334,6 +334,18 @@ export interface SlackSocketModeMessageEvent {
334
334
  event_ts?: string
335
335
  edited?: { user: string; ts: string }
336
336
  hidden?: boolean
337
+ // Set on every reply within a thread; identifies the author of the message
338
+ // the thread is rooted at. Useful for deciding whether a reply targets the
339
+ // bot, another human, or an unknown parent.
340
+ parent_user_id?: string
341
+ // Client-generated UUID on user-authored messages, stable across Slack-side
342
+ // resends of the same gesture. Primary dedupe key for the "one user action
343
+ // surfaces as two events" case.
344
+ client_msg_id?: string
345
+ // Attachments delivered inline on the same message event. Slack does not
346
+ // fire a separate file_share envelope for messages we receive over Socket
347
+ // Mode, so consumers reading attachments off `message` events look here.
348
+ files?: SlackFile[]
337
349
  [key: string]: unknown
338
350
  }
339
351
 
@@ -345,6 +357,10 @@ export interface SlackSocketModeAppMentionEvent {
345
357
  ts: string
346
358
  thread_ts?: string
347
359
  event_ts?: string
360
+ // `app_mention` envelopes do not always carry `client_msg_id`, but typing
361
+ // it keeps the promotion to a message-shaped event lossless if Slack ever
362
+ // starts sending it on this event.
363
+ client_msg_id?: string
348
364
  [key: string]: unknown
349
365
  }
350
366
 
File without changes