@kodelyth/feishu 2026.5.42 → 2026.6.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/klaw.plugin.json +1712 -47
- package/package.json +17 -4
- package/api.ts +0 -32
- package/channel-entry.ts +0 -20
- package/channel-plugin-api.ts +0 -1
- package/contract-api.ts +0 -16
- package/index.ts +0 -82
- package/runtime-api.ts +0 -52
- package/secret-contract-api.ts +0 -5
- package/security-contract-api.ts +0 -1
- package/session-key-api.ts +0 -1
- package/setup-api.ts +0 -3
- package/setup-entry.test.ts +0 -19
- package/setup-entry.ts +0 -13
- package/src/accounts.test.ts +0 -480
- package/src/accounts.ts +0 -333
- package/src/agent-config.ts +0 -21
- package/src/app-registration.ts +0 -331
- package/src/approval-auth.test.ts +0 -24
- package/src/approval-auth.ts +0 -25
- package/src/async.test.ts +0 -35
- package/src/async.ts +0 -104
- package/src/audio-preflight.runtime.ts +0 -9
- package/src/bitable.test.ts +0 -136
- package/src/bitable.ts +0 -762
- package/src/bot-content.ts +0 -485
- package/src/bot-group-name.test.ts +0 -116
- package/src/bot-runtime-api.ts +0 -12
- package/src/bot-sender-name.ts +0 -125
- package/src/bot.broadcast.test.ts +0 -523
- package/src/bot.card-action.test.ts +0 -552
- package/src/bot.checkBotMentioned.test.ts +0 -265
- package/src/bot.helpers.test.ts +0 -135
- package/src/bot.stripBotMention.test.ts +0 -126
- package/src/bot.test.ts +0 -3671
- package/src/bot.ts +0 -1703
- package/src/card-action.ts +0 -447
- package/src/card-interaction.test.ts +0 -131
- package/src/card-interaction.ts +0 -159
- package/src/card-test-helpers.ts +0 -54
- package/src/card-ux-approval.ts +0 -65
- package/src/card-ux-launcher.test.ts +0 -106
- package/src/card-ux-launcher.ts +0 -121
- package/src/card-ux-shared.ts +0 -33
- package/src/channel-runtime-api.ts +0 -16
- package/src/channel.runtime.ts +0 -47
- package/src/channel.test.ts +0 -1151
- package/src/channel.ts +0 -1423
- package/src/chat-schema.ts +0 -25
- package/src/chat.test.ts +0 -240
- package/src/chat.ts +0 -188
- package/src/client-timeout.ts +0 -42
- package/src/client.test.ts +0 -447
- package/src/client.ts +0 -262
- package/src/comment-dispatcher-runtime-api.ts +0 -6
- package/src/comment-dispatcher.test.ts +0 -185
- package/src/comment-dispatcher.ts +0 -107
- package/src/comment-handler-runtime-api.ts +0 -3
- package/src/comment-handler.test.ts +0 -592
- package/src/comment-handler.ts +0 -303
- package/src/comment-reaction.test.ts +0 -138
- package/src/comment-reaction.ts +0 -259
- package/src/comment-shared.test.ts +0 -183
- package/src/comment-shared.ts +0 -406
- package/src/comment-target.ts +0 -44
- package/src/config-schema.test.ts +0 -326
- package/src/config-schema.ts +0 -335
- package/src/conversation-id.test.ts +0 -18
- package/src/conversation-id.ts +0 -199
- package/src/dedup-runtime-api.ts +0 -1
- package/src/dedup.ts +0 -141
- package/src/dedupe-key.ts +0 -72
- package/src/directory.static.ts +0 -61
- package/src/directory.test.ts +0 -141
- package/src/directory.ts +0 -124
- package/src/doc-schema.ts +0 -182
- package/src/docx-batch-insert.test.ts +0 -116
- package/src/docx-batch-insert.ts +0 -223
- package/src/docx-color-text.ts +0 -154
- package/src/docx-table-ops.test.ts +0 -53
- package/src/docx-table-ops.ts +0 -316
- package/src/docx-types.ts +0 -38
- package/src/docx.account-selection.test.ts +0 -95
- package/src/docx.test.ts +0 -701
- package/src/docx.ts +0 -1596
- package/src/drive-schema.ts +0 -92
- package/src/drive.test.ts +0 -1237
- package/src/drive.ts +0 -829
- package/src/dynamic-agent.test.ts +0 -155
- package/src/dynamic-agent.ts +0 -143
- package/src/event-types.ts +0 -45
- package/src/external-keys.test.ts +0 -20
- package/src/external-keys.ts +0 -19
- package/src/lifecycle.test-support.ts +0 -220
- package/src/media.test.ts +0 -955
- package/src/media.ts +0 -1105
- package/src/mention-target.types.ts +0 -5
- package/src/mention.ts +0 -114
- package/src/message-action-contract.ts +0 -13
- package/src/monitor-state-runtime-api.ts +0 -7
- package/src/monitor-transport-runtime-api.ts +0 -10
- package/src/monitor.account.ts +0 -492
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +0 -219
- package/src/monitor.bot-identity.ts +0 -86
- package/src/monitor.bot-menu-handler.ts +0 -165
- package/src/monitor.bot-menu.lifecycle.test-support.ts +0 -224
- package/src/monitor.bot-menu.test.ts +0 -188
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +0 -264
- package/src/monitor.card-action.lifecycle.test-support.ts +0 -421
- package/src/monitor.cleanup.test.ts +0 -383
- package/src/monitor.comment-notice-handler.ts +0 -105
- package/src/monitor.comment.test.ts +0 -967
- package/src/monitor.comment.ts +0 -1386
- package/src/monitor.lifecycle.test.ts +0 -4
- package/src/monitor.message-handler.ts +0 -350
- package/src/monitor.reaction.lifecycle.test-support.ts +0 -68
- package/src/monitor.reaction.test.ts +0 -739
- package/src/monitor.startup.test.ts +0 -213
- package/src/monitor.startup.ts +0 -74
- package/src/monitor.state.defaults.test.ts +0 -46
- package/src/monitor.state.ts +0 -170
- package/src/monitor.synthetic-error.ts +0 -18
- package/src/monitor.test-mocks.ts +0 -46
- package/src/monitor.transport.ts +0 -451
- package/src/monitor.ts +0 -100
- package/src/monitor.webhook-e2e.test.ts +0 -279
- package/src/monitor.webhook-security.test.ts +0 -389
- package/src/monitor.webhook.test-helpers.ts +0 -116
- package/src/outbound-runtime-api.ts +0 -1
- package/src/outbound.test.ts +0 -1118
- package/src/outbound.ts +0 -785
- package/src/perm-schema.ts +0 -52
- package/src/perm.ts +0 -170
- package/src/pins.ts +0 -108
- package/src/policy.test.ts +0 -223
- package/src/policy.ts +0 -318
- package/src/post.test.ts +0 -105
- package/src/post.ts +0 -275
- package/src/probe.test.ts +0 -283
- package/src/probe.ts +0 -166
- package/src/processing-claims.ts +0 -59
- package/src/qr-terminal.ts +0 -1
- package/src/reactions.ts +0 -123
- package/src/reasoning-preview.test.ts +0 -113
- package/src/reasoning-preview.ts +0 -28
- package/src/reply-dispatcher-runtime-api.ts +0 -7
- package/src/reply-dispatcher.test.ts +0 -1513
- package/src/reply-dispatcher.ts +0 -748
- package/src/runtime.ts +0 -9
- package/src/secret-contract.ts +0 -145
- package/src/secret-input.ts +0 -1
- package/src/security-audit-shared.ts +0 -69
- package/src/security-audit.test.ts +0 -59
- package/src/security-audit.ts +0 -1
- package/src/send-result.ts +0 -80
- package/src/send-target.test.ts +0 -86
- package/src/send-target.ts +0 -35
- package/src/send.reply-fallback.test.ts +0 -417
- package/src/send.test.ts +0 -621
- package/src/send.ts +0 -861
- package/src/sequential-key.test.ts +0 -72
- package/src/sequential-key.ts +0 -25
- package/src/sequential-queue.test.ts +0 -165
- package/src/sequential-queue.ts +0 -86
- package/src/session-conversation.ts +0 -42
- package/src/session-route.ts +0 -48
- package/src/setup-core.ts +0 -51
- package/src/setup-surface.test.ts +0 -484
- package/src/setup-surface.ts +0 -618
- package/src/streaming-card.test.ts +0 -397
- package/src/streaming-card.ts +0 -571
- package/src/subagent-hooks.test.ts +0 -627
- package/src/subagent-hooks.ts +0 -413
- package/src/targets.ts +0 -97
- package/src/test-support/lifecycle-test-support.ts +0 -454
- package/src/thread-bindings.test.ts +0 -180
- package/src/thread-bindings.ts +0 -331
- package/src/tool-account-routing.test.ts +0 -250
- package/src/tool-account.test.ts +0 -44
- package/src/tool-account.ts +0 -93
- package/src/tool-factory-test-harness.ts +0 -79
- package/src/tool-result.test.ts +0 -32
- package/src/tool-result.ts +0 -16
- package/src/tools-config.test.ts +0 -21
- package/src/tools-config.ts +0 -22
- package/src/types.ts +0 -106
- package/src/typing.test.ts +0 -144
- package/src/typing.ts +0 -214
- package/src/wiki-schema.ts +0 -69
- package/src/wiki.ts +0 -270
- package/subagent-hooks-api.ts +0 -31
- package/tsconfig.json +0 -16
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
|
|
4
|
-
|
|
5
|
-
vi.mock("klaw/plugin-sdk/ssrf-runtime", () => ({
|
|
6
|
-
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
FeishuStreamingSession,
|
|
11
|
-
mergeStreamingText,
|
|
12
|
-
resolveStreamingCardSendMode,
|
|
13
|
-
} from "./streaming-card.js";
|
|
14
|
-
|
|
15
|
-
type StreamingSessionState = {
|
|
16
|
-
cardId: string;
|
|
17
|
-
messageId: string;
|
|
18
|
-
sequence: number;
|
|
19
|
-
currentText: string;
|
|
20
|
-
sentText: string;
|
|
21
|
-
hasNote: boolean;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function setStreamingSessionInternals(
|
|
25
|
-
session: FeishuStreamingSession,
|
|
26
|
-
values: {
|
|
27
|
-
state: StreamingSessionState;
|
|
28
|
-
lastUpdateTime?: number;
|
|
29
|
-
},
|
|
30
|
-
): void {
|
|
31
|
-
const internals = session as unknown as {
|
|
32
|
-
state: StreamingSessionState;
|
|
33
|
-
lastUpdateTime: number;
|
|
34
|
-
};
|
|
35
|
-
internals.state = values.state;
|
|
36
|
-
if (values.lastUpdateTime !== undefined) {
|
|
37
|
-
internals.lastUpdateTime = values.lastUpdateTime;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe("FeishuStreamingSession", () => {
|
|
42
|
-
afterAll(() => {
|
|
43
|
-
vi.doUnmock("klaw/plugin-sdk/ssrf-runtime");
|
|
44
|
-
vi.resetModules();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
vi.useRealTimers();
|
|
49
|
-
fetchWithSsrFGuardMock.mockReset();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
afterEach(() => {
|
|
53
|
-
vi.useRealTimers();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
function mockFetches(
|
|
57
|
-
updateBodies: string[],
|
|
58
|
-
failedContentUpdateIndexes: ReadonlySet<number> = new Set<number>(),
|
|
59
|
-
replaceBodies: string[] = [],
|
|
60
|
-
failedContentUpdateStatuses: ReadonlyMap<number, number> = new Map<number, number>(),
|
|
61
|
-
failedReplaceStatuses: ReadonlyMap<number, number> = new Map<number, number>(),
|
|
62
|
-
): void {
|
|
63
|
-
fetchWithSsrFGuardMock.mockImplementation(
|
|
64
|
-
async ({ url, init }: { url: string; init?: { body?: string } }) => {
|
|
65
|
-
const release = vi.fn(async () => {});
|
|
66
|
-
let ok = true;
|
|
67
|
-
let status = 200;
|
|
68
|
-
if (url.includes("/auth/")) {
|
|
69
|
-
return {
|
|
70
|
-
response: {
|
|
71
|
-
ok: true,
|
|
72
|
-
json: async () => ({
|
|
73
|
-
code: 0,
|
|
74
|
-
msg: "ok",
|
|
75
|
-
tenant_access_token: "token",
|
|
76
|
-
expire: 7200,
|
|
77
|
-
}),
|
|
78
|
-
},
|
|
79
|
-
release,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
if (url.includes("/elements/content/content")) {
|
|
83
|
-
const updateIndex = updateBodies.length;
|
|
84
|
-
updateBodies.push(init?.body ?? "");
|
|
85
|
-
if (failedContentUpdateIndexes.has(updateIndex)) {
|
|
86
|
-
throw new Error(`content update ${updateIndex} failed`);
|
|
87
|
-
}
|
|
88
|
-
const failedStatus = failedContentUpdateStatuses.get(updateIndex);
|
|
89
|
-
if (failedStatus !== undefined) {
|
|
90
|
-
ok = false;
|
|
91
|
-
status = failedStatus;
|
|
92
|
-
}
|
|
93
|
-
} else if (url.includes("/elements/content")) {
|
|
94
|
-
const replaceIndex = replaceBodies.length;
|
|
95
|
-
replaceBodies.push(init?.body ?? "");
|
|
96
|
-
const failedStatus = failedReplaceStatuses.get(replaceIndex);
|
|
97
|
-
if (failedStatus !== undefined) {
|
|
98
|
-
ok = false;
|
|
99
|
-
status = failedStatus;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
response: {
|
|
104
|
-
ok,
|
|
105
|
-
status,
|
|
106
|
-
json: async () => ({ code: 0, msg: "ok" }),
|
|
107
|
-
},
|
|
108
|
-
release,
|
|
109
|
-
};
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
it("flushes throttled pending text after the throttle window", async () => {
|
|
115
|
-
vi.useFakeTimers();
|
|
116
|
-
vi.setSystemTime(1_000);
|
|
117
|
-
const updateBodies: string[] = [];
|
|
118
|
-
mockFetches(updateBodies);
|
|
119
|
-
|
|
120
|
-
const session = new FeishuStreamingSession({} as never, {
|
|
121
|
-
appId: "app_pending_flush",
|
|
122
|
-
appSecret: "secret",
|
|
123
|
-
});
|
|
124
|
-
setStreamingSessionInternals(session, {
|
|
125
|
-
state: {
|
|
126
|
-
cardId: "card_1",
|
|
127
|
-
messageId: "om_1",
|
|
128
|
-
sequence: 1,
|
|
129
|
-
currentText: "hello",
|
|
130
|
-
sentText: "hello",
|
|
131
|
-
hasNote: false,
|
|
132
|
-
},
|
|
133
|
-
lastUpdateTime: 1_000,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
await session.update("hello small");
|
|
137
|
-
expect(updateBodies).toHaveLength(0);
|
|
138
|
-
|
|
139
|
-
await vi.advanceTimersByTimeAsync(160);
|
|
140
|
-
|
|
141
|
-
expect(updateBodies).toHaveLength(1);
|
|
142
|
-
expect(JSON.parse(updateBodies[0] ?? "{}")).toEqual({
|
|
143
|
-
content: " small",
|
|
144
|
-
sequence: 2,
|
|
145
|
-
uuid: "s_card_1_2",
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("pushes natural-boundary updates immediately inside the throttle window", async () => {
|
|
150
|
-
vi.useFakeTimers();
|
|
151
|
-
vi.setSystemTime(2_000);
|
|
152
|
-
const updateBodies: string[] = [];
|
|
153
|
-
mockFetches(updateBodies);
|
|
154
|
-
|
|
155
|
-
const session = new FeishuStreamingSession({} as never, {
|
|
156
|
-
appId: "app_boundary_flush",
|
|
157
|
-
appSecret: "secret",
|
|
158
|
-
});
|
|
159
|
-
setStreamingSessionInternals(session, {
|
|
160
|
-
state: {
|
|
161
|
-
cardId: "card_2",
|
|
162
|
-
messageId: "om_2",
|
|
163
|
-
sequence: 1,
|
|
164
|
-
currentText: "hello",
|
|
165
|
-
sentText: "hello",
|
|
166
|
-
hasNote: false,
|
|
167
|
-
},
|
|
168
|
-
lastUpdateTime: 2_000,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
await session.update("hello!");
|
|
172
|
-
|
|
173
|
-
expect(updateBodies).toHaveLength(1);
|
|
174
|
-
expect(JSON.parse(updateBodies[0] ?? "{}")).toEqual({
|
|
175
|
-
content: "!",
|
|
176
|
-
sequence: 2,
|
|
177
|
-
uuid: "s_card_2_2",
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("retries unsent suffix content after a failed delta update", async () => {
|
|
182
|
-
vi.useFakeTimers();
|
|
183
|
-
vi.setSystemTime(3_000);
|
|
184
|
-
const updateBodies: string[] = [];
|
|
185
|
-
mockFetches(updateBodies, new Set([0]));
|
|
186
|
-
|
|
187
|
-
const session = new FeishuStreamingSession({} as never, {
|
|
188
|
-
appId: "app_failed_delta_retry",
|
|
189
|
-
appSecret: "secret",
|
|
190
|
-
});
|
|
191
|
-
setStreamingSessionInternals(session, {
|
|
192
|
-
state: {
|
|
193
|
-
cardId: "card_3",
|
|
194
|
-
messageId: "om_3",
|
|
195
|
-
sequence: 1,
|
|
196
|
-
currentText: "hello",
|
|
197
|
-
sentText: "hello",
|
|
198
|
-
hasNote: false,
|
|
199
|
-
},
|
|
200
|
-
lastUpdateTime: 2_000,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
await session.update("hello world");
|
|
204
|
-
await session.update("hello world!");
|
|
205
|
-
|
|
206
|
-
expect(updateBodies).toHaveLength(2);
|
|
207
|
-
expect(JSON.parse(updateBodies[0] ?? "{}")).toEqual({
|
|
208
|
-
content: " world",
|
|
209
|
-
sequence: 2,
|
|
210
|
-
uuid: "s_card_3_2",
|
|
211
|
-
});
|
|
212
|
-
expect(JSON.parse(updateBodies[1] ?? "{}")).toEqual({
|
|
213
|
-
content: " world!",
|
|
214
|
-
sequence: 3,
|
|
215
|
-
uuid: "s_card_3_3",
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it("retries unsent suffix content after a non-OK delta update", async () => {
|
|
220
|
-
vi.useFakeTimers();
|
|
221
|
-
vi.setSystemTime(3_500);
|
|
222
|
-
const updateBodies: string[] = [];
|
|
223
|
-
mockFetches(updateBodies, new Set<number>(), [], new Map([[0, 429]]));
|
|
224
|
-
|
|
225
|
-
const session = new FeishuStreamingSession({} as never, {
|
|
226
|
-
appId: "app_non_ok_delta_retry",
|
|
227
|
-
appSecret: "secret",
|
|
228
|
-
});
|
|
229
|
-
setStreamingSessionInternals(session, {
|
|
230
|
-
state: {
|
|
231
|
-
cardId: "card_5",
|
|
232
|
-
messageId: "om_5",
|
|
233
|
-
sequence: 1,
|
|
234
|
-
currentText: "hello",
|
|
235
|
-
sentText: "hello",
|
|
236
|
-
hasNote: false,
|
|
237
|
-
},
|
|
238
|
-
lastUpdateTime: 2_000,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
await session.update("hello world");
|
|
242
|
-
await session.update("hello world!");
|
|
243
|
-
|
|
244
|
-
expect(updateBodies).toHaveLength(2);
|
|
245
|
-
expect(JSON.parse(updateBodies[0] ?? "{}")).toEqual({
|
|
246
|
-
content: " world",
|
|
247
|
-
sequence: 2,
|
|
248
|
-
uuid: "s_card_5_2",
|
|
249
|
-
});
|
|
250
|
-
expect(JSON.parse(updateBodies[1] ?? "{}")).toEqual({
|
|
251
|
-
content: " world!",
|
|
252
|
-
sequence: 3,
|
|
253
|
-
uuid: "s_card_5_3",
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("replaces content when final text removes transient streamed status", async () => {
|
|
258
|
-
vi.useFakeTimers();
|
|
259
|
-
vi.setSystemTime(4_000);
|
|
260
|
-
const updateBodies: string[] = [];
|
|
261
|
-
const replaceBodies: string[] = [];
|
|
262
|
-
mockFetches(updateBodies, new Set<number>(), replaceBodies);
|
|
263
|
-
|
|
264
|
-
const session = new FeishuStreamingSession({} as never, {
|
|
265
|
-
appId: "app_final_rewrite",
|
|
266
|
-
appSecret: "secret",
|
|
267
|
-
});
|
|
268
|
-
setStreamingSessionInternals(session, {
|
|
269
|
-
state: {
|
|
270
|
-
cardId: "card_4",
|
|
271
|
-
messageId: "om_4",
|
|
272
|
-
sequence: 1,
|
|
273
|
-
currentText: "🔎 Web Search\n\nfinal answer",
|
|
274
|
-
sentText: "🔎 Web Search\n\nfinal answer",
|
|
275
|
-
hasNote: false,
|
|
276
|
-
},
|
|
277
|
-
lastUpdateTime: 3_000,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
await session.close("final answer");
|
|
281
|
-
|
|
282
|
-
expect(updateBodies).toHaveLength(0);
|
|
283
|
-
expect(replaceBodies).toHaveLength(1);
|
|
284
|
-
const replacePayload = JSON.parse(replaceBodies[0] ?? "{}") as {
|
|
285
|
-
element?: string;
|
|
286
|
-
sequence?: number;
|
|
287
|
-
uuid?: string;
|
|
288
|
-
};
|
|
289
|
-
expect({
|
|
290
|
-
...replacePayload,
|
|
291
|
-
element: JSON.parse(replacePayload.element ?? "{}"),
|
|
292
|
-
}).toEqual({
|
|
293
|
-
element: {
|
|
294
|
-
tag: "markdown",
|
|
295
|
-
content: "final answer",
|
|
296
|
-
element_id: "content",
|
|
297
|
-
},
|
|
298
|
-
sequence: 2,
|
|
299
|
-
uuid: "r_card_4_2",
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("logs a final replacement failure when CardKit returns non-OK", async () => {
|
|
304
|
-
vi.useFakeTimers();
|
|
305
|
-
vi.setSystemTime(4_500);
|
|
306
|
-
const updateBodies: string[] = [];
|
|
307
|
-
const replaceBodies: string[] = [];
|
|
308
|
-
mockFetches(
|
|
309
|
-
updateBodies,
|
|
310
|
-
new Set<number>(),
|
|
311
|
-
replaceBodies,
|
|
312
|
-
new Map<number, number>(),
|
|
313
|
-
new Map([[0, 500]]),
|
|
314
|
-
);
|
|
315
|
-
const log = vi.fn();
|
|
316
|
-
|
|
317
|
-
const session = new FeishuStreamingSession(
|
|
318
|
-
{} as never,
|
|
319
|
-
{
|
|
320
|
-
appId: "app_final_rewrite_non_ok",
|
|
321
|
-
appSecret: "secret",
|
|
322
|
-
},
|
|
323
|
-
log,
|
|
324
|
-
);
|
|
325
|
-
setStreamingSessionInternals(session, {
|
|
326
|
-
state: {
|
|
327
|
-
cardId: "card_6",
|
|
328
|
-
messageId: "om_6",
|
|
329
|
-
sequence: 1,
|
|
330
|
-
currentText: "working\n\nfinal answer",
|
|
331
|
-
sentText: "working\n\nfinal answer",
|
|
332
|
-
hasNote: false,
|
|
333
|
-
},
|
|
334
|
-
lastUpdateTime: 3_000,
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
await session.close("final answer");
|
|
338
|
-
|
|
339
|
-
expect(updateBodies).toHaveLength(0);
|
|
340
|
-
expect(replaceBodies).toHaveLength(1);
|
|
341
|
-
expect(log).toHaveBeenCalledWith(
|
|
342
|
-
"Final replace failed: Error: Replace card content failed with HTTP 500",
|
|
343
|
-
);
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
describe("mergeStreamingText", () => {
|
|
348
|
-
it("prefers the latest full text when it already includes prior text", () => {
|
|
349
|
-
expect(mergeStreamingText("hello", "hello world")).toBe("hello world");
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it("keeps previous text when the next partial is empty or redundant", () => {
|
|
353
|
-
expect(mergeStreamingText("hello", "")).toBe("hello");
|
|
354
|
-
expect(mergeStreamingText("hello world", "hello")).toBe("hello world");
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it("appends fragmented chunks without injecting newlines", () => {
|
|
358
|
-
expect(mergeStreamingText("hello wor", "ld")).toBe("hello world");
|
|
359
|
-
expect(mergeStreamingText("line1", "line2")).toBe("line1line2");
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it("merges overlap between adjacent partial snapshots", () => {
|
|
363
|
-
expect(mergeStreamingText("好的,让我", "让我再读取一遍")).toBe("好的,让我再读取一遍");
|
|
364
|
-
expect(mergeStreamingText("revision_id: 552", "2,一点变化都没有")).toBe(
|
|
365
|
-
"revision_id: 552,一点变化都没有",
|
|
366
|
-
);
|
|
367
|
-
expect(mergeStreamingText("abc", "cabc")).toBe("cabc");
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
describe("resolveStreamingCardSendMode", () => {
|
|
372
|
-
it("prefers message.reply when reply target and root id both exist", () => {
|
|
373
|
-
expect(
|
|
374
|
-
resolveStreamingCardSendMode({
|
|
375
|
-
replyToMessageId: "om_parent",
|
|
376
|
-
rootId: "om_topic_root",
|
|
377
|
-
}),
|
|
378
|
-
).toBe("reply");
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it("falls back to root create when reply target is absent", () => {
|
|
382
|
-
expect(
|
|
383
|
-
resolveStreamingCardSendMode({
|
|
384
|
-
rootId: "om_topic_root",
|
|
385
|
-
}),
|
|
386
|
-
).toBe("root_create");
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it("uses create mode when no reply routing fields are provided", () => {
|
|
390
|
-
expect(resolveStreamingCardSendMode()).toBe("create");
|
|
391
|
-
expect(
|
|
392
|
-
resolveStreamingCardSendMode({
|
|
393
|
-
replyInThread: true,
|
|
394
|
-
}),
|
|
395
|
-
).toBe("create");
|
|
396
|
-
});
|
|
397
|
-
});
|