@rynx-ai/plugin-channel-lark 0.1.9

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 (41) hide show
  1. package/dist/cli-mirror/mirror-index.d.ts +46 -0
  2. package/dist/cli-mirror/mirror-index.js +48 -0
  3. package/dist/cli-mirror/rollout-mirror.d.ts +85 -0
  4. package/dist/cli-mirror/rollout-mirror.js +333 -0
  5. package/dist/cli-mirror/rollout-watcher.d.ts +109 -0
  6. package/dist/cli-mirror/rollout-watcher.js +347 -0
  7. package/dist/host-adapters.d.ts +82 -0
  8. package/dist/host-adapters.js +404 -0
  9. package/dist/index.d.ts +7 -0
  10. package/dist/index.js +7 -0
  11. package/dist/lark-card-stream.d.ts +363 -0
  12. package/dist/lark-card-stream.js +1335 -0
  13. package/dist/lark-content.d.ts +30 -0
  14. package/dist/lark-content.js +138 -0
  15. package/dist/lark-conversation-directory.d.ts +65 -0
  16. package/dist/lark-conversation-directory.js +106 -0
  17. package/dist/lark-diff-card.d.ts +25 -0
  18. package/dist/lark-diff-card.js +58 -0
  19. package/dist/lark-fork-transcript.d.ts +16 -0
  20. package/dist/lark-fork-transcript.js +141 -0
  21. package/dist/lark-resume-card.d.ts +31 -0
  22. package/dist/lark-resume-card.js +105 -0
  23. package/dist/lark-sdk/client.d.ts +8 -0
  24. package/dist/lark-sdk/client.js +32 -0
  25. package/dist/lark-sdk/index.d.ts +1 -0
  26. package/dist/lark-sdk/index.js +1 -0
  27. package/dist/lark-sender.d.ts +114 -0
  28. package/dist/lark-sender.js +323 -0
  29. package/dist/lark-session-store.d.ts +13 -0
  30. package/dist/lark-session-store.js +14 -0
  31. package/dist/lark-settings-card.d.ts +55 -0
  32. package/dist/lark-settings-card.js +158 -0
  33. package/dist/lark-setup.d.ts +17 -0
  34. package/dist/lark-setup.js +86 -0
  35. package/dist/lark.d.ts +500 -0
  36. package/dist/lark.js +2010 -0
  37. package/dist/runtime.d.ts +16 -0
  38. package/dist/runtime.js +157 -0
  39. package/dist/runtime.mjs +130863 -0
  40. package/package.json +35 -0
  41. package/rynx-plugin.json +42 -0
