agent-messenger 2.36.1 → 2.37.1
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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/kakaotalk/chat-classifier.d.ts +5 -4
- package/dist/src/platforms/kakaotalk/chat-classifier.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/chat-classifier.js +15 -9
- package/dist/src/platforms/kakaotalk/chat-classifier.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +33 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +377 -42
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/index.d.ts +4 -3
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +2 -2
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +13 -2
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/login-response.d.ts +1 -0
- package/dist/src/platforms/kakaotalk/protocol/login-response.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/login-response.js +1 -1
- package/dist/src/platforms/kakaotalk/protocol/login-response.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +50 -2
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +18 -1
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +7 -1
- package/docs/content/docs/sdk/kakaotalk.mdx +29 -1
- package/package.json +1 -1
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-kakaotalk/references/common-patterns.md +36 -0
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/kakaotalk/chat-classifier.test.ts +23 -1
- package/src/platforms/kakaotalk/chat-classifier.ts +16 -10
- package/src/platforms/kakaotalk/client.test.ts +1225 -88
- package/src/platforms/kakaotalk/client.ts +476 -45
- package/src/platforms/kakaotalk/index.test.ts +39 -0
- package/src/platforms/kakaotalk/index.ts +12 -1
- package/src/platforms/kakaotalk/protocol/connection.test.ts +87 -0
- package/src/platforms/kakaotalk/protocol/connection.ts +15 -2
- package/src/platforms/kakaotalk/protocol/login-response.test.ts +13 -2
- package/src/platforms/kakaotalk/protocol/login-response.ts +1 -1
- package/src/platforms/kakaotalk/types.ts +41 -2
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
KakaoChatSchema,
|
|
11
11
|
KakaoConfigSchema,
|
|
12
12
|
KakaoLeaveChatResultSchema,
|
|
13
|
+
KakaoMessagePageSchema,
|
|
13
14
|
KakaoMessageSchema,
|
|
14
15
|
KakaoSendResultSchema,
|
|
15
16
|
KakaoTalkClient,
|
|
16
17
|
KakaoTalkError,
|
|
18
|
+
isOpenKakaoChatType,
|
|
17
19
|
KakaoTalkListener,
|
|
18
20
|
KakaoTalkPushMemberEventSchema,
|
|
19
21
|
KakaoTalkPushMessageEventSchema,
|
|
@@ -47,10 +49,41 @@ it('KakaoChatSchema is exported from barrel', () => {
|
|
|
47
49
|
expect(typeof KakaoChatSchema.parse).toBe('function')
|
|
48
50
|
})
|
|
49
51
|
|
|
52
|
+
it('KakaoChatSchema preserves a non-empty string channel type', () => {
|
|
53
|
+
for (const channelType of ['DirectChat', 'MultiChat', 'PlusChat', 'MemoChat', 'OM', 'OD', 'FutureChat']) {
|
|
54
|
+
expect(
|
|
55
|
+
KakaoChatSchema.parse({
|
|
56
|
+
chat_id: '300',
|
|
57
|
+
type: channelType,
|
|
58
|
+
display_name: null,
|
|
59
|
+
title: null,
|
|
60
|
+
active_members: 2,
|
|
61
|
+
unread_count: 0,
|
|
62
|
+
last_message: null,
|
|
63
|
+
}).type,
|
|
64
|
+
).toBe(channelType)
|
|
65
|
+
}
|
|
66
|
+
expect(() =>
|
|
67
|
+
KakaoChatSchema.parse({
|
|
68
|
+
chat_id: '300',
|
|
69
|
+
type: '',
|
|
70
|
+
display_name: null,
|
|
71
|
+
title: null,
|
|
72
|
+
active_members: 2,
|
|
73
|
+
unread_count: 0,
|
|
74
|
+
last_message: null,
|
|
75
|
+
}),
|
|
76
|
+
).toThrow()
|
|
77
|
+
})
|
|
78
|
+
|
|
50
79
|
it('KakaoMessageSchema is exported from barrel', () => {
|
|
51
80
|
expect(typeof KakaoMessageSchema.parse).toBe('function')
|
|
52
81
|
})
|
|
53
82
|
|
|
83
|
+
it('KakaoMessagePageSchema is exported from barrel', () => {
|
|
84
|
+
expect(typeof KakaoMessagePageSchema.parse).toBe('function')
|
|
85
|
+
})
|
|
86
|
+
|
|
54
87
|
it('KakaoSendResultSchema is exported from barrel', () => {
|
|
55
88
|
expect(typeof KakaoSendResultSchema.parse).toBe('function')
|
|
56
89
|
})
|
|
@@ -83,6 +116,12 @@ it('classifyKakaoChat is exported from barrel', () => {
|
|
|
83
116
|
expect(typeof classifyKakaoChat).toBe('function')
|
|
84
117
|
})
|
|
85
118
|
|
|
119
|
+
it('isOpenKakaoChatType is exported from barrel', () => {
|
|
120
|
+
expect(isOpenKakaoChatType('OM')).toBe(true)
|
|
121
|
+
expect(isOpenKakaoChatType(13)).toBe(true)
|
|
122
|
+
expect(isOpenKakaoChatType('UNKNOWN')).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
86
125
|
it('KakaoLeaveChatResultSchema is exported from barrel', () => {
|
|
87
126
|
expect(typeof KakaoLeaveChatResultSchema.parse).toBe('function')
|
|
88
127
|
})
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { KakaoTalkClient, KakaoTalkError } from './client'
|
|
2
|
-
export {
|
|
2
|
+
export type {
|
|
3
|
+
KakaoTalkGetChatFailureReason,
|
|
4
|
+
KakaoTalkResponseFailureKind,
|
|
5
|
+
KakaoTalkResponseStatusSource,
|
|
6
|
+
} from './client'
|
|
7
|
+
export { classifyKakaoChat, isOpenKakaoChatType } from './chat-classifier'
|
|
3
8
|
export type { KakaoChatKind } from './chat-classifier'
|
|
4
9
|
export { KakaoCredentialManager, CredentialManager } from './credential-manager'
|
|
5
10
|
export { KakaoTalkListener } from './listener'
|
|
@@ -9,6 +14,8 @@ export type {
|
|
|
9
14
|
KakaoAuthErrorCode,
|
|
10
15
|
KakaoAuthMethod,
|
|
11
16
|
KakaoChat,
|
|
17
|
+
KakaoChatType,
|
|
18
|
+
KakaoOpenChatType,
|
|
12
19
|
KakaoConfig,
|
|
13
20
|
KakaoDeviceType,
|
|
14
21
|
KakaoEmoticonKind,
|
|
@@ -18,7 +25,9 @@ export type {
|
|
|
18
25
|
KakaoLoginResult,
|
|
19
26
|
KakaoMarkReadResult,
|
|
20
27
|
KakaoMember,
|
|
28
|
+
KakaoMemberSnapshot,
|
|
21
29
|
KakaoMessage,
|
|
30
|
+
KakaoMessagePage,
|
|
22
31
|
KakaoMultiPhotoExtra,
|
|
23
32
|
KakaoPhotoExtra,
|
|
24
33
|
KakaoProfile,
|
|
@@ -44,7 +53,9 @@ export {
|
|
|
44
53
|
KakaoLeaveChatResultSchema,
|
|
45
54
|
KakaoMarkReadResultSchema,
|
|
46
55
|
KakaoMemberSchema,
|
|
56
|
+
KakaoMemberSnapshotSchema,
|
|
47
57
|
KakaoMessageSchema,
|
|
58
|
+
KakaoMessagePageSchema,
|
|
48
59
|
KakaoProfileSchema,
|
|
49
60
|
KakaoSendResultSchema,
|
|
50
61
|
KakaoTalkPushEmoticonEventSchema,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { LocoConnection } from './connection'
|
|
4
|
+
import { encodePacket } from './packet'
|
|
5
|
+
import type { LocoPacket } from './types'
|
|
6
|
+
|
|
7
|
+
interface ConnectionInternals {
|
|
8
|
+
socket: {
|
|
9
|
+
write: (data: Buffer, callback: (error?: Error) => void) => boolean
|
|
10
|
+
} | null
|
|
11
|
+
pendingResolvers: Map<number, unknown>
|
|
12
|
+
timedOutIds: Set<number>
|
|
13
|
+
onData: (chunk: Buffer) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function internals(connection: LocoConnection): ConnectionInternals {
|
|
17
|
+
return connection as unknown as ConnectionInternals
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('LocoConnection.sendPacket', () => {
|
|
21
|
+
it('resolves a response delivered synchronously during write without double settlement', async () => {
|
|
22
|
+
const connection = new LocoConnection()
|
|
23
|
+
const owned = internals(connection)
|
|
24
|
+
const response: LocoPacket = {
|
|
25
|
+
packetId: 1,
|
|
26
|
+
statusCode: 0,
|
|
27
|
+
method: 'FAST',
|
|
28
|
+
bodyType: 0,
|
|
29
|
+
body: { accepted: true },
|
|
30
|
+
}
|
|
31
|
+
const pushed: LocoPacket[] = []
|
|
32
|
+
connection.onPush((packet) => pushed.push(packet))
|
|
33
|
+
owned.socket = {
|
|
34
|
+
write: (_data, callback) => {
|
|
35
|
+
owned.onData(encodePacket(response))
|
|
36
|
+
callback(new Error('late write callback failure'))
|
|
37
|
+
return false
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await expect(connection.sendPacket('FAST')).resolves.toEqual(response)
|
|
42
|
+
expect(pushed).toEqual([])
|
|
43
|
+
expect(owned.pendingResolvers.size).toBe(0)
|
|
44
|
+
expect(owned.timedOutIds.size).toBe(0)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('rejects a write failure and clears only its pending entry', async () => {
|
|
48
|
+
const connection = new LocoConnection()
|
|
49
|
+
const owned = internals(connection)
|
|
50
|
+
const pushed: LocoPacket[] = []
|
|
51
|
+
const sentinelTimer = setTimeout(() => {}, 60_000)
|
|
52
|
+
const sentinel = { resolve: () => {}, timer: sentinelTimer }
|
|
53
|
+
owned.pendingResolvers.set(99, sentinel)
|
|
54
|
+
connection.onPush((packet) => pushed.push(packet))
|
|
55
|
+
owned.socket = {
|
|
56
|
+
write: (_data, callback) => {
|
|
57
|
+
callback(new Error('socket write failed'))
|
|
58
|
+
return false
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await expect(connection.sendPacket('FAIL')).rejects.toThrow('socket write failed')
|
|
64
|
+
expect(owned.pendingResolvers.size).toBe(1)
|
|
65
|
+
expect(owned.pendingResolvers.get(99)).toBe(sentinel)
|
|
66
|
+
expect(owned.timedOutIds.has(1)).toBe(true)
|
|
67
|
+
|
|
68
|
+
owned.onData(
|
|
69
|
+
encodePacket({
|
|
70
|
+
packetId: 1,
|
|
71
|
+
statusCode: 0,
|
|
72
|
+
method: 'FAIL',
|
|
73
|
+
bodyType: 0,
|
|
74
|
+
body: { late: true },
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
expect(pushed).toEqual([])
|
|
79
|
+
expect(owned.pendingResolvers.size).toBe(1)
|
|
80
|
+
expect(owned.pendingResolvers.get(99)).toBe(sentinel)
|
|
81
|
+
expect(owned.timedOutIds.size).toBe(0)
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(sentinelTimer)
|
|
84
|
+
owned.pendingResolvers.delete(99)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -73,15 +73,28 @@ export class LocoConnection {
|
|
|
73
73
|
|
|
74
74
|
const raw = encodePacket(packet)
|
|
75
75
|
const data = this.crypto ? this.crypto.encrypt(raw) : raw
|
|
76
|
-
await this.write(data)
|
|
77
76
|
|
|
78
77
|
return new Promise((resolve, reject) => {
|
|
78
|
+
let entry: {
|
|
79
|
+
resolve: (packet: LocoPacket) => void
|
|
80
|
+
timer: ReturnType<typeof setTimeout>
|
|
81
|
+
}
|
|
79
82
|
const timer = setTimeout(() => {
|
|
83
|
+
if (this.pendingResolvers.get(packetId) !== entry) return
|
|
80
84
|
this.pendingResolvers.delete(packetId)
|
|
81
85
|
this.timedOutIds.add(packetId)
|
|
82
86
|
reject(new Error(`LOCO packet timeout: ${method} (${DEFAULT_SEND_TIMEOUT_MS}ms)`))
|
|
83
87
|
}, DEFAULT_SEND_TIMEOUT_MS)
|
|
84
|
-
|
|
88
|
+
entry = { resolve, timer }
|
|
89
|
+
this.pendingResolvers.set(packetId, entry)
|
|
90
|
+
|
|
91
|
+
void this.write(data).catch((error: unknown) => {
|
|
92
|
+
if (this.pendingResolvers.get(packetId) !== entry) return
|
|
93
|
+
clearTimeout(entry.timer)
|
|
94
|
+
this.pendingResolvers.delete(packetId)
|
|
95
|
+
this.timedOutIds.add(packetId)
|
|
96
|
+
reject(error)
|
|
97
|
+
})
|
|
85
98
|
})
|
|
86
99
|
}
|
|
87
100
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test'
|
|
2
2
|
|
|
3
|
-
import { validateLoginListResponse } from './login-response'
|
|
3
|
+
import { isSyntheticConnectionClose, validateLoginListResponse } from './login-response'
|
|
4
4
|
import type { LocoPacket } from './types'
|
|
5
5
|
|
|
6
6
|
function packet(statusCode: number, body: Record<string, unknown>): LocoPacket {
|
|
@@ -54,11 +54,22 @@ describe('LOGINLIST response classification', () => {
|
|
|
54
54
|
})
|
|
55
55
|
|
|
56
56
|
it('keeps a synthetic socket close on the transport-error path', () => {
|
|
57
|
-
const
|
|
57
|
+
const response = packet(-1, { error: 'connection closed' })
|
|
58
|
+
const error = captureError(() => validateLoginListResponse(response))
|
|
58
59
|
|
|
60
|
+
expect(isSyntheticConnectionClose(response)).toBe(true)
|
|
59
61
|
expect(error).toBeInstanceOf(Error)
|
|
60
62
|
expect(error).toMatchObject({ message: 'KakaoTalk LOGINLIST transport failed: connection closed' })
|
|
61
63
|
expect(error).not.toHaveProperty('code')
|
|
62
64
|
expect(error).not.toHaveProperty('serverStatus')
|
|
63
65
|
})
|
|
66
|
+
|
|
67
|
+
it('does not classify status -1 with a nonmatching body as a synthetic socket close', () => {
|
|
68
|
+
const response = packet(-1, { error: 'provider rejected' })
|
|
69
|
+
|
|
70
|
+
expect(isSyntheticConnectionClose(response)).toBe(false)
|
|
71
|
+
expect(() => validateLoginListResponse(response)).toThrow(
|
|
72
|
+
expect.objectContaining({ code: 'login_rejected', serverStatus: -1 }),
|
|
73
|
+
)
|
|
74
|
+
})
|
|
64
75
|
})
|
|
@@ -17,7 +17,7 @@ export class KakaoLoginResponseError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function isSyntheticConnectionClose(response: LocoPacket): boolean {
|
|
20
|
+
export function isSyntheticConnectionClose(response: LocoPacket): boolean {
|
|
21
21
|
return response.statusCode === CONNECTION_CLOSED_STATUS && response.body.error === 'connection closed'
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -70,9 +70,20 @@ export const KAKAO_NEXT_ACTIONS: Record<string, { next_action: string; message:
|
|
|
70
70
|
},
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export type KakaoOpenChatType = 2 | 13 | 14 | 15 | 16 | 'OM' | 'OD'
|
|
74
|
+
export const KNOWN_KAKAO_CHAT_STRING_TYPES = ['DirectChat', 'MultiChat', 'PlusChat', 'MemoChat', 'OM', 'OD'] as const
|
|
75
|
+
export type KakaoChatType = number | string
|
|
76
|
+
|
|
77
|
+
export function isKnownKakaoChatStringType(value: unknown): value is (typeof KNOWN_KAKAO_CHAT_STRING_TYPES)[number] {
|
|
78
|
+
return (
|
|
79
|
+
typeof value === 'string' &&
|
|
80
|
+
KNOWN_KAKAO_CHAT_STRING_TYPES.includes(value as (typeof KNOWN_KAKAO_CHAT_STRING_TYPES)[number])
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
73
84
|
export interface KakaoChat {
|
|
74
85
|
chat_id: string
|
|
75
|
-
type:
|
|
86
|
+
type: KakaoChatType
|
|
76
87
|
display_name: string | null
|
|
77
88
|
title: string | null
|
|
78
89
|
active_members: number
|
|
@@ -95,6 +106,12 @@ export interface KakaoMessage {
|
|
|
95
106
|
sent_at: number
|
|
96
107
|
}
|
|
97
108
|
|
|
109
|
+
export interface KakaoMessagePage {
|
|
110
|
+
messages: KakaoMessage[]
|
|
111
|
+
next_cursor: string | null
|
|
112
|
+
complete: boolean
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
export interface KakaoMember {
|
|
99
116
|
user_id: string
|
|
100
117
|
nickname: string
|
|
@@ -112,6 +129,14 @@ export interface KakaoMember {
|
|
|
112
129
|
open_permission: number | null
|
|
113
130
|
}
|
|
114
131
|
|
|
132
|
+
export interface KakaoMemberSnapshot {
|
|
133
|
+
chat_id: string
|
|
134
|
+
active_members: number
|
|
135
|
+
members: KakaoMember[]
|
|
136
|
+
complete: true
|
|
137
|
+
consistency_basis: 'stable_double_read_chatinfo_getmem'
|
|
138
|
+
}
|
|
139
|
+
|
|
115
140
|
export interface KakaoSendResult {
|
|
116
141
|
success: boolean
|
|
117
142
|
status_code: number
|
|
@@ -222,7 +247,7 @@ export interface KakaoTypingResult {
|
|
|
222
247
|
|
|
223
248
|
export const KakaoChatSchema = z.object({
|
|
224
249
|
chat_id: z.string(),
|
|
225
|
-
type: z.number(),
|
|
250
|
+
type: z.union([z.number(), z.string().min(1)]),
|
|
226
251
|
display_name: z.string().nullable(),
|
|
227
252
|
title: z.string().nullable(),
|
|
228
253
|
active_members: z.number(),
|
|
@@ -247,6 +272,12 @@ export const KakaoMessageSchema = z.object({
|
|
|
247
272
|
sent_at: z.number(),
|
|
248
273
|
})
|
|
249
274
|
|
|
275
|
+
export const KakaoMessagePageSchema = z.object({
|
|
276
|
+
messages: z.array(KakaoMessageSchema),
|
|
277
|
+
next_cursor: z.string().nullable(),
|
|
278
|
+
complete: z.boolean(),
|
|
279
|
+
})
|
|
280
|
+
|
|
250
281
|
export const KakaoMemberSchema = z.object({
|
|
251
282
|
user_id: z.string(),
|
|
252
283
|
nickname: z.string(),
|
|
@@ -261,6 +292,14 @@ export const KakaoMemberSchema = z.object({
|
|
|
261
292
|
open_permission: z.number().nullable(),
|
|
262
293
|
})
|
|
263
294
|
|
|
295
|
+
export const KakaoMemberSnapshotSchema = z.object({
|
|
296
|
+
chat_id: z.string(),
|
|
297
|
+
active_members: z.number().int().nonnegative(),
|
|
298
|
+
members: z.array(KakaoMemberSchema),
|
|
299
|
+
complete: z.literal(true),
|
|
300
|
+
consistency_basis: z.literal('stable_double_read_chatinfo_getmem'),
|
|
301
|
+
})
|
|
302
|
+
|
|
264
303
|
export const KakaoSendResultSchema = z.object({
|
|
265
304
|
success: z.boolean(),
|
|
266
305
|
status_code: z.number(),
|