@rynx-ai/daemon 0.1.10 → 0.1.11-beta.2
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/bundled-plugins/.rynx-plugin/marketplace.json +10 -0
- package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +37 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.d.ts +48 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +54 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.d.ts +85 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.js +333 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.d.ts +109 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.js +347 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +95 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +504 -0
- package/bundled-plugins/plugins/lark/dist/index.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/index.js +7 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.d.ts +363 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.js +1335 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.js +135 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.js +138 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.d.ts +65 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.js +106 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.d.ts +25 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.js +58 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.d.ts +16 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.js +141 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.d.ts +31 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.js +105 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.d.ts +18 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.js +95 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.d.ts +8 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.js +32 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.d.ts +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.js +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.d.ts +114 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.js +323 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.js +14 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.d.ts +55 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.js +158 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.d.ts +39 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.js +103 -0
- package/bundled-plugins/plugins/lark/dist/lark.d.ts +507 -0
- package/bundled-plugins/plugins/lark/dist/lark.js +1977 -0
- package/bundled-plugins/plugins/lark/dist/runtime.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/runtime.js +131316 -0
- package/bundled-plugins/plugins/lark/dist/settings.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/settings.js +126958 -0
- package/bundled-plugins/plugins/lark/package.json +57 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +1 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-D9N-AkLX.js +201 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/index.html +13 -0
- package/dist/bundled-plugins.d.ts +15 -0
- package/dist/bundled-plugins.js +134 -0
- package/dist/cli.js +26 -208
- package/dist/control-deps.js +2 -79
- package/dist/daemon-server.d.ts +4 -1
- package/dist/daemon-server.js +167 -57
- package/dist/db.js +145 -124
- package/dist/index-daemon.js +25 -4
- package/dist/migrations/cleanup-legacy-sessions.d.ts +1 -1
- package/dist/migrations/cleanup-legacy-sessions.js +33 -15
- package/dist/official-plugins.d.ts +2 -0
- package/dist/official-plugins.js +2 -0
- package/dist/plugin-cli.d.ts +1 -3
- package/dist/plugin-cli.js +56 -45
- package/dist/plugin-host-leases.d.ts +4 -0
- package/dist/plugin-host-leases.js +7 -0
- package/dist/plugin-host-rpc.d.ts +42 -4
- package/dist/plugin-host-rpc.js +144 -34
- package/dist/plugin-installer.d.ts +24 -55
- package/dist/plugin-installer.js +202 -415
- package/dist/plugin-management-service.d.ts +30 -23
- package/dist/plugin-management-service.js +290 -105
- package/dist/plugin-marketplace.d.ts +35 -0
- package/dist/plugin-marketplace.js +299 -0
- package/dist/plugin-package.d.ts +22 -24
- package/dist/plugin-package.js +520 -571
- package/dist/plugin-skill-context.d.ts +4 -0
- package/dist/plugin-skill-context.js +35 -0
- package/dist/plugin-store.d.ts +36 -37
- package/dist/plugin-store.js +140 -92
- package/dist/plugin-supervisor.d.ts +45 -125
- package/dist/plugin-supervisor.js +516 -1053
- package/dist/pm2.js +15 -10
- package/dist/runtime-project-store.d.ts +9 -0
- package/dist/runtime-project-store.js +83 -0
- package/dist/session-fallback-workspace.d.ts +22 -0
- package/dist/session-fallback-workspace.js +120 -0
- package/dist/session-fork-store.d.ts +41 -0
- package/dist/session-fork-store.js +288 -0
- package/dist/session-launch-operations.d.ts +14 -2
- package/dist/session-launch-operations.js +34 -2
- package/dist/session-meta-store.d.ts +7 -9
- package/dist/session-meta-store.js +28 -49
- package/dist/setup.js +26 -14
- package/package.json +14 -11
- package/dist/channel-store.d.ts +0 -22
- package/dist/channel-store.js +0 -65
- package/dist/instance-store.d.ts +0 -38
- package/dist/instance-store.js +0 -85
- package/dist/plugin-install-restart.d.ts +0 -13
- package/dist/plugin-install-restart.js +0 -24
- package/dist/plugin-runtime-control.d.ts +0 -2
- package/dist/plugin-runtime-control.js +0 -37
- package/dist/registry.d.ts +0 -10
- package/dist/registry.js +0 -52
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const TRANSCRIPT_MAX_BYTES = 80 * 1024;
|
|
2
|
+
/** Extract a human-readable line from a Feishu message body `content` (a JSON
|
|
3
|
+
* string whose shape depends on `msgType`). Non-text types become a short
|
|
4
|
+
* placeholder so the transcript stays readable. */
|
|
5
|
+
export function extractMessageText(msgType, content) {
|
|
6
|
+
try {
|
|
7
|
+
const parsed = JSON.parse(content);
|
|
8
|
+
if (msgType === "text") {
|
|
9
|
+
return typeof parsed.text === "string" ? parsed.text.trim() : "";
|
|
10
|
+
}
|
|
11
|
+
if (msgType === "post") {
|
|
12
|
+
return extractPostText(parsed);
|
|
13
|
+
}
|
|
14
|
+
if (msgType === "interactive") {
|
|
15
|
+
return extractCardText(parsed) || "[卡片]";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// fall through to placeholder
|
|
20
|
+
}
|
|
21
|
+
switch (msgType) {
|
|
22
|
+
case "image":
|
|
23
|
+
return "[图片]";
|
|
24
|
+
case "file":
|
|
25
|
+
return "[文件]";
|
|
26
|
+
case "audio":
|
|
27
|
+
return "[语音]";
|
|
28
|
+
case "media":
|
|
29
|
+
return "[视频]";
|
|
30
|
+
case "sticker":
|
|
31
|
+
return "[表情]";
|
|
32
|
+
case "interactive":
|
|
33
|
+
return "[卡片]";
|
|
34
|
+
default:
|
|
35
|
+
return msgType ? `[${msgType}]` : "";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const CARD_TEXT_KEYS = new Set([
|
|
39
|
+
"text",
|
|
40
|
+
"content",
|
|
41
|
+
"title",
|
|
42
|
+
"lines",
|
|
43
|
+
"elements",
|
|
44
|
+
"columns",
|
|
45
|
+
"plain_text",
|
|
46
|
+
"i18n",
|
|
47
|
+
"header",
|
|
48
|
+
"body",
|
|
49
|
+
]);
|
|
50
|
+
const CARD_TEXT_MAX_CHARS = 400;
|
|
51
|
+
/** Best-effort visible-text extraction from an interactive card body. Returns
|
|
52
|
+
* "" when the card carries no inline text (e.g. a bare card_id reference), so
|
|
53
|
+
* the caller can fall back to a "[卡片]" placeholder. */
|
|
54
|
+
function extractCardText(parsed) {
|
|
55
|
+
const fragments = [];
|
|
56
|
+
collectCardText(parsed, fragments);
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const deduped = [];
|
|
59
|
+
for (const fragment of fragments) {
|
|
60
|
+
const normalized = fragment.replace(/\s+/g, " ").trim();
|
|
61
|
+
if (!normalized || seen.has(normalized)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
seen.add(normalized);
|
|
65
|
+
deduped.push(normalized);
|
|
66
|
+
}
|
|
67
|
+
const joined = deduped.join(" ");
|
|
68
|
+
if (!joined) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
return joined.length > CARD_TEXT_MAX_CHARS
|
|
72
|
+
? `${joined.slice(0, CARD_TEXT_MAX_CHARS)}…`
|
|
73
|
+
: joined;
|
|
74
|
+
}
|
|
75
|
+
function collectCardText(value, out) {
|
|
76
|
+
if (typeof value === "string") {
|
|
77
|
+
out.push(value);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
for (const item of value) {
|
|
82
|
+
collectCardText(item, out);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!value || typeof value !== "object") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
90
|
+
if (CARD_TEXT_KEYS.has(key)) {
|
|
91
|
+
collectCardText(nested, out);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function extractPostText(parsed) {
|
|
96
|
+
// post content is { <locale>: { title, content: item[][] } } or already the
|
|
97
|
+
// locale object. Collect title + any text/link fragments.
|
|
98
|
+
const locale = Array.isArray(parsed.content)
|
|
99
|
+
? parsed
|
|
100
|
+
: Object.values(parsed).find((value) => value && typeof value === "object" && Array.isArray(value.content));
|
|
101
|
+
if (!locale) {
|
|
102
|
+
return "[富文本]";
|
|
103
|
+
}
|
|
104
|
+
const parts = [];
|
|
105
|
+
if (typeof locale.title === "string" && locale.title.trim()) {
|
|
106
|
+
parts.push(locale.title.trim());
|
|
107
|
+
}
|
|
108
|
+
for (const block of locale.content ?? []) {
|
|
109
|
+
for (const item of block) {
|
|
110
|
+
if (typeof item.text === "string") {
|
|
111
|
+
parts.push(item.text);
|
|
112
|
+
}
|
|
113
|
+
else if (item.tag === "a" && typeof item.href === "string") {
|
|
114
|
+
parts.push(item.href);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return parts.join(" ").trim() || "[富文本]";
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Render a conversation history as a transcript: one `名字:内容` line per
|
|
122
|
+
* message. `nameById` maps a user open_id to a display name; messages from the
|
|
123
|
+
* bot (sender_type !== "user") are labelled 机器人. Bounded to a sane size.
|
|
124
|
+
*/
|
|
125
|
+
export function buildForkTranscript(messages, nameById) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
for (const message of messages) {
|
|
128
|
+
const text = extractMessageText(message.msgType, message.content);
|
|
129
|
+
if (!text) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const isBot = message.senderType !== "user" || !message.senderId;
|
|
133
|
+
const name = isBot ? "机器人" : nameById.get(message.senderId) || "用户";
|
|
134
|
+
lines.push(`${name}:${text}`);
|
|
135
|
+
}
|
|
136
|
+
let transcript = lines.join("\n");
|
|
137
|
+
if (Buffer.byteLength(transcript, "utf8") > TRANSCRIPT_MAX_BYTES) {
|
|
138
|
+
transcript = `${transcript.slice(0, TRANSCRIPT_MAX_BYTES)}\n…(记录过长已截断)`;
|
|
139
|
+
}
|
|
140
|
+
return transcript;
|
|
141
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LarkConversationRecord } from "./lark-conversation-directory.js";
|
|
2
|
+
/** applink that opens a chat (p2p or group) in the Feishu client. Topic
|
|
3
|
+
* threads have no deep-link, so those are forwarded instead (see the
|
|
4
|
+
* `resume_forward` card action). */
|
|
5
|
+
export declare function buildChatApplink(chatId: string): string;
|
|
6
|
+
/** A single-button card whose button opens `chatId` (used by /fork to give the
|
|
7
|
+
* user a one-tap entry into the new group from the current conversation). */
|
|
8
|
+
export declare function buildOpenChatCardJson(chatId: string, chatName: string): string;
|
|
9
|
+
/** Where /resume was invoked — the forward lands here. When `threadId` is
|
|
10
|
+
* set (invoked inside a topic thread) the forward targets that thread so it
|
|
11
|
+
* appears in-place; otherwise it targets the chat. */
|
|
12
|
+
export interface ResumeTarget {
|
|
13
|
+
chatId: string;
|
|
14
|
+
threadId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ResumeForwardValue {
|
|
17
|
+
kind: "resume_forward";
|
|
18
|
+
shortId: string;
|
|
19
|
+
/** The topic thread being resumed (forwarded). */
|
|
20
|
+
sourceThreadId: string;
|
|
21
|
+
/** Current chat to forward into. */
|
|
22
|
+
targetChatId: string;
|
|
23
|
+
/** Current thread to forward into, when /resume was invoked inside one. */
|
|
24
|
+
targetThreadId?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build a CardKit 2.0 card listing recent conversations. Each row shows the
|
|
28
|
+
* title + short id, with a button that either opens the chat (applink) or
|
|
29
|
+
* forwards the topic thread into `targetChatId`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildResumeCardJson(records: LarkConversationRecord[], target: ResumeTarget): string;
|
|
@@ -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,18 @@
|
|
|
1
|
+
import type { PluginHealth } from "@rynx-ai/plugin-sdk";
|
|
2
|
+
export interface LarkRuntimeInstanceState {
|
|
3
|
+
state: "starting" | "running" | "failed" | "stopping" | "stopped";
|
|
4
|
+
connected: boolean;
|
|
5
|
+
lastError: string | null;
|
|
6
|
+
lastEventAt: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface LarkRuntimeState {
|
|
9
|
+
active: boolean;
|
|
10
|
+
health: PluginHealth;
|
|
11
|
+
instances: Record<string, LarkRuntimeInstanceState>;
|
|
12
|
+
}
|
|
13
|
+
export declare class LarkRuntimeStateStore {
|
|
14
|
+
private readonly file;
|
|
15
|
+
constructor(dataDir: string);
|
|
16
|
+
read(): LarkRuntimeState;
|
|
17
|
+
write(state: LarkRuntimeState): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
export class LarkRuntimeStateStore {
|
|
5
|
+
file;
|
|
6
|
+
constructor(dataDir) {
|
|
7
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
8
|
+
this.file = path.join(dataDir, "lark-runtime-state.json");
|
|
9
|
+
}
|
|
10
|
+
read() {
|
|
11
|
+
if (!existsSync(this.file)) {
|
|
12
|
+
return { active: false, health: { state: "running" }, instances: {} };
|
|
13
|
+
}
|
|
14
|
+
const info = lstatSync(this.file);
|
|
15
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) {
|
|
16
|
+
return {
|
|
17
|
+
active: false,
|
|
18
|
+
health: { state: "degraded", message: "Runtime state is unavailable" },
|
|
19
|
+
instances: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return parseState(JSON.parse(readFileSync(this.file, "utf8")));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {
|
|
27
|
+
active: false,
|
|
28
|
+
health: { state: "degraded", message: "Runtime state is unavailable" },
|
|
29
|
+
instances: {},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
write(state) {
|
|
34
|
+
const temporary = `${this.file}.tmp-${randomUUID()}`;
|
|
35
|
+
try {
|
|
36
|
+
writeFileSync(temporary, `${JSON.stringify(state)}\n`, {
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
mode: 0o600,
|
|
39
|
+
flag: "wx",
|
|
40
|
+
});
|
|
41
|
+
chmodSync(temporary, 0o600);
|
|
42
|
+
renameSync(temporary, this.file);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
rmSync(temporary, { force: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function parseState(value) {
|
|
50
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
51
|
+
throw new Error("invalid runtime state");
|
|
52
|
+
}
|
|
53
|
+
const record = value;
|
|
54
|
+
if (typeof record.active !== "boolean")
|
|
55
|
+
throw new Error("invalid runtime activity");
|
|
56
|
+
const health = parseHealth(record.health);
|
|
57
|
+
if (typeof record.instances !== "object" || record.instances === null ||
|
|
58
|
+
Array.isArray(record.instances)) {
|
|
59
|
+
throw new Error("invalid runtime instances");
|
|
60
|
+
}
|
|
61
|
+
const instances = {};
|
|
62
|
+
for (const [id, raw] of Object.entries(record.instances)) {
|
|
63
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
64
|
+
throw new Error("invalid runtime instance");
|
|
65
|
+
}
|
|
66
|
+
const item = raw;
|
|
67
|
+
if (!["starting", "running", "failed", "stopping", "stopped"].includes(String(item.state)) ||
|
|
68
|
+
typeof item.connected !== "boolean" ||
|
|
69
|
+
(item.lastError !== null && typeof item.lastError !== "string") ||
|
|
70
|
+
(item.lastEventAt !== null && typeof item.lastEventAt !== "string")) {
|
|
71
|
+
throw new Error("invalid runtime instance");
|
|
72
|
+
}
|
|
73
|
+
instances[id] = {
|
|
74
|
+
state: item.state,
|
|
75
|
+
connected: item.connected,
|
|
76
|
+
lastError: item.lastError,
|
|
77
|
+
lastEventAt: item.lastEventAt,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { active: record.active, health, instances };
|
|
81
|
+
}
|
|
82
|
+
function parseHealth(value) {
|
|
83
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
84
|
+
throw new Error("invalid runtime health");
|
|
85
|
+
}
|
|
86
|
+
const record = value;
|
|
87
|
+
if ((record.state !== "running" && record.state !== "degraded") ||
|
|
88
|
+
(record.message !== undefined && typeof record.message !== "string")) {
|
|
89
|
+
throw new Error("invalid runtime health");
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
state: record.state,
|
|
93
|
+
...(typeof record.message === "string" ? { message: record.message } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -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
|
+
}
|