@@ -0,0 +1,105 @@
1
+ /** applink that opens a chat (p2p or group) in the Feishu client. Topic
2
+ * threads have no deep-link, so those are forwarded instead (see the
3
+ * `resume_forward` card action). */
4
+ export function buildChatApplink(chatId) {
5
+ return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
6
+ }
7
+ /** A single-button card whose button opens `chatId` (used by /fork to give the
8
+ * user a one-tap entry into the new group from the current conversation). */
9
+ export function buildOpenChatCardJson(chatId, chatName) {
10
+ const url = buildChatApplink(chatId);
11
+ const card = {
12
+ schema: "2.0",
13
+ header: {
14
+ title: { tag: "plain_text", content: "已 fork 到新群" },
15
+ template: "green",
16
+ },
17
+ body: {
18
+ elements: [
19
+ { tag: "markdown", content: `新群聊:**${chatName}**\n已搬运历史记录并带上当前会话上下文。` },
20
+ {
21
+ tag: "button",
22
+ text: { tag: "plain_text", content: "进入新群聊" },
23
+ type: "primary",
24
+ behaviors: [
25
+ { type: "open_url", default_url: url, pc_url: url, ios_url: url, android_url: url },
26
+ ],
27
+ },
28
+ ],
29
+ },
30
+ };
31
+ return JSON.stringify(card);
32
+ }
33
+ function buildResumeButton(record, target) {
34
+ if (record.kind === "thread" && record.threadId) {
35
+ return {
36
+ tag: "button",
37
+ text: { tag: "plain_text", content: "转发到此" },
38
+ type: "primary",
39
+ size: "small",
40
+ behaviors: [
41
+ {
42
+ type: "callback",
43
+ value: {
44
+ kind: "resume_forward",
45
+ shortId: record.shortId,
46
+ sourceThreadId: record.threadId,
47
+ targetChatId: target.chatId,
48
+ ...(target.threadId ? { targetThreadId: target.threadId } : {}),
49
+ },
50
+ },
51
+ ],
52
+ };
53
+ }
54
+ const url = buildChatApplink(record.chatId);
55
+ return {
56
+ tag: "button",
57
+ text: { tag: "plain_text", content: "打开" },
58
+ type: "primary",
59
+ size: "small",
60
+ behaviors: [
61
+ { type: "open_url", default_url: url, pc_url: url, ios_url: url, android_url: url },
62
+ ],
63
+ };
64
+ }
65
+ /**
66
+ * Build a CardKit 2.0 card listing recent conversations. Each row shows the
67
+ * title + short id, with a button that either opens the chat (applink) or
68
+ * forwards the topic thread into `targetChatId`.
69
+ */
70
+ export function buildResumeCardJson(records, target) {
71
+ const elements = records.map((record, index) => ({
72
+ tag: "column_set",
73
+ flex_mode: "none",
74
+ horizontal_align: "left",
75
+ columns: [
76
+ {
77
+ tag: "column",
78
+ width: "weighted",
79
+ weight: 1,
80
+ vertical_align: "center",
81
+ elements: [
82
+ {
83
+ tag: "markdown",
84
+ content: `**${index + 1}.** ${record.title} <font color='grey'>\`${record.shortId}\`</font>`,
85
+ },
86
+ ],
87
+ },
88
+ {
89
+ tag: "column",
90
+ width: "auto",
91
+ vertical_align: "center",
92
+ elements: [buildResumeButton(record, target)],
93
+ },
94
+ ],
95
+ }));
96
+ const card = {
97
+ schema: "2.0",
98
+ header: {
99
+ title: { tag: "plain_text", content: "最近会话" },
100
+ template: "blue",
101
+ },
102
+ body: { elements },
103
+ };
104
+ return JSON.stringify(card);
105
+ }
@@ -0,0 +1,8 @@
1
+ import * as Lark from "@larksuiteoapi/node-sdk";
2
+ export interface LarkSdkClientConfig {
3
+ appId?: string;
4
+ appSecret?: string;
5
+ }
6
+ export declare function createLarkSdkClientConfig(env?: NodeJS.ProcessEnv): LarkSdkClientConfig;
7
+ export declare function createLarkSdkClient(config?: LarkSdkClientConfig, logger?: Lark.Logger): Lark.Client;
8
+ export declare function createLarkWsClient(config?: LarkSdkClientConfig, logger?: Lark.Logger): Lark.WSClient;
@@ -0,0 +1,32 @@
1
+ import * as Lark from "@larksuiteoapi/node-sdk";
2
+ export function createLarkSdkClientConfig(env = process.env) {
3
+ return {
4
+ appId: env.LARK_APP_ID?.trim() || undefined,
5
+ appSecret: env.LARK_APP_SECRET?.trim() || undefined,
6
+ };
7
+ }
8
+ export function createLarkSdkClient(config = createLarkSdkClientConfig(), logger) {
9
+ return new Lark.Client({
10
+ appId: requireLarkConfig(config.appId, "LARK_APP_ID"),
11
+ appSecret: requireLarkConfig(config.appSecret, "LARK_APP_SECRET"),
12
+ appType: Lark.AppType.SelfBuild,
13
+ domain: Lark.Domain.Feishu,
14
+ loggerLevel: Lark.LoggerLevel.info,
15
+ ...(logger ? { logger } : {}),
16
+ });
17
+ }
18
+ export function createLarkWsClient(config = createLarkSdkClientConfig(), logger) {
19
+ return new Lark.WSClient({
20
+ appId: requireLarkConfig(config.appId, "LARK_APP_ID"),
21
+ appSecret: requireLarkConfig(config.appSecret, "LARK_APP_SECRET"),
22
+ autoReconnect: true,
23
+ loggerLevel: Lark.LoggerLevel.info,
24
+ ...(logger ? { logger } : {}),
25
+ });
26
+ }
27
+ function requireLarkConfig(value, key) {
28
+ if (!value) {
29
+ throw new Error(`Missing required configuration ${key}`);
30
+ }
31
+ return value;
32
+ }
@@ -0,0 +1 @@
1
+ export * from "./client.js";
@@ -0,0 +1 @@
1
+ export * from "./client.js";
@@ -0,0 +1,114 @@
1
+ import * as Lark from "@larksuiteoapi/node-sdk";
2
+ import type { LarkFetchedMessage, LarkMessageSender, LarkWsClientProvider, LarkWsClientStartOptions } from "./lark.js";
3
+ export declare class LarkSdkMessageSender implements LarkMessageSender {
4
+ private readonly client;
5
+ constructor(client: Lark.Client);
6
+ getMessage({ messageId }: {
7
+ messageId: string;
8
+ }): Promise<LarkFetchedMessage | null>;
9
+ createText({ chatId, text, uuid, }: {
10
+ chatId: string;
11
+ text: string;
12
+ uuid: string;
13
+ }): Promise<void>;
14
+ createReaction({ messageId, emojiType, }: {
15
+ messageId: string;
16
+ emojiType: string;
17
+ }): Promise<string | undefined>;
18
+ deleteReaction({ messageId, reactionId, }: {
19
+ messageId: string;
20
+ reactionId: string;
21
+ }): Promise<void>;
22
+ replyText({ messageId, text, uuid, replyInThread, }: {
23
+ messageId: string;
24
+ text: string;
25
+ uuid: string;
26
+ replyInThread: boolean;
27
+ }): Promise<void>;
28
+ createCard({ cardJson }: {
29
+ cardJson: string;
30
+ }): Promise<{
31
+ cardId: string;
32
+ }>;
33
+ batchUpdateCard({ cardId, sequence, actions, uuid, }: {
34
+ cardId: string;
35
+ sequence: number;
36
+ actions: string;
37
+ uuid?: string;
38
+ }): Promise<void>;
39
+ sendInteractive({ chatId, cardId, uuid, }: {
40
+ chatId: string;
41
+ cardId: string;
42
+ uuid: string;
43
+ }): Promise<{
44
+ messageId: string;
45
+ }>;
46
+ replyInteractive({ messageId, cardId, uuid, replyInThread, }: {
47
+ messageId: string;
48
+ cardId: string;
49
+ uuid: string;
50
+ replyInThread: boolean;
51
+ }): Promise<{
52
+ messageId: string;
53
+ }>;
54
+ forwardThread({ threadId, receiveId, receiveIdType, uuid, }: {
55
+ /** Source thread being forwarded. */
56
+ threadId: string;
57
+ /** Target id: a chat_id, or a thread_id to land inside that thread. */
58
+ receiveId: string;
59
+ receiveIdType: "chat_id" | "thread_id";
60
+ uuid: string;
61
+ }): Promise<void>;
62
+ sendText({ chatId, text, uuid, }: {
63
+ chatId: string;
64
+ text: string;
65
+ uuid: string;
66
+ }): Promise<{
67
+ messageId: string;
68
+ threadId?: string;
69
+ }>;
70
+ createChat({ name, userIdList, uuid, }: {
71
+ name: string;
72
+ userIdList: string[];
73
+ uuid: string;
74
+ }): Promise<{
75
+ chatId: string;
76
+ }>;
77
+ getChat({ chatId }: {
78
+ chatId: string;
79
+ }): Promise<{
80
+ name: string;
81
+ }>;
82
+ listChatMembers({ chatId, }: {
83
+ chatId: string;
84
+ }): Promise<Array<{
85
+ memberId: string;
86
+ name: string;
87
+ }>>;
88
+ listMessages({ containerIdType, containerId, pageSize, }: {
89
+ containerIdType: "chat" | "thread";
90
+ containerId: string;
91
+ pageSize?: number;
92
+ }): Promise<Array<{
93
+ msgType: string;
94
+ senderId?: string;
95
+ senderType?: string;
96
+ content: string;
97
+ }>>;
98
+ }
99
+ export declare class LarkClientProvider implements LarkWsClientProvider {
100
+ readonly client: Lark.Client;
101
+ private readonly wsClient;
102
+ private botOpenIdPromise?;
103
+ constructor(options?: Record<string, unknown>, logger?: Lark.Logger);
104
+ getMessageSender(): LarkMessageSender;
105
+ /**
106
+ * This bot's own `open_id`, fetched once from the bot info API and memoized.
107
+ * Lets the ingestor tell, in a group hosting multiple bots, whether an
108
+ * incoming `@` actually targets us. Returns `null` if the lookup fails so the
109
+ * caller can fall back to its legacy behavior rather than going silent.
110
+ */
111
+ getBotOpenId(): Promise<string | null>;
112
+ start({ onMessage, onCardAction }: LarkWsClientStartOptions): Promise<void>;
113
+ stop(): Promise<void>;
114
+ }
@@ -0,0 +1,323 @@
1
+ import * as Lark from "@larksuiteoapi/node-sdk";
2
+ import { createLarkSdkClient, createLarkWsClient } from "./lark-sdk/index.js";
3
+ export class LarkSdkMessageSender {
4
+ client;
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ async getMessage({ messageId }) {
9
+ const response = await this.client.im.v1.message.get({
10
+ path: {
11
+ message_id: messageId,
12
+ },
13
+ });
14
+ if (response.code && response.code !== 0) {
15
+ throw new Error(response.msg ?? "Failed to get the Feishu message.");
16
+ }
17
+ const item = response.data?.items?.[0];
18
+ if (!item) {
19
+ return null;
20
+ }
21
+ return {
22
+ messageId: item.message_id,
23
+ msgType: item.msg_type,
24
+ body: {
25
+ content: item.body?.content,
26
+ },
27
+ };
28
+ }
29
+ async createText({ chatId, text, uuid, }) {
30
+ const response = await this.client.im.message.create({
31
+ params: {
32
+ receive_id_type: "chat_id",
33
+ },
34
+ data: {
35
+ receive_id: chatId,
36
+ msg_type: "text",
37
+ content: JSON.stringify({ text }),
38
+ uuid,
39
+ },
40
+ });
41
+ if (response.code && response.code !== 0) {
42
+ throw new Error(response.msg ?? "Failed to send Feishu chat message.");
43
+ }
44
+ }
45
+ async createReaction({ messageId, emojiType, }) {
46
+ const response = await this.client.im.messageReaction.create({
47
+ path: {
48
+ message_id: messageId,
49
+ },
50
+ data: {
51
+ reaction_type: {
52
+ emoji_type: emojiType,
53
+ },
54
+ },
55
+ });
56
+ if (response.code && response.code !== 0) {
57
+ throw new Error(response.msg ?? "Failed to add Feishu message reaction.");
58
+ }
59
+ return response.data?.reaction_id;
60
+ }
61
+ async deleteReaction({ messageId, reactionId, }) {
62
+ const response = await this.client.im.messageReaction.delete({
63
+ path: {
64
+ message_id: messageId,
65
+ reaction_id: reactionId,
66
+ },
67
+ });
68
+ if (response.code && response.code !== 0) {
69
+ throw new Error(response.msg ?? "Failed to delete Feishu message reaction.");
70
+ }
71
+ }
72
+ async replyText({ messageId, text, uuid, replyInThread, }) {
73
+ const response = await this.client.im.message.reply({
74
+ path: {
75
+ message_id: messageId,
76
+ },
77
+ data: {
78
+ msg_type: "text",
79
+ content: JSON.stringify({ text }),
80
+ reply_in_thread: replyInThread,
81
+ uuid,
82
+ },
83
+ });
84
+ if (response.code && response.code !== 0) {
85
+ throw new Error(response.msg ?? "Failed to reply to the Feishu message.");
86
+ }
87
+ }
88
+ async createCard({ cardJson }) {
89
+ const response = await this.client.cardkit.v1.card.create({
90
+ data: {
91
+ type: "card_json",
92
+ data: cardJson,
93
+ },
94
+ });
95
+ if (response.code && response.code !== 0) {
96
+ throw new Error(response.msg ?? "Failed to create the Feishu CardKit card.");
97
+ }
98
+ const cardId = response.data?.card_id;
99
+ if (!cardId) {
100
+ throw new Error("Feishu CardKit create returned no card_id.");
101
+ }
102
+ return { cardId };
103
+ }
104
+ async batchUpdateCard({ cardId, sequence, actions, uuid, }) {
105
+ const response = await this.client.cardkit.v1.card.batchUpdate({
106
+ data: {
107
+ sequence,
108
+ actions,
109
+ ...(uuid ? { uuid } : {}),
110
+ },
111
+ path: {
112
+ card_id: cardId,
113
+ },
114
+ });
115
+ if (response.code && response.code !== 0) {
116
+ throw new Error(response.msg ?? "Failed to batch update the Feishu CardKit card.");
117
+ }
118
+ }
119
+ async sendInteractive({ chatId, cardId, uuid, }) {
120
+ const response = await this.client.im.message.create({
121
+ params: {
122
+ receive_id_type: "chat_id",
123
+ },
124
+ data: {
125
+ receive_id: chatId,
126
+ msg_type: "interactive",
127
+ content: JSON.stringify({ type: "card", data: { card_id: cardId } }),
128
+ uuid,
129
+ },
130
+ });
131
+ if (response.code && response.code !== 0) {
132
+ throw new Error(response.msg ?? "Failed to send the Feishu interactive card.");
133
+ }
134
+ const messageId = response.data?.message_id;
135
+ if (!messageId) {
136
+ throw new Error("Feishu interactive send returned no message_id.");
137
+ }
138
+ return { messageId };
139
+ }
140
+ async replyInteractive({ messageId, cardId, uuid, replyInThread, }) {
141
+ const response = await this.client.im.message.reply({
142
+ path: {
143
+ message_id: messageId,
144
+ },
145
+ data: {
146
+ msg_type: "interactive",
147
+ content: JSON.stringify({ type: "card", data: { card_id: cardId } }),
148
+ reply_in_thread: replyInThread,
149
+ uuid,
150
+ },
151
+ });
152
+ if (response.code && response.code !== 0) {
153
+ throw new Error(response.msg ?? "Failed to reply with the Feishu interactive card.");
154
+ }
155
+ const replyMessageId = response.data?.message_id;
156
+ if (!replyMessageId) {
157
+ throw new Error("Feishu interactive reply returned no message_id.");
158
+ }
159
+ return { messageId: replyMessageId };
160
+ }
161
+ async forwardThread({ threadId, receiveId, receiveIdType, uuid, }) {
162
+ const response = await this.client.im.v1.thread.forward({
163
+ path: { thread_id: threadId },
164
+ params: { receive_id_type: receiveIdType, uuid },
165
+ data: { receive_id: receiveId },
166
+ });
167
+ if (response.code && response.code !== 0) {
168
+ throw new Error(response.msg ?? "Failed to forward the Feishu thread.");
169
+ }
170
+ }
171
+ async sendText({ chatId, text, uuid, }) {
172
+ const response = await this.client.im.message.create({
173
+ params: { receive_id_type: "chat_id" },
174
+ data: {
175
+ receive_id: chatId,
176
+ msg_type: "text",
177
+ content: JSON.stringify({ text }),
178
+ uuid,
179
+ },
180
+ });
181
+ if (response.code && response.code !== 0) {
182
+ throw new Error(response.msg ?? "Failed to send Feishu chat message.");
183
+ }
184
+ const data = response.data;
185
+ return { messageId: data?.message_id ?? "", threadId: data?.thread_id };
186
+ }
187
+ async createChat({ name, userIdList, uuid, }) {
188
+ const response = await this.client.im.v1.chat.create({
189
+ params: { user_id_type: "open_id", uuid },
190
+ data: { name, user_id_list: userIdList, group_message_type: "chat" },
191
+ });
192
+ if (response.code && response.code !== 0) {
193
+ throw new Error(response.msg ?? "Failed to create the Feishu chat.");
194
+ }
195
+ const chatId = response.data?.chat_id;
196
+ if (!chatId) {
197
+ throw new Error("Feishu chat create returned no chat_id.");
198
+ }
199
+ return { chatId };
200
+ }
201
+ async getChat({ chatId }) {
202
+ const response = await this.client.im.v1.chat.get({ path: { chat_id: chatId } });
203
+ if (response.code && response.code !== 0) {
204
+ throw new Error(response.msg ?? "Failed to get the Feishu chat.");
205
+ }
206
+ const name = response.data?.name ?? "";
207
+ return { name };
208
+ }
209
+ async listChatMembers({ chatId, }) {
210
+ const response = await this.client.im.v1.chatMembers.get({
211
+ path: { chat_id: chatId },
212
+ params: { member_id_type: "open_id", page_size: 100 },
213
+ });
214
+ if (response.code && response.code !== 0) {
215
+ throw new Error(response.msg ?? "Failed to list Feishu chat members.");
216
+ }
217
+ const items = response.data
218
+ ?.items ?? [];
219
+ return items
220
+ .filter((item) => item.member_id)
221
+ .map((item) => ({ memberId: item.member_id, name: item.name ?? "" }));
222
+ }
223
+ async listMessages({ containerIdType, containerId, pageSize = 50, }) {
224
+ const response = await this.client.im.v1.message.list({
225
+ params: {
226
+ container_id_type: containerIdType,
227
+ container_id: containerId,
228
+ sort_type: "ByCreateTimeAsc",
229
+ page_size: pageSize,
230
+ },
231
+ });
232
+ if (response.code && response.code !== 0) {
233
+ throw new Error(response.msg ?? "Failed to list Feishu messages.");
234
+ }
235
+ const items = response.data?.items ?? [];
236
+ return items.map((item) => ({
237
+ msgType: item.msg_type ?? "",
238
+ senderId: item.sender?.id,
239
+ senderType: item.sender?.sender_type,
240
+ content: item.body?.content ?? "",
241
+ }));
242
+ }
243
+ }
244
+ /** Read a per-instance string option, trimmed; undefined when absent/blank. */
245
+ function larkOption(options, key) {
246
+ const value = options?.[key];
247
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
248
+ }
249
+ export class LarkClientProvider {
250
+ client;
251
+ wsClient;
252
+ botOpenIdPromise;
253
+ constructor(options, logger) {
254
+ // Credentials come from this channel instance's stored config (`options`).
255
+ const appId = larkOption(options, "LARK_APP_ID");
256
+ const appSecret = larkOption(options, "LARK_APP_SECRET");
257
+ this.client = createLarkSdkClient({ appId: appId, appSecret: appSecret }, logger);
258
+ this.wsClient = createLarkWsClient({ appId: appId, appSecret: appSecret }, logger);
259
+ }
260
+ getMessageSender() {
261
+ return new LarkSdkMessageSender(this.client);
262
+ }
263
+ /**
264
+ * This bot's own `open_id`, fetched once from the bot info API and memoized.
265
+ * Lets the ingestor tell, in a group hosting multiple bots, whether an
266
+ * incoming `@` actually targets us. Returns `null` if the lookup fails so the
267
+ * caller can fall back to its legacy behavior rather than going silent.
268
+ */
269
+ async getBotOpenId() {
270
+ if (!this.botOpenIdPromise) {
271
+ this.botOpenIdPromise = this.client
272
+ .request({
273
+ method: "GET",
274
+ url: "/open-apis/bot/v3/info",
275
+ })
276
+ .then((response) => response?.bot?.open_id ?? null)
277
+ .catch(() => null);
278
+ }
279
+ return this.botOpenIdPromise;
280
+ }
281
+ async start({ onMessage, onCardAction }) {
282
+ const handlers = {
283
+ "im.message.receive_v1": async (data) => {
284
+ onMessage(data);
285
+ },
286
+ };
287
+ if (onCardAction) {
288
+ // Feishu CardKit button clicks arrive as `card.action.trigger` events
289
+ // over the same WS pipe (self-built bot, persistent connection).
290
+ // The return value (e.g. `{ toast: {...} }`) is forwarded back to
291
+ // Feishu so the user gets immediate UI feedback — also required for
292
+ // Feishu to consider the callback well-formed (avoids error 200340).
293
+ handlers["card.action.trigger"] = async (data) => {
294
+ const payload = data;
295
+ const rawValue = payload.action?.value;
296
+ const value = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue)
297
+ ? rawValue
298
+ : null;
299
+ // `option` carries the picked value of a `select_static`; `form_value`
300
+ // carries all fields of a `form_submit`. Both are surfaced so settings
301
+ // cards can read selections without a dedicated form round-trip.
302
+ const option = typeof payload.action?.option === "string" ? payload.action.option : null;
303
+ const rawForm = payload.action?.form_value;
304
+ const formValue = rawForm && typeof rawForm === "object" && !Array.isArray(rawForm)
305
+ ? rawForm
306
+ : null;
307
+ return await onCardAction({
308
+ value,
309
+ option,
310
+ formValue,
311
+ messageId: payload.context?.open_message_id,
312
+ });
313
+ };
314
+ }
315
+ const eventDispatcher = new Lark.EventDispatcher({}).register(handlers);
316
+ await this.wsClient.start({
317
+ eventDispatcher,
318
+ });
319
+ }
320
+ async stop() {
321
+ this.wsClient.close();
322
+ }
323
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lark binding over the channel-agnostic @rynx-ai/core conversation session store.
3
+ * The store engine (the conversation → current-session + execution binding) lives
4
+ * in core; Lark only supplies the per-instance file path + keeps the historical
5
+ * `Lark*` export names so existing importers are unchanged.
6
+ */
7
+ import { FileConversationSessionStore, type ConversationSessionStoreOptions } from "@rynx-ai/core";
8
+ export type { ConversationSessionRecord as LarkActiveSessionRecord, ConversationSessionStore as LarkActiveSessionStore, SessionSettingsUpdate as LarkSettingsUpdate, EnsureSessionResult as LarkEnsureSessionResult, RotateSessionResult as LarkRotateSessionResult, SessionExecution as LarkSessionExecution, } from "@rynx-ai/core";
9
+ /** File-backed active-session store for a Lark bot instance: the core engine with
10
+ * the Lark per-instance file path injected by default. */
11
+ export declare class FileLarkActiveSessionStore extends FileConversationSessionStore {
12
+ constructor(options: ConversationSessionStoreOptions);
13
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Lark binding over the channel-agnostic @rynx-ai/core conversation session store.
3
+ * The store engine (the conversation → current-session + execution binding) lives
4
+ * in core; Lark only supplies the per-instance file path + keeps the historical
5
+ * `Lark*` export names so existing importers are unchanged.
6
+ */
7
+ import { FileConversationSessionStore, } from "@rynx-ai/core";
8
+ /** File-backed active-session store for a Lark bot instance: the core engine with
9
+ * the Lark per-instance file path injected by default. */
10
+ export class FileLarkActiveSessionStore extends FileConversationSessionStore {
11
+ constructor(options) {
12
+ super(options);
13
+ }
14
+ }
@@ -0,0 +1,55 @@
1
+ import type { AgentRuntimeId } from "@rynx-ai/core";
2
+ import type { ReasoningEffort } from "@rynx-ai/core";
3
+ /** Sentinel option value meaning "use the runtime/model default". */
4
+ export declare const SETTINGS_DEFAULT_VALUE = "__default__";
5
+ export interface SettingsCardModelOption {
6
+ value: string;
7
+ label: string;
8
+ isDefault?: boolean;
9
+ /** Reasoning efforts this model supports; empty/unknown → full effort set. */
10
+ supportedEfforts?: ReasoningEffort[];
11
+ }
12
+ export interface SettingsCardRuntimeOption {
13
+ value: AgentRuntimeId;
14
+ label: string;
15
+ }
16
+ export interface SettingsCardInput {
17
+ sessionKey: string;
18
+ /** `/runtime` shows the runtime selector; `/model` hides it. */
19
+ includeRuntime: boolean;
20
+ runtimeOptions: SettingsCardRuntimeOption[];
21
+ /** Models available for the selected runtime (excludes the default sentinel). */
22
+ models: SettingsCardModelOption[];
23
+ /** Pre-selected dropdown values (drive `initial_option`). */
24
+ selectedRuntime: AgentRuntimeId;
25
+ selectedModel: string;
26
+ selectedEffort: string;
27
+ }
28
+ export declare const SETTINGS_EFFORT_VALUES: ReasoningEffort[];
29
+ /**
30
+ * Build the `/runtime` / `/model` settings card object: dropdowns for runtime
31
+ * (optional), model and reasoning effort, plus confirm / cancel buttons.
32
+ * Selections round-trip via `card.action.trigger` callbacks (`settings_select`
33
+ * per change — runtime/model changes re-render this card; `settings_apply` /
34
+ * `settings_cancel` on the buttons). The effort options reflect the currently
35
+ * selected model's supported efforts.
36
+ */
37
+ export declare function buildSettingsCard(input: SettingsCardInput): Record<string, unknown>;
38
+ /** String form of {@link buildSettingsCard} (for the initial card send). */
39
+ export declare function buildSettingsCardJson(input: SettingsCardInput): string;
40
+ /**
41
+ * Build a static result card shown after the user confirms or cancels the
42
+ * settings card — replaces the interactive form with a plain conclusion.
43
+ * Returned as a plain object so it can be embedded in a card-action response.
44
+ */
45
+ export declare function buildSettingsResultCard(input: {
46
+ includeRuntime: boolean;
47
+ template: "green" | "grey";
48
+ content: string;
49
+ }): Record<string, unknown>;
50
+ /** String form of {@link buildSettingsResultCard}. */
51
+ export declare function buildSettingsResultCardJson(input: {
52
+ includeRuntime: boolean;
53
+ template: "green" | "grey";
54
+ content: string;
55
+ }): string;