agent-messenger 2.19.5 → 2.20.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/line/client.d.ts +4 -0
- package/dist/src/platforms/line/client.d.ts.map +1 -1
- package/dist/src/platforms/line/client.js +124 -24
- package/dist/src/platforms/line/client.js.map +1 -1
- package/dist/src/platforms/line/e2ee-storage.d.ts +16 -0
- package/dist/src/platforms/line/e2ee-storage.d.ts.map +1 -0
- package/dist/src/platforms/line/e2ee-storage.js +93 -0
- package/dist/src/platforms/line/e2ee-storage.js.map +1 -0
- package/dist/src/platforms/teams/cli.d.ts.map +1 -1
- package/dist/src/platforms/teams/cli.js +2 -1
- package/dist/src/platforms/teams/cli.js.map +1 -1
- package/dist/src/platforms/teams/client.d.ts +4 -1
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +84 -0
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/chat.d.ts +13 -0
- package/dist/src/platforms/teams/commands/chat.d.ts.map +1 -0
- package/dist/src/platforms/teams/commands/chat.js +111 -0
- package/dist/src/platforms/teams/commands/chat.js.map +1 -0
- package/dist/src/platforms/teams/commands/index.d.ts +1 -0
- package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/index.js +1 -0
- package/dist/src/platforms/teams/commands/index.js.map +1 -1
- package/dist/src/platforms/teams/types.d.ts +24 -0
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/types.js +8 -0
- package/dist/src/platforms/teams/types.js.map +1 -1
- package/dist/src/tui/adapters/line-adapter.js +1 -1
- package/dist/src/tui/adapters/line-adapter.js.map +1 -1
- package/docs/content/docs/cli/line.mdx +13 -11
- 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-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +7 -5
- package/skills/agent-line/references/common-patterns.md +5 -2
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +20 -2
- package/skills/agent-teams/references/common-patterns.md +28 -0
- 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-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/line/client.test.ts +223 -20
- package/src/platforms/line/client.ts +141 -29
- package/src/platforms/line/e2ee-storage.test.ts +154 -0
- package/src/platforms/line/e2ee-storage.ts +119 -0
- package/src/platforms/teams/cli.ts +2 -0
- package/src/platforms/teams/client.test.ts +96 -0
- package/src/platforms/teams/client.ts +133 -0
- package/src/platforms/teams/commands/chat.test.ts +100 -0
- package/src/platforms/teams/commands/chat.ts +131 -0
- package/src/platforms/teams/commands/index.ts +1 -0
- package/src/platforms/teams/types.ts +20 -0
- package/src/tui/adapters/line-adapter.ts +1 -1
|
@@ -71,12 +71,15 @@ MESSAGES=$(agent-line message list "$CHAT_ID" -n 50)
|
|
|
71
71
|
MSG_COUNT=$(echo "$MESSAGES" | jq 'length')
|
|
72
72
|
echo "Found $MSG_COUNT messages"
|
|
73
73
|
|
|
74
|
-
# Show messages;
|
|
75
|
-
|
|
74
|
+
# Show messages by display name; Letter Sealing messages are decrypted when E2EE
|
|
75
|
+
# key material is available, otherwise decryption_error explains why text is null.
|
|
76
|
+
echo "$MESSAGES" | jq -r '.[] | "\(.author_name // .author_id): \(.text // .decryption_error.message // "[non-text]")"'
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
**When to use**: Context gathering, summarizing conversations, catching up on missed messages.
|
|
79
80
|
|
|
81
|
+
**E2EE note**: `message list` decrypts Letter Sealing (E2EE) messages when key material is available — restored from a prior QR/email login. When keys are missing, `text` is `null` and `decryption_error.code` is `missing_e2ee_key`; re-run `agent-line auth login` (QR) to provision keys.
|
|
82
|
+
|
|
80
83
|
## Pattern 4: Monitor for New Messages
|
|
81
84
|
|
|
82
85
|
**Use case**: Watch a chat room and respond to new messages using the SDK
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-teams
|
|
3
3
|
description: Interact with Microsoft Teams - send messages, read channels, manage reactions
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.20.1
|
|
5
5
|
allowed-tools: Bash(agent-teams:*)
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
|
@@ -207,6 +207,23 @@ agent-teams channel info <team-id> 19:abc123@thread.tacv2
|
|
|
207
207
|
agent-teams channel history <team-id> <channel-id> --limit 100
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
### Chat Commands
|
|
211
|
+
|
|
212
|
+
For personal Microsoft accounts (`@outlook.com` / `@live.com`) that have no teams or channels — only 1:1, group, and self chat threads. Work accounts can use these too for their 1:1 and group chats.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# List 1:1, group, and self chats
|
|
216
|
+
agent-teams chat list
|
|
217
|
+
|
|
218
|
+
# Get chat message history
|
|
219
|
+
agent-teams chat history <chat-id> --limit 100
|
|
220
|
+
|
|
221
|
+
# Send a message to a chat
|
|
222
|
+
agent-teams chat send <chat-id> "Hello"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Chat IDs look like `19:guid1_guid2@unq.gbl.spaces` (1:1) or `19:guid@thread.tacv2` (group). Get them from `chat list`. The `48:notes` chat (`type: self`) is your personal "to me" notes thread.
|
|
226
|
+
|
|
210
227
|
### Team Commands
|
|
211
228
|
|
|
212
229
|
```bash
|
|
@@ -357,7 +374,7 @@ Common errors:
|
|
|
357
374
|
|
|
358
375
|
- `Not authenticated`: No valid token (auto-extraction failed — see Troubleshooting)
|
|
359
376
|
- `Token expired`: Token has expired and auto-refresh failed — see Troubleshooting
|
|
360
|
-
- `No current team set`: Run `team switch <id>` first
|
|
377
|
+
- `No current team set`: Run `team switch <id>` first. Personal accounts have no teams — use `chat list` / `chat history` / `chat send` instead
|
|
361
378
|
- `Message not found`: Invalid message ID
|
|
362
379
|
- `Channel not found`: Invalid channel ID
|
|
363
380
|
- `401 Unauthorized`: Token expired and auto-refresh failed — see Troubleshooting
|
|
@@ -419,6 +436,7 @@ See the [Teams SDK documentation](https://agent-messenger.dev/docs/sdk/teams) fo
|
|
|
419
436
|
- No real-time events / WebSocket connection
|
|
420
437
|
- No voice/video channel support
|
|
421
438
|
- No team management (create/delete channels, roles)
|
|
439
|
+
- Personal accounts: chats only (no teams/channels); use the `chat` commands
|
|
422
440
|
- No meeting support
|
|
423
441
|
- No webhook support
|
|
424
442
|
- Plain text messages only (no adaptive cards in v1)
|
|
@@ -438,6 +438,34 @@ SNAPSHOT=$(teams_cmd agent-teams snapshot)
|
|
|
438
438
|
|
|
439
439
|
**When to use**: Any script that runs for more than a few minutes.
|
|
440
440
|
|
|
441
|
+
## Pattern 12: Personal Account Chats (No Teams)
|
|
442
|
+
|
|
443
|
+
**Use case**: Message from a personal Microsoft account (`@outlook.com` / `@live.com`), which has no teams or channels — only 1:1, group, and self ("to me") chats
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
#!/bin/bash
|
|
447
|
+
|
|
448
|
+
# Personal accounts have no teams, so `team list` is empty and team/channel
|
|
449
|
+
# commands fail with "No current team set". Use the `chat` commands instead.
|
|
450
|
+
|
|
451
|
+
agent-teams auth extract 2>/dev/null || true
|
|
452
|
+
|
|
453
|
+
# List chats (type is oneOnOne, group, or self)
|
|
454
|
+
CHATS=$(agent-teams chat list)
|
|
455
|
+
echo "$CHATS" | jq -r '.[] | " \(.type): \(.id) — \(.topic // .last_message // "")"'
|
|
456
|
+
|
|
457
|
+
# Pick a chat ID (here: the self "to me" notes thread)
|
|
458
|
+
CHAT_ID=$(echo "$CHATS" | jq -r '.[] | select(.type=="self") | .id')
|
|
459
|
+
|
|
460
|
+
# Read history and send a message
|
|
461
|
+
agent-teams chat history "$CHAT_ID" --limit 20
|
|
462
|
+
agent-teams chat send "$CHAT_ID" "Reminder: stand-up at 10am"
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**When to use**: Any workflow on a personal/consumer Teams account. Get chat IDs from `chat list` — they look like `19:guid1_guid2@unq.gbl.spaces` (1:1), `19:guid@thread.tacv2` (group), or `48:notes` (self).
|
|
466
|
+
|
|
467
|
+
**Note**: Work accounts also have 1:1 and group chats and can use these same `chat` commands.
|
|
468
|
+
|
|
441
469
|
## Best Practices
|
|
442
470
|
|
|
443
471
|
### 1. Always Handle Token Expiry
|
|
@@ -56,9 +56,10 @@ describe('LineClient', () => {
|
|
|
56
56
|
})
|
|
57
57
|
|
|
58
58
|
describe('getMessages()', () => {
|
|
59
|
-
function clientWithTalk(talk: Record<string, unknown>): LineClient {
|
|
59
|
+
function clientWithTalk(talk: Record<string, unknown>, e2ee?: Record<string, unknown>): LineClient {
|
|
60
60
|
const client = new LineClient()
|
|
61
|
-
|
|
61
|
+
const { profile, ...talkMethods } = talk
|
|
62
|
+
;(client as any).client = { base: { talk: talkMethods, profile, e2ee: e2ee ?? {} } }
|
|
62
63
|
return client
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -106,28 +107,179 @@ describe('LineClient', () => {
|
|
|
106
107
|
expect(result.map((m) => m.text)).toEqual(['c', 'b'])
|
|
107
108
|
})
|
|
108
109
|
|
|
109
|
-
it('
|
|
110
|
+
it('resolves author MIDs to display names via getContacts', async () => {
|
|
110
111
|
const client = clientWithTalk({
|
|
112
|
+
profile: { mid: 'me' },
|
|
111
113
|
getServerTime: async () => 1700000000000,
|
|
112
114
|
getPreviousMessagesV2WithRequest: async () => [
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
{ id: '10', from: 'u1', text: 'hi', contentType: 'NONE', createdTime: 1700000001000 },
|
|
116
|
+
{ id: '11', from: 'u2', text: 'yo', contentType: 'NONE', createdTime: 1700000002000 },
|
|
117
|
+
],
|
|
118
|
+
getContacts: async ({ mids }: { mids: string[] }) =>
|
|
119
|
+
mids.map((mid) => ({ mid, displayName: mid === 'u1' ? 'Alice' : 'Bob' })),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const result = await client.getMessages('chat1', { count: 2 })
|
|
123
|
+
expect(result.map((m) => m.author_name)).toEqual(['Alice', 'Bob'])
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('batch-resolves unique authors in a single getContacts call', async () => {
|
|
127
|
+
let contactCalls = 0
|
|
128
|
+
const client = clientWithTalk({
|
|
129
|
+
profile: { mid: 'me' },
|
|
130
|
+
getServerTime: async () => 1700000000000,
|
|
131
|
+
getPreviousMessagesV2WithRequest: async () => [
|
|
132
|
+
{ id: '10', from: 'u1', text: 'a', contentType: 'NONE', createdTime: 1700000001000 },
|
|
133
|
+
{ id: '11', from: 'u1', text: 'b', contentType: 'NONE', createdTime: 1700000002000 },
|
|
134
|
+
{ id: '12', from: 'u2', text: 'c', contentType: 'NONE', createdTime: 1700000003000 },
|
|
135
|
+
],
|
|
136
|
+
getContacts: async ({ mids }: { mids: string[] }) => {
|
|
137
|
+
contactCalls++
|
|
138
|
+
expect([...mids].sort()).toEqual(['u1', 'u2'])
|
|
139
|
+
return mids.map((mid) => ({ mid, displayName: mid.toUpperCase() }))
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const result = await client.getMessages('chat1', { count: 3 })
|
|
144
|
+
expect(contactCalls).toBe(1)
|
|
145
|
+
expect(result.map((m) => m.author_name)).toEqual(['U1', 'U1', 'U2'])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('resolves the current user via getProfile since getContacts omits self', async () => {
|
|
149
|
+
const client = clientWithTalk({
|
|
150
|
+
profile: { mid: 'me' },
|
|
151
|
+
getServerTime: async () => 1700000000000,
|
|
152
|
+
getPreviousMessagesV2WithRequest: async () => [
|
|
153
|
+
{ id: '10', from: 'me', text: 'mine', contentType: 'NONE', createdTime: 1700000001000 },
|
|
122
154
|
],
|
|
155
|
+
getContacts: async () => [],
|
|
156
|
+
getProfile: async () => ({ mid: 'me', displayName: 'My Name' }),
|
|
123
157
|
})
|
|
124
158
|
|
|
125
159
|
const result = await client.getMessages('chat1', { count: 1 })
|
|
126
|
-
expect(result[0].
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
160
|
+
expect(result[0].author_name).toBe('My Name')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('falls back to bare author_id when name resolution fails', async () => {
|
|
164
|
+
const client = clientWithTalk({
|
|
165
|
+
profile: { mid: 'me' },
|
|
166
|
+
getServerTime: async () => 1700000000000,
|
|
167
|
+
getPreviousMessagesV2WithRequest: async () => [
|
|
168
|
+
{ id: '10', from: 'u1', text: 'hi', contentType: 'NONE', createdTime: 1700000001000 },
|
|
169
|
+
],
|
|
170
|
+
getContacts: async () => {
|
|
171
|
+
throw new Error('network down')
|
|
172
|
+
},
|
|
130
173
|
})
|
|
174
|
+
|
|
175
|
+
const result = await client.getMessages('chat1', { count: 1 })
|
|
176
|
+
expect(result[0].author_name).toBeUndefined()
|
|
177
|
+
expect(result[0].author_id).toBe('u1')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('decrypts Letter-Sealing chunk messages via decryptE2EEMessage', async () => {
|
|
181
|
+
const encrypted = {
|
|
182
|
+
id: '40',
|
|
183
|
+
from: 'u1',
|
|
184
|
+
text: null,
|
|
185
|
+
contentType: 'NONE',
|
|
186
|
+
createdTime: 1700000004000,
|
|
187
|
+
chunks: ['a', 'b'],
|
|
188
|
+
metadata: { e2eeMark: '2', e2eeVersion: '2' },
|
|
189
|
+
}
|
|
190
|
+
const client = clientWithTalk(
|
|
191
|
+
{
|
|
192
|
+
getServerTime: async () => 1700000000000,
|
|
193
|
+
getPreviousMessagesV2WithRequest: async () => [encrypted],
|
|
194
|
+
},
|
|
195
|
+
{ decryptE2EEMessage: async (m: { id: string }) => ({ ...m, text: 'decrypted secret' }) },
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
const result = await client.getMessages('chat1', { count: 1 })
|
|
199
|
+
expect(result[0].text).toBe('decrypted secret')
|
|
200
|
+
expect(result[0].decryption_error).toBeUndefined()
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('normalizes metadata-shaped history messages to contentMetadata before decrypting', async () => {
|
|
204
|
+
let received: { contentMetadata?: unknown } | undefined
|
|
205
|
+
const client = clientWithTalk(
|
|
206
|
+
{
|
|
207
|
+
getServerTime: async () => 1700000000000,
|
|
208
|
+
getPreviousMessagesV2WithRequest: async () => [
|
|
209
|
+
{
|
|
210
|
+
id: '40',
|
|
211
|
+
from: 'u1',
|
|
212
|
+
text: null,
|
|
213
|
+
contentType: 'NONE',
|
|
214
|
+
createdTime: 1700000004000,
|
|
215
|
+
chunks: ['a', 'b'],
|
|
216
|
+
metadata: { e2eeMark: '2', e2eeVersion: '2' },
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
decryptE2EEMessage: async (m: { contentMetadata?: unknown }) => {
|
|
222
|
+
received = m
|
|
223
|
+
// mirror the vendor decryptor: it reads contentMetadata.e2eeVersion
|
|
224
|
+
const meta = m.contentMetadata as { e2eeVersion?: string }
|
|
225
|
+
return { text: `v${meta.e2eeVersion}` }
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
const result = await client.getMessages('chat1', { count: 1 })
|
|
231
|
+
expect((received?.contentMetadata as { e2eeVersion?: string })?.e2eeVersion).toBe('2')
|
|
232
|
+
expect(result[0].text).toBe('v2')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('surfaces missing_e2ee_key when decryption fails for lack of keys', async () => {
|
|
236
|
+
const client = clientWithTalk(
|
|
237
|
+
{
|
|
238
|
+
getServerTime: async () => 1700000000000,
|
|
239
|
+
getPreviousMessagesV2WithRequest: async () => [
|
|
240
|
+
{
|
|
241
|
+
id: '40',
|
|
242
|
+
from: 'u1',
|
|
243
|
+
text: null,
|
|
244
|
+
contentType: 'NONE',
|
|
245
|
+
createdTime: 1700000004000,
|
|
246
|
+
chunks: ['a', 'b'],
|
|
247
|
+
metadata: { e2eeMark: '2', e2eeVersion: '2' },
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
decryptE2EEMessage: async () => {
|
|
253
|
+
throw new Error('NoE2EEKey: E2EE Key has not been saved')
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
const result = await client.getMessages('chat1', { count: 1 })
|
|
259
|
+
expect(result[0].text).toBeNull()
|
|
260
|
+
expect(result[0].decryption_error?.code).toBe('missing_e2ee_key')
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('does not decrypt plain messages that already carry text', async () => {
|
|
264
|
+
let decryptCalls = 0
|
|
265
|
+
const client = clientWithTalk(
|
|
266
|
+
{
|
|
267
|
+
getServerTime: async () => 1700000000000,
|
|
268
|
+
getPreviousMessagesV2WithRequest: async () => [
|
|
269
|
+
{ id: '50', from: 'u1', text: 'plain hello', contentType: 'NONE', createdTime: 1700000005000 },
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
decryptE2EEMessage: async () => {
|
|
274
|
+
decryptCalls++
|
|
275
|
+
return { text: 'should-not-run' }
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const result = await client.getMessages('chat1', { count: 1 })
|
|
281
|
+
expect(result[0].text).toBe('plain hello')
|
|
282
|
+
expect(decryptCalls).toBe(0)
|
|
131
283
|
})
|
|
132
284
|
})
|
|
133
285
|
|
|
@@ -206,8 +358,17 @@ describe('LineClient', () => {
|
|
|
206
358
|
return out
|
|
207
359
|
}
|
|
208
360
|
|
|
209
|
-
it('decrypts messages and emits event + message', async () => {
|
|
210
|
-
const op = {
|
|
361
|
+
it('decrypts Letter-Sealing chunk messages and emits event + message', async () => {
|
|
362
|
+
const op = {
|
|
363
|
+
type: 'RECEIVE_MESSAGE',
|
|
364
|
+
message: {
|
|
365
|
+
id: '1',
|
|
366
|
+
from: 'u1',
|
|
367
|
+
to: 'me',
|
|
368
|
+
chunks: ['a', 'b'],
|
|
369
|
+
contentMetadata: { e2eeMark: '2', e2eeVersion: '2' },
|
|
370
|
+
},
|
|
371
|
+
}
|
|
211
372
|
const client = clientWithStream([op], async () => ({ id: '1', from: 'u1', to: 'me', text: 'hello' }))
|
|
212
373
|
|
|
213
374
|
const events = await collect(client)
|
|
@@ -216,9 +377,48 @@ describe('LineClient', () => {
|
|
|
216
377
|
expect(msg.message.text).toBe('hello')
|
|
217
378
|
})
|
|
218
379
|
|
|
380
|
+
it('normalizes metadata-only E2EE messages to contentMetadata before decrypting', async () => {
|
|
381
|
+
// given: an encrypted message whose E2EE metadata lives under `metadata`
|
|
382
|
+
// (not `contentMetadata`); the vendor decryptor reads contentMetadata
|
|
383
|
+
// unconditionally, so it must be normalized first — same as the history path
|
|
384
|
+
const op = {
|
|
385
|
+
type: 'RECEIVE_MESSAGE',
|
|
386
|
+
message: { id: '1', from: 'u1', to: 'me', chunks: ['a', 'b'], metadata: { e2eeMark: '2', e2eeVersion: '2' } },
|
|
387
|
+
}
|
|
388
|
+
const client = clientWithStream([op], async (m) => {
|
|
389
|
+
const meta = (m as { contentMetadata?: { e2eeVersion?: string } }).contentMetadata
|
|
390
|
+
return { id: '1', from: 'u1', to: 'me', text: `v${meta?.e2eeVersion}` }
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
const events = await collect(client)
|
|
394
|
+
const msg = events[1] as Extract<LineRawEvent, { kind: 'message' }>
|
|
395
|
+
expect(msg.message.text).toBe('v2')
|
|
396
|
+
expect(msg.message.decryption_error).toBeUndefined()
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('keeps a plain message text without decrypting it', async () => {
|
|
400
|
+
// given: a plain (non-Letter-Sealing) message that already carries text and
|
|
401
|
+
// has no E2EE chunks — decrypt MUST NOT run, mirroring the history path
|
|
402
|
+
const op = { type: 'RECEIVE_MESSAGE', message: { id: '1', from: 'u1', to: 'me', text: 'plain hi' } }
|
|
403
|
+
let decryptCalls = 0
|
|
404
|
+
const client = clientWithStream([op], async (m) => {
|
|
405
|
+
decryptCalls++
|
|
406
|
+
return m
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
const events = await collect(client)
|
|
410
|
+
const msg = events[1] as Extract<LineRawEvent, { kind: 'message' }>
|
|
411
|
+
expect(decryptCalls).toBe(0)
|
|
412
|
+
expect(msg.message.text).toBe('plain hi')
|
|
413
|
+
expect(msg.message.decryption_error).toBeUndefined()
|
|
414
|
+
})
|
|
415
|
+
|
|
219
416
|
it('keeps streaming when E2EE decryption fails (no reconnect loop)', async () => {
|
|
220
417
|
const ops = [
|
|
221
|
-
{
|
|
418
|
+
{
|
|
419
|
+
type: 'RECEIVE_MESSAGE',
|
|
420
|
+
message: { id: '1', from: 'u1', to: 'me', chunks: ['a', 'b'], metadata: { e2eeMark: '2', e2eeVersion: '2' } },
|
|
421
|
+
},
|
|
222
422
|
{ type: 'NOTIFIED_READ_MESSAGE' },
|
|
223
423
|
]
|
|
224
424
|
const client = clientWithStream(ops, async () => {
|
|
@@ -234,7 +434,10 @@ describe('LineClient', () => {
|
|
|
234
434
|
})
|
|
235
435
|
|
|
236
436
|
it('marks missing E2EE key failures explicitly', async () => {
|
|
237
|
-
const op = {
|
|
437
|
+
const op = {
|
|
438
|
+
type: 'RECEIVE_MESSAGE',
|
|
439
|
+
message: { id: '1', from: 'u1', to: 'me', chunks: ['a', 'b'], metadata: { e2eeMark: '2', e2eeVersion: '2' } },
|
|
440
|
+
}
|
|
238
441
|
const client = clientWithStream([op], async () => {
|
|
239
442
|
throw new Error('NoE2EEKey: E2EE Key has not been saved')
|
|
240
443
|
})
